Typescript: Type wrongly assumed to be undefined | number in this case

Created on 26 May 2019  路  3Comments  路  Source: microsoft/TypeScript

TypeScript Version: 3.4.0-dev.201xxxxx

let a: number | undefined;
let b: number | undefined;

if (a || b) {
    const c = a || b;
}

馃悰 c is wrongly assumed to be number | undefined when it should be number.

Design Limitation

Most helpful comment

Yeah, the problem is that a || b, from the compiler's point of view, doesn't prove anything individually about a or b, so it can't actually narrow them. At the time the assignment to c happens the types are still as follows:

a :: number | undefined
b :: number | undefined

So as far as the compiler is concerned, both could be undefined at the time the assignment to c happens. We know that's not the case since a || b requires at least one of them to be non-falsy, but the type narrowing mechanism isn't quite that nuanced. It doesn't track dependencies between variables--the same reason this doesn't work:

let x: number | undefined
let isNumber = typeof x === 'number';
if (isNumber) {
    // x :: number | undefined
    x += 1;  // error
}

All 3 comments

Probably a design limitation because it requires narrowing the type of an expression (instead of a variable, or property), but just wanted to point out the obvious; you can restructure your code to work around it.

let a: number | undefined;
let b: number | undefined;
const tmp = a || b;

if (tmp) {
    const c = tmp;
}

Although, it would be cool to have narrowing for side-effect free expressions. Not sure if there's much benefit to it.

I never use logical operators with non-boolean types, though.

Yeah, the problem is that a || b, from the compiler's point of view, doesn't prove anything individually about a or b, so it can't actually narrow them. At the time the assignment to c happens the types are still as follows:

a :: number | undefined
b :: number | undefined

So as far as the compiler is concerned, both could be undefined at the time the assignment to c happens. We know that's not the case since a || b requires at least one of them to be non-falsy, but the type narrowing mechanism isn't quite that nuanced. It doesn't track dependencies between variables--the same reason this doesn't work:

let x: number | undefined
let isNumber = typeof x === 'number';
if (isNumber) {
    // x :: number | undefined
    x += 1;  // error
}

I believe we just encountered this limitation and were scratching our heads:

Playground link

interface TestInterface {
  value?: string;
}

function foo({ value }: TestInterface) {
  const isValidValue = !!value && /bar/.test(value);
  const thing = isValidValue ? value.split('x') : [];
                            // ^ Object is possibly 'undefined'
}

This is a simplified example of what we were trying within our code. "Clearly" if value is falsy, there's no way it would be undefined when checking isValidValue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bgrieder picture bgrieder  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

dlaberge picture dlaberge  路  3Comments

Zlatkovsky picture Zlatkovsky  路  3Comments

blendsdk picture blendsdk  路  3Comments