Typescript: Unexpected error: TS2365: Operator '!==' cannot be applied to types 'false' and 'true'

Created on 9 Dec 2016  路  20Comments  路  Source: microsoft/TypeScript

TypeScript Version: 2.1.4

This code in my VS Code extension has stopped compiling (without changes) recently.

if (context.globalState.get(stateKey, false) !== true)
    // blah

It returns the error:

src/user_config_prompts.ts(17,6):
    error TS2365: Operator '!==' cannot be applied to types 'false' and 'true'.

The full source is here and the build output here.

It seems like TypeScript has decided that the call to get can only return false, and therefore this comparison is bad; however I don't believe that's true - this method access stored state, so I can put some value in it and then reload my extension and it would receive the stored value.

This may be the same as #12772, but the response is confusing to me:

the function expression is not considered part of the flow, since it is not guaranteed to execute

This logic seems flawed - you can't assume something can never have a certain value because it might not be called; surely you have to assume it might have that value because you do not know if it will be called?

Working as Intended

Most helpful comment

Oh yeah, I see that, and cool that the compiler now catches that! :)

In sanitising the internal code, I removed the mutation:

function  getLengthAsStr(strs: string[]): string {
  if(strs.length !== 222){
     return "not 222"
  }
  //strs.length == 222
  strs.push("morestring")
 //strs.length == 223
  if(strs.length === 223){
    return "223"
  }
  return "blah"
}

which also fails to compile.

All 20 comments

Indeed, there seems to be a bug in 2.1.4. (maybe a side effect of the new type inference).

interface A {
    get<T>(key: string, defaultValue: T): T;
}
let a: A;
var x = a.get("key", false); // Ok, x is boolean
var y = a.get("key", false) == true; // Error TS2365    Operator '==' cannot be applied to types 'false' and 'true'

@ahejlsberg thoughts on how to properly type this?

you can define A as:

interface A {
    get(key: string, defaultValue: boolean): boolean;
    get<T>(key: string, defaultValue: T): T;
}

Probably worth providing overloads on all the primitives in that case

I'm slightly confused - do you mean that all generic methods in all codebase like this will need to have loads of overloads (one for each primitive type)?

Surely it's incorrect to assume that if a literal value is passed as a generic arg that the return type is that specific value rather than its type? I can't imagine this would ever be the expected thing?

Surely it's incorrect to assume that if a literal value is passed as a generic arg that the return type is that specific value rather than its type? I can't imagine this would ever be the expected thing?

I do not see why this is true. the error message in the OP is a valid one. a.get("key", false) is false and not any boolean and a.get("key", false) == true is guaranteed to fail all the time. so not sure why i see this is a bad thing.

a.get("key", false) is false and not any boolean

I don't understand why this is the case. When someone defines a generic method like T get<T>(default: T) surely they are saying that return type is the same type as default and not it is the same value as default. What would be the point of the method if it always just returned the default value?!

a.get("key", false) == true is guaranteed to fail all the time

The implementation of this method is in VS Code; I can persist a value and read it back. The default I pass is for if there is no stored value. If the stored value is true, then this method returns true. I'm confused why you think this would fail (what use would a method be if it only ever returns the exact value you passed it?).

is the same type as default

true is a type, and so is false. boolean is true | false, and true === false is a comparison between two distinct types. nothing about values here. this is the same for numeric literals as well. 1 === 2 is an error as well.

The implementation of this method is in VS Code; I can persist a value and read it back. The default I pass is for if there is no stored value. If the stored value is true, then this method returns true. I'm confused why you think this would fail (what use would a method be if it only ever returns the exact value you passed it?).

the method declaration says it takes a type T and returns the same type T. why would it take a true and return boolean? would it be OK if the method took number | string and returned number?

true is a type, and so is false. boolean is true | false, and true === false is a comparison between two distinct types. nothing about values here. this is the same for numeric literals as well. 1 === 2 is an error as well.

I can't currently test; but if I pass a 1 as defaultValue to the function above would the return type be "the 1 type" or a number? If the "the 1 type" then I think that's also crazy, if a number, then this seems inconsistent.

I'm sure there are many advantages to treating true and false as their own types, but this behaviour feels really wonky to me. It seems most natural to consider true and false to be values of type bool and having to cast them to bools all over the place seems unnecessary and unwanted :(

@mhegazy
This works fine in TS 2.0.
Providing overloads for all primitives, makes the generic version useless.
There is surely something wrong with the new type inference.

This is a widely used pattern.

From : https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack/Configuration/AppSettingsBase.cs

 public virtual T Get<T>(string name, T defaultValue)

