microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
99.5k stars 12.33k forks source link

Type inference worked in 4.2.3 but fails in 4.3.2 #44412

Open errorx666 opened 3 years ago

errorx666 commented 3 years ago

Bug Report

πŸ”Ž Search Terms

type inference

πŸ•— Version & Regression Information

4.3.2

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

(strict null checks off)

interface PromptChoiceOptions<T extends string> {
    label: string;
    button: string;
    default?: T;
    choices: readonly ( T | [ string, T ] )[];
}

function promptChoice<T extends string>( options?: Partial<PromptChoiceOptions<T>> ) {
    return options?.choices?.[ 0 ] ?? null; // simplified
}

const choice = promptChoice( { label: 'example', button: 'select', choices: [
    [ 'null', null ], // problem seems to be related to this line
    [ 'One', 'one' ],
    [ 'Two', 'two' ],
    [ 'Three', 'three' ]
] } );

πŸ™ Actual behavior

Type [string, "two"] is not assignable to type [string, "one"]

πŸ™‚ Expected behavior

choice is inferred to be 'one'|'two'|'three'


In creating a minimal repro for this, I discovered that commenting out the null choice makes the code behave properly. Also, changing the type of choices to readonly ( T | [ string, T|null] )[]; with strict null checking makes the problem always occur.

whzx5byb commented 3 years ago

A minimal repo

// @strictNullChecks: false
declare function func<T extends string>(input: [T][]): T;

var a = func([[null], ['x'], ['y']]);
// ^ a: "x", and error TS2322 (v4.3.2)
// ^ a: "x" | "y" (v4.2.3)
RyanCavanaugh commented 3 years ago

The strictNullCheck on definition you'd want is

interface PromptChoiceOptions<T extends string> {
    label: string;
    button: string;
    default?: T;
    choices: readonly ( T | [ string, T ] | [string, null] )[];
}

function promptChoice<T extends string>( options?: Partial<PromptChoiceOptions<T>> ) {
    return options?.choices?.[ 0 ] ?? null; // simplified
}

const choice = promptChoice( { label: 'example', button: 'select', choices: [
    [ 'null', null ], // problem seems to be related to this line
    [ 'One', 'one' ],
    [ 'Two', 'two' ],
    [ 'Three', 'three' ]
] } );

The bug seems to be that with SNC off we should be effectively discarding the null type before seeing if a primitive-containing union can be formed from the remaining candidates