8

Once again I'm here seeking for some guidance on Typescript heuristics. I'm having trouble writing a type guard because Typescript wants to be too narrow when it comes to comparing.

Consider the following scenario (or the Typescript Playground):

const FOO = 'foo' as const;
const BAR = 'bar' as const;

const ALL_THINGS = [FOO, BAR];
type AllThingsType = typeof ALL_THINGS[number];

function isAllThings(value: unknown): value is AllThingsType {
  return typeof value === 'string' && ALL_THINGS.includes(value);
}

The error will be the following one:

Argument of type 'string' is not assignable to parameter of type '"foo" | "bar"'.ts(2345)

There is technically a way of working around this:

function isAllThingsWorkaround(value: unknown): value is AllThingsType {
  return typeof value === 'string' && (ALL_THINGS as string[]).includes(value);
}

Am I missing something in regards to how I should do this? The snippet I shared is a simplified version, you can assume that ALL_THINGS is actually a collection of almost 25 consts. How could I improve this in order to avoid the workaround?

Thanks for the help!

4
  • Try this let a: any = value; return typeof value === 'string' && ALL_THINGS.includes(a); Commented Jun 15, 2022 at 16:02
  • 2
    You've run into ms/TS#26255 and there are various workarounds, see linked Q/A for more info Commented Jun 15, 2022 at 16:07
  • I'm not sure it's exactly the same. Removing the "as const" from foo and bar allows them to be treated more widely as strings. After removing them, there are no errors in the playground. typescriptlang.org/docs/handbook/release-notes/… Commented Jun 15, 2022 at 16:10
  • 1
    Presumably OP actually wants string literal types, and widening them to string defeats the purpose. It should be perfectly safe to search for a string in an array of string literal types, but TypeScript doesn't let you do it, which is the topic of ms/TS#26255. Commented Jun 15, 2022 at 16:17

1 Answer 1

7

One way you could do this is to not use .includes.

const FOO = 'foo' as const;
const BAR = 'bar' as const;

const ALL_THINGS = [FOO, BAR];
type AllThingsType = typeof ALL_THINGS[number];

function isAllThings(value: unknown): value is AllThingsType {
  return typeof value === 'string' && ALL_THINGS.some(a => a === value);
}

console.log(isAllThings("cat")); // false
console.log(isAllThings("foo")); // true

This way does not require type casting, and you get to decide what "includes" actually means, rather than letting javascript decide.

Sign up to request clarification or add additional context in comments.

1 Comment

I wasn't sure why using includes wouldn't work if that's what makes sense for me but I think this approach is good enough, safe, and covers this scenario perfectly. Thank you for the suggestion.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.