https://github.com/ninject/Ninject/blob/master/src/Ninject/NinjectSettings.cs

public T Get<T>(string key, T defaultValue)

https://github.com/Microsoft/vscode/blob/5bf21a60b655546857f8162fa9b14a60d0e52280/src/vs/workbench/api/node/extHostExtensionService.ts

get<T>(key: string, defaultValue: T): T;

This works fine in TS 2.0.

This was an intentional change in TS 2.1, please see https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#literal-types-are-inferred-by-default-for-const-variables-and-readonly-properties. not treating literal types as types causes other errors as well. i would recommend adding overloads for the primitive values to ensure you are returning the base type instead of the literal type as suggested above.

Perhaps I'm missing something here too, but shouldn't this compile as it used to pre 2.1?

function  getLengthAsStr(strs: string[]): string {
  if(strs.length !== 111){
     return "111"
  }
  if(strs.length !== 222){
    return "222"
  }
  return "blah"
}

results in:

Operator '!==' cannot be applied to types '111' and '222'.

There are a few things we can do to make it compile, such as asserting the type:

function  getLengthAsStr2(strs: string[]): string {
  if(strs.length !== <number>111){
     return "111"
  }
  if(strs.length !== <number>222){
    return "222"
  }
  return "blah"
}

or similarly:

function  getLengthAsStr3(strs: string[]): string {
  if((<number>strs.length) !== 111){
     return "111"
  }
  if((<number>strs.length) !== 222){
    return "222"
  }
  return "blah"
}

or weirdly, replacing a return statement with something else:

function  getLengthAsStr4(strs: string[]): string {
  if(strs.length !== 111){
    console.log("222")
  }
  if(strs.length !== 222){
    console.log("222")
  }
  return "blah"
}
function  getLengthAsStr(strs: string[]): string {
  if(strs.length !== 111){
     return "111"
  }
  // `strs.length` here can not be any thing other than `111`
  // this check is guaranteed to be true
  if(strs.length !== 222){
    return "222"
  }
  return "blah"
}

Oh yeah, I see that, and cool that the compiler now catches that! :)

In sanitising the internal code, I removed the mutation:

function  getLengthAsStr(strs: string[]): string {
  if(strs.length !== 222){
     return "not 222"
  }
  //strs.length == 222
  strs.push("morestring")
 //strs.length == 223
  if(strs.length === 223){
    return "223"
  }
  return "blah"
}

which also fails to compile.

Similarly, here's a snippet of from a lexer implementation:

    if (this.tip == "\"") {
      // ...
      let isEscaped = false;
      // NB: `this._advance()` will cause `this.tip` to differ from above
      for (this._advance(); this.tip != ""; this._advance()) { // TS2365
        if (this.tip == "\\" && !isEscaped) {
          isEscaped = true;
          continue;
        }

        if (this.tip == "\"" && !isEscaped) break; // TS2365

        // ...
      }
      // ...
    }

At both marked locations, buggy inference in tsc leads to spurious errors
because it treats this.tip as bound to ", presumably as a result of the
check on the first line.

error TS2365: Operator '!=' cannot be applied to types '"\""' and '""'.
error TS2365: Operator '==' cannot be applied to types '"\""' and '"\\"'.

In reality, this.tip differs at those two locations due side effects induced
by the calls to this._advance.

@ghost
You could write it like this:

while (this._advance() && this.tip != "") {

  if (this.tip == "\\" && !isEscaped) {
    isEscaped = true;
    continue;
  }

  if (this.tip == "\"" && !isEscaped) break; // TS2365
}

where _advance returns a boolean and looks like

_advance(): this is this & { tip: string } { ... }

I also had this error, and after spending some time I figured out that this error is erroneously appearing when there's unreachable code. And sometimes even for reachable code, without an explanation.

Example:

class TrueFalse {
    one: boolean = true;
    two: boolean = false;

    public test () {
        if (this.one == false && this.two == false) {
            console.log("that's true.")
        }
        else if (this.one == false) {
            console.log("that's false.")
        }
        else if (this.one == false) { // unreacheable code, removing gets rid of the error
            console.log("that's false.")
        }

    }
}


let t = new TrueFalse();
t.test();
Error: index.ts|12 col 18 error| Operator '==' cannot be applied to types 'true' and 'false'.

Your second else if is actually a bug that the compiler is catching for you. It's dead code. The branch will never be taken because the previous else's if checks for the same condition

@aluanhaddad I know, but the error message provided by the compiler is misleading. I also want to stress that sometimes this is happening even when there's no unreachable code, but I couldn't reproduce/isolate the scenario yet.

Was this page helpful?
0 / 5 - 0 ratings