7

Take a look at following code:

export class Smth {
  private flag: boolean;

  public update() {
    this.flag = true;

    this.inner();

    if (this.flag === false) { // Operator '===' cannot be applied to types 'true' and 'false'.
      console.log(123);
    }
  }

  private inner() {
    this.flag = false;
  }
}

I can't understand what's wrong with the line

if (this.flag === false)

Typescript says

Operator '===' cannot be applied to types 'true' and 'false'.

But actually there are boolean and false.

I'm using typescript 2.6.2 but online playground shows the same result with 2.7.


It's not a dublicate of Operator '==' cannot be applied to types x and y in Typescript 2 as that question is about comparing constants. But in my code it's a chagable class field and there is a function that changes the value. Moreover, it is called.

this.flag = true;

this.inner(); // exectues this.flag = false;

if (this.flag === false) { // ... types 'true' and 'false'. - WHY?
Qwertiy
  • 19,681
  • 15
  • 61
  • 128
  • Why wouldn't you write `if (!this.flag) { ... }`? – jonrsharpe Feb 05 '18 at 16:04
  • 1
    Possible duplicate of [Operator '==' cannot be applied to types x and y in Typescript 2](https://stackoverflow.com/questions/39243143/operator-cannot-be-applied-to-types-x-and-y-in-typescript-2) (TL;DR: the compiler can see you're trying to compare something that will always be true to something that will always be false) – Joe Clay Feb 05 '18 at 16:04
  • @jonrsharpe, because in the real code I have enum with 3 values. – Qwertiy Feb 05 '18 at 16:05
  • 2
    Then show *that*. Note the error message you posted leads to many existing resources e.g. https://github.com/Microsoft/TypeScript/issues/11178 – jonrsharpe Feb 05 '18 at 16:06
  • @JoeClay, no, that'n not always `true`. I'm calling `this.inner()` and it changes state of `this.flag` to `false`. Flag is NOT `true` anymore. – Qwertiy Feb 05 '18 at 16:08
  • Hm, that's actually a good point. This seems like a deficiency in TypeScript's type narrowing system, then. Will retract my duplicate vote. – Joe Clay Feb 05 '18 at 16:11

2 Answers2

5

TypeScript is kinda smart, in that it has a static analysis on your method, and it sees that you never assign anything other than false, at lease in this closure or context - which brings the type definition to assume your variable's type is false and not boolean. It does not look for changes in inner called methods.

Think of the definition as if it were declared globally like this:

export type boolean = true | false

while false is only false, without the true there.

There are several solutions:

  1. Assign the type from the get-go in the class declaration, like so:

    class MyClass {
      private flag: boolean = true
      ...
    }
    
  2. Just don't test for direct equality, use the boolean on its own:

    if (!this.flag) // instead of this.flag === false
    
casraf
  • 21,085
  • 9
  • 56
  • 91
  • Take a look at `private inner()` and call to `this.inner()` - it changes the `flag`, so your asumption doesn't seem to be right. – Qwertiy Feb 05 '18 at 16:10
  • I noticed and noted "at least in this closure or context", the static analysis doesn't bleed to other method on each it checks – casraf Feb 05 '18 at 16:10
  • I find it hard to believe that typescript compiler doesn't understand the concept of calls to other methods possibly changing class variables as it does in this example. This would seem to be a massive oversight to me... If flag was scoped to the function it would make more sense but its not... I think you should probably cite the relevant part of the typescript spec to support this claim. – Chris Feb 05 '18 at 16:12
  • It's not an oversight, it is by design. It also makes some sort of sense as side-effects are pretty hard to check for in static analysis https://github.com/Microsoft/TypeScript/issues/11178 – casraf Feb 05 '18 at 16:14
4

The problem you have is part of a broader discussion about the drawbacks of flow analysis. You can read the general problem here and a very similar problem to yours here. But the gist of it is:

The primary question is: When a function is invoked, what should we assume its side effects are? One option is to be pessimistic and reset all narrowings, assuming that any function might mutate any object it could possibly get its hands on. Another option is to be optimistic and assume the function doesn't modify any state. Both of these seem to be bad. This problem spans both locals (which might be subject to some "closed over or not" analysis) and object fields.

The simple way to work around this is to cast the constant to the general type. You should do this where necessary, one could argue the error has some value, as it warns you about possibly unreachable code and can be easily disabled where erroneous:

export class Smth {
  private flag: boolean;

  public update() {
    this.flag = true as boolean;

    this.inner();

    if (this.flag === false) { 
      console.log(123);
    }
  }

  private inner() {
    this.flag = false;
  }
}
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357