2

I have read Operator '==' cannot be applied to types x and y in Typescript 2 and it has not been informative to my case.

In TypeScript 2.5.3, we define many models using string enums of the form:

export interface Event {
   category: "MORNING" | "NIGHT";
   enabled: boolean;
}

And then apply comparators to them like:

morning = events.filter(event => event.category === 'MORNING');

without complaint.

Now, in this code fragment:

if (event.enabled || event.category === 'MORNING') {
 // something
}
else if (event.category !== 'MORNING') {
 // something else
}

I get the Operator '!==' cannot be applied to types '"MORNING"' and '"NIGHT"' compile error in the else if condition, but not in the if condition, which uses the same (but opposite) comparator.

Reducing the example further, the following compiles:

if (event.category !== 'MORNING') {
 // something
}
else if (event.category !== 'MORNING') {
 // something else
}

And this compiles:

if (event.category !== 'MORNING') {
 // something
}
else if (event.category === 'MORNING') {
 // something else
}

Whereas this throws an error (in the else if line):

if (event.category === 'MORNING') {
 // something
}
else if (event.category !== 'MORNING') {
 // something else
}

What fundamental property of the type-checker have I misunderstood?

(Note: The final examples are reduced from more complex situations, so I cannot use a simple else.)

JLRishe
  • 99,490
  • 19
  • 131
  • 169
msanford
  • 11,803
  • 11
  • 66
  • 93
  • Your `else if` condition makes no sense. The `if` condition is `event.enabled || event.category === 'MORNING'`, so by definition, if that condition was not true, then by definition, `event.category` is `'NIGHT'` and `event.category !== 'MORNING'` is true. Why is there a need to check it again? If that's not an accurate representation of the condition you actually need to check, please give us an example that _is_ an accurate representation of it. – JLRishe Nov 03 '17 at 17:55
  • 1
    @JLRishe Is right; I've tried every combination where your conditional isn't redundant and it works. Something to do with type narrowing. – Alex Guerra Nov 03 '17 at 17:56
  • This _is_ happening for more or less the same reason as the matter in the question you linked to. – JLRishe Nov 03 '17 at 18:13
  • @JLRishe Indeed, but as I explained, this is a _reduced (and fairly contrived) example_ from a more complex decision matrix with other things in it that I progressively removed to arrive at the final cases. In the real code, `category` is also more complex, but in building my [mcve], I noted identical behaviour with the provided example. Adding the real wall of code would not have added value to the question as I only wanted to know why those three final cases existed. The answer, which I am grateful for you having provided, is _control flow based type analysis._ :) – msanford Nov 06 '17 at 14:11

2 Answers2

4

You're getting an error because the compiler already knows that at the point of the else if condition, event.category is not 'MORNING' and will no longer allow you to compare event.category to it.

If this condition evaluates to false,

if (event.enabled || event.category === 'MORNING') {
 // something
}

then by definition, event.category is not 'MORNING' and is therefore 'NIGHT'. The compiler won't allow you to compare it to 'MORNING' again (and indeed there's no point in comparing it to 'MORNING' again), because it's already known to not be 'MORNING' at the point that the else if condition is being evaluated.

Incidentally, the following produces a compile error for essentially the same reason:

if (event.category !== 'MORNING') {
 // something
}
else if (event.category !== 'NIGHT') {
 // something else
}

As does this:

if (event.category === 'MORNING' || event.category !== 'MORNING') {
 // something
}

This is due to the way that TypeScript "narrows down" union types as it evaluates subsequent boolean expressions.

As suggested in the comments below, please see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#control-flow-based-type-analysis, and also https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#4.24

JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • 2
    I was in the middle of writing a more verbose version of this, but I'll give up now! You might want to mention that this is due to TypeScript's [control flow based type analysis](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#control-flow-based-type-analysis), which is how it goes about narrowing `event.category`. Cheers! – jcalz Nov 03 '17 at 18:04
1

I think this is right and has to do with type checking system in Typescript and TypeGuards. Consider:

Your fist example:

if (event.category !== 'MORNING') {
 // something
}
else if (event.category === 'MORNING') {
 // something else
}

After first if typescript cannot assume anything about type of category. Simple check if something is not equal to some kind of value does tell us nothing about type of variable. In next chains of else if it could be anything that matches your variable type.

Not working example:

if (event.category === 'MORNING') {
 // something
}
else if (event.category !== 'MORNING') {
 // something else
}

But if you check the other way around, you know for sure that category cannot be equal to 'MORNING', so Typescript changes your type. Its some kind of type narrowing.

https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#424-type-guards More info in spec C:

Kacper Wiszczuk
  • 1,809
  • 11
  • 14