Typescript: Array indexing and logical OR with null behave differently depending on the array type

Created on 8 Nov 2019  路  4Comments  路  Source: microsoft/TypeScript

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.

Working as Intended

Most helpful comment

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.

All 4 comments

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([]);
Was this page helpful?
0 / 5 - 0 ratings