From 42a215c8fb22f5564c4a4e5063d302c1ca52bb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 2 Apr 2024 20:49:21 +0200 Subject: [PATCH] Fixed type predicate inference for discriminated union parameters (#57952) --- src/compiler/checker.ts | 7 +-- .../reference/inferTypePredicates.errors.txt | 10 ++++ .../reference/inferTypePredicates.js | 25 +++++++++ .../reference/inferTypePredicates.symbols | 30 +++++++++++ .../reference/inferTypePredicates.types | 54 +++++++++++++++++++ tests/cases/compiler/inferTypePredicates.ts | 10 ++++ 6 files changed, 133 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e3e075a6721..d94e86969c2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -37690,8 +37690,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { { flags: FlowFlags.Start }; const trueCondition: FlowCondition = { flags: FlowFlags.TrueCondition, - node: expr, antecedent, + node: expr, }; const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition); @@ -37700,10 +37700,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // "x is T" means that x is T if and only if it returns true. If it returns false then x is not T. // This means that if the function is called with an argument of type trueType, there can't be anything left in the `else` branch. It must reduce to `never`. const falseCondition: FlowCondition = { - ...trueCondition, flags: FlowFlags.FalseCondition, + antecedent, + node: expr, }; - const falseSubtype = getFlowTypeOfReference(param.name, trueType, trueType, func, falseCondition); + const falseSubtype = getFlowTypeOfReference(param.name, initType, trueType, func, falseCondition); return falseSubtype.flags & TypeFlags.Never ? trueType : undefined; } diff --git a/tests/baselines/reference/inferTypePredicates.errors.txt b/tests/baselines/reference/inferTypePredicates.errors.txt index 89ee1266137..e61c1cb72be 100644 --- a/tests/baselines/reference/inferTypePredicates.errors.txt +++ b/tests/baselines/reference/inferTypePredicates.errors.txt @@ -315,4 +315,14 @@ inferTypePredicates.ts(205,7): error TS2741: Property 'z' is missing in type 'C1 function inferWithRest(x: string | null, ...f: ["a", "b"]) { return typeof x === 'string'; } + + // https://github.com/microsoft/TypeScript/issues/57947 + declare const foobar: + | { type: "foo"; foo: number } + | { type: "bar"; bar: string }; + + const foobarPred = (fb: typeof foobar) => fb.type === "foo"; + if (foobarPred(foobar)) { + foobar.foo; + } \ No newline at end of file diff --git a/tests/baselines/reference/inferTypePredicates.js b/tests/baselines/reference/inferTypePredicates.js index 61eed027ddb..b5802c0e50a 100644 --- a/tests/baselines/reference/inferTypePredicates.js +++ b/tests/baselines/reference/inferTypePredicates.js @@ -269,6 +269,16 @@ const noInferenceFromImpossibleRest = (...f: []) => typeof f === "undefined"; function inferWithRest(x: string | null, ...f: ["a", "b"]) { return typeof x === 'string'; } + +// https://github.com/microsoft/TypeScript/issues/57947 +declare const foobar: + | { type: "foo"; foo: number } + | { type: "bar"; bar: string }; + +const foobarPred = (fb: typeof foobar) => fb.type === "foo"; +if (foobarPred(foobar)) { + foobar.foo; +} //// [inferTypePredicates.js] @@ -524,6 +534,10 @@ function inferWithRest(x) { } return typeof x === 'string'; } +var foobarPred = function (fb) { return fb.type === "foo"; }; +if (foobarPred(foobar)) { + foobar.foo; +} //// [inferTypePredicates.d.ts] @@ -605,3 +619,14 @@ declare function narrowFromAny(x: any): x is number; declare const noInferenceFromRest: (f_0: "a" | "b") => boolean; declare const noInferenceFromImpossibleRest: () => boolean; declare function inferWithRest(x: string | null, ...f: ["a", "b"]): x is string; +declare const foobar: { + type: "foo"; + foo: number; +} | { + type: "bar"; + bar: string; +}; +declare const foobarPred: (fb: typeof foobar) => fb is { + type: "foo"; + foo: number; +}; diff --git a/tests/baselines/reference/inferTypePredicates.symbols b/tests/baselines/reference/inferTypePredicates.symbols index bf795485b82..8fd879787c2 100644 --- a/tests/baselines/reference/inferTypePredicates.symbols +++ b/tests/baselines/reference/inferTypePredicates.symbols @@ -747,3 +747,33 @@ function inferWithRest(x: string | null, ...f: ["a", "b"]) { >x : Symbol(x, Decl(inferTypePredicates.ts, 265, 23)) } +// https://github.com/microsoft/TypeScript/issues/57947 +declare const foobar: +>foobar : Symbol(foobar, Decl(inferTypePredicates.ts, 270, 13)) + + | { type: "foo"; foo: number } +>type : Symbol(type, Decl(inferTypePredicates.ts, 271, 5)) +>foo : Symbol(foo, Decl(inferTypePredicates.ts, 271, 18)) + + | { type: "bar"; bar: string }; +>type : Symbol(type, Decl(inferTypePredicates.ts, 272, 5)) +>bar : Symbol(bar, Decl(inferTypePredicates.ts, 272, 18)) + +const foobarPred = (fb: typeof foobar) => fb.type === "foo"; +>foobarPred : Symbol(foobarPred, Decl(inferTypePredicates.ts, 274, 5)) +>fb : Symbol(fb, Decl(inferTypePredicates.ts, 274, 20)) +>foobar : Symbol(foobar, Decl(inferTypePredicates.ts, 270, 13)) +>fb.type : Symbol(type, Decl(inferTypePredicates.ts, 271, 5), Decl(inferTypePredicates.ts, 272, 5)) +>fb : Symbol(fb, Decl(inferTypePredicates.ts, 274, 20)) +>type : Symbol(type, Decl(inferTypePredicates.ts, 271, 5), Decl(inferTypePredicates.ts, 272, 5)) + +if (foobarPred(foobar)) { +>foobarPred : Symbol(foobarPred, Decl(inferTypePredicates.ts, 274, 5)) +>foobar : Symbol(foobar, Decl(inferTypePredicates.ts, 270, 13)) + + foobar.foo; +>foobar.foo : Symbol(foo, Decl(inferTypePredicates.ts, 271, 18)) +>foobar : Symbol(foobar, Decl(inferTypePredicates.ts, 270, 13)) +>foo : Symbol(foo, Decl(inferTypePredicates.ts, 271, 18)) +} + diff --git a/tests/baselines/reference/inferTypePredicates.types b/tests/baselines/reference/inferTypePredicates.types index e27436d8a02..8ef83f50177 100644 --- a/tests/baselines/reference/inferTypePredicates.types +++ b/tests/baselines/reference/inferTypePredicates.types @@ -1595,3 +1595,57 @@ function inferWithRest(x: string | null, ...f: ["a", "b"]) { > : ^^^^^^^^ } +// https://github.com/microsoft/TypeScript/issues/57947 +declare const foobar: +>foobar : { type: "foo"; foo: number; } | { type: "bar"; bar: string; } +> : ^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^ ^^^ + + | { type: "foo"; foo: number } +>type : "foo" +> : ^^^^^ +>foo : number +> : ^^^^^^ + + | { type: "bar"; bar: string }; +>type : "bar" +> : ^^^^^ +>bar : string +> : ^^^^^^ + +const foobarPred = (fb: typeof foobar) => fb.type === "foo"; +>foobarPred : (fb: typeof foobar) => fb is { type: "foo"; foo: number; } +> : ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>(fb: typeof foobar) => fb.type === "foo" : (fb: typeof foobar) => fb is { type: "foo"; foo: number; } +> : ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>fb : { type: "foo"; foo: number; } | { type: "bar"; bar: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>foobar : { type: "foo"; foo: number; } | { type: "bar"; bar: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>fb.type === "foo" : boolean +> : ^^^^^^^ +>fb.type : "bar" | "foo" +> : ^^^^^^^^^^^^^ +>fb : { type: "foo"; foo: number; } | { type: "bar"; bar: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>type : "bar" | "foo" +> : ^^^^^^^^^^^^^ +>"foo" : "foo" +> : ^^^^^ + +if (foobarPred(foobar)) { +>foobarPred(foobar) : boolean +> : ^^^^^^^ +>foobarPred : (fb: { type: "foo"; foo: number; } | { type: "bar"; bar: string; }) => fb is { type: "foo"; foo: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>foobar : { type: "foo"; foo: number; } | { type: "bar"; bar: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + foobar.foo; +>foobar.foo : number +> : ^^^^^^ +>foobar : { type: "foo"; foo: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : number +> : ^^^^^^ +} + diff --git a/tests/cases/compiler/inferTypePredicates.ts b/tests/cases/compiler/inferTypePredicates.ts index 49edf16ca9b..9b996ee8c84 100644 --- a/tests/cases/compiler/inferTypePredicates.ts +++ b/tests/cases/compiler/inferTypePredicates.ts @@ -269,3 +269,13 @@ const noInferenceFromImpossibleRest = (...f: []) => typeof f === "undefined"; function inferWithRest(x: string | null, ...f: ["a", "b"]) { return typeof x === 'string'; } + +// https://github.com/microsoft/TypeScript/issues/57947 +declare const foobar: + | { type: "foo"; foo: number } + | { type: "bar"; bar: string }; + +const foobarPred = (fb: typeof foobar) => fb.type === "foo"; +if (foobarPred(foobar)) { + foobar.foo; +}