Patrick Desjardins Blog
Patrick Desjardins picture from a conference

TypeScript 2.0 cast required for simple boolean?

Posted on: 2016-09-27

TypeScript is awesome to help you develop web application with significant amount of client-side script. It had on top of JavaScript some strong types and once compiled bring back everything to Javascript. Many huge project, like Angular 2 is using TypeScript. At Microsoft, Visual Studio Team Services is also using TypeScript. Few days ago, the TypeScript team released version 2.0 which bring some existing code to fail. Here is a snippet that illustrate the problem. The following code doesn't compile, right at the IF line.

var bool: boolean = true;
if (bool === false) { 
  console.log("not true"); 
} else { 
  console.log("not false"); 
} 

However, the following code works:

var bool = true;

if (bool === false) { 
  console.log("not true");
} 
else { 
  console.log("not false"); 
} 

Or this one works:

var bool: boolean = true;

if ((bool as boolean) === false) { 
  console.log("not true"); 
}
else { 
  console.log("not false"); 
} 

Before I start explaining, let's be clear : I think the way it was working was good and I am not convinced that this new behavior will help reducing the amount of errors.

So, how come var bool: boolean = true; doesn't work when var bool = true; works? The second one infer the type from the value. And, if you play with it in the official TypeScript Playground. You can see that the inferred type is boolean.

In fact, the one without type is a bug and in version 2.1 will be fixed to also fail. But why? The culprit is under the boolean type which doesn't exist in JavaScript. TypeScript creates an union of true | false. However, in the example, we only set it to true. The compiler figure out that the real type of this variable is true, not true or false. TypeScript 2.0 supports literal types true and false. TypeScript only narrows union types. What means narrowing? Narrowing is limiting the value space that a variable can host based on some checks like typeof, instanceof, equality, etc.. Narrow type can be boolean as we just saw but also enum.

You can still trick TypeScript compiler by using function that alter the value. But it still flacky. For example, the following code set the type to boolean, set the value to true and alter it to false within a function. Written this way, TypeScript figures out that the value can be true or false, hence keep the legacy boolean validation as correct.

var bool: boolean = true; 
let f = (() => { bool = false; })();

if (bool === false) { 
  console.log("bool is false"); 
} 
else { 
  console.log("bool is true"); 
} 

On the other hand, if you try to write the same code differently, this one get lost and won't let you compile:

var bool: boolean = true; 
let f = (() => { bool = false; }); f();

if (bool === false) { 
  console.log("bool is false"); 
} else { 
  console.log("bool is true"); 
} 

The last piece of code is harder for TypeScript to understand if the function f will be called. The first code is for sure to be executed. Does that mean that if we were to compare with true that it would fail? The answer is yes. The bool type is true.

Overall, this change is weird. Why would you take the time to define a type to boolean (true | false) and let TypeScript overwrites your decision to decide either it should be true only or false only. It's also bringing some issues if you are using TypeScript's enum. For example,

enum MyEnum { Choice1, Choice2, Choice3 } 

This translate into Choice1 being 0, Choice2 1 and Choice3 = 2. The problem is that 0 is false in JavaScript. The first check is to be sure we do not pass null or undefined. Since MyEnum.Choice1 is 0, which is false, it will never goes into the if of the following code.

function isChoice1(yourChoice: MyEnum) { 
  if (yourChoice) {
     return yourChoice === MyEnum.Choice1; 
  } 
  return false; 
} 

This code won't compile because the triple equal is not about being yourChoice being a MyEnum but yourChoice being of type Choice2 | Choice3.

Overall, this change will probably give you some headache first. You can always quickly fix it by casting to the type you desire. In the long run, you'll get more used about this control flow TypeScript analysis and will develop new habits.