typescriptif-statementtypescript-2.5

Operator '!==' cannot be applied to types "A" | "B", but '===' can?


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.)


Solution

  • 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