TypeScript 3.7.2
Playground link
Compiler Options:
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"useDefineForClassFields": false,
"alwaysStrict": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"downlevelIteration": false,
"noEmitHelpers": false,
"noLib": false,
"noStrictGenericChecks": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"esModuleInterop": true,
"preserveConstEnums": false,
"removeComments": false,
"skipLibCheck": false,
"checkJs": false,
"allowJs": false,
"experimentalDecorators": false,
"emitDecoratorMetadata": false,
"target": "ES2017",
"module": "ESNext"
}
}
Input:
function foo(items: Number[]) {
const firstElement = items[0];
(firstElement || null).toString();
}
function bar(items: number[]) {
const firstElement = items[0];
(firstElement || null).toString();
}
foo([]);
bar([]);
Output:
"use strict";
function foo(items) {
const firstElement = items[0];
(firstElement || null).toString();
}
function bar(items) {
const firstElement = items[0];
(firstElement || null).toString();
}
foo([]);
bar([]);
Actual behavior:
Compilation error in the second case only.
Expected behavior:
Compilation errors in both cases.
A smaller repro
declare const a: Number;
declare const b: number;
// error
(a || null).toString()
// no error
(b || null).toString()
The control flow analysis assume that a variable with type number will always be truthy,
but its actually not true for 0.
Funny thing is,
declare let b: number;
b = 5;
// error!
(b || null).toString()
Maybe https://github.com/microsoft/TypeScript/issues/9998 will have some info
This is the correct behaviour.
a || null will always be of type Number (which I doubt someone will want to use) and b || null is of type number | null, so .toString() is not safe.
As for @Bnaya's repro, it has a missing semi-colon that makes it look like the error is inverted, add the semi-colon and it errors correctly on the last statement.
Objects of type Number are always truthy
> !!(new Number(0))
true
A frequent source of confusion: Number is not an alias for number.
Thank you for the answers!
I think I shouldn't have used Number in the first example because it was confusing. The first version of the reproduction example was:
class Foo { }
function foo(items: Foo[]) {
const firstElement = items[0];
(firstElement || null).toString();
}
function bar(items: number[]) {
const firstElement = items[0];
(firstElement || null).toString();
}
foo([]);
bar([]);
Most helpful comment
This is the correct behaviour.
a || nullwill always be of typeNumber(which I doubt someone will want to use) andb || nullis of typenumber | null, so.toString()is not safe.As for @Bnaya's repro, it has a missing semi-colon that makes it look like the error is inverted, add the semi-colon and it errors correctly on the last statement.