From fcaa900012252bf2ed90ab31e1a7e4660c88bf28 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 23 Aug 2023 11:43:41 -0700 Subject: [PATCH] Fix check in getDiscriminantPropertyAccess (#55464) --- src/compiler/checker.ts | 2 +- .../reference/narrowingUnionToUnion.js | 28 ++++++++ .../reference/narrowingUnionToUnion.symbols | 56 ++++++++++++++++ .../reference/narrowingUnionToUnion.types | 67 +++++++++++++++++++ tests/cases/compiler/narrowingUnionToUnion.ts | 14 ++++ 5 files changed, 166 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ae0e1c5b57d..8e69c07877f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27460,7 +27460,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { - const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType; + const type = !(computedType.flags & TypeFlags.Union) && declaredType.flags & TypeFlags.Union ? declaredType : computedType; if (type.flags & TypeFlags.Union) { const access = getCandidateDiscriminantPropertyAccess(expr); if (access) { diff --git a/tests/baselines/reference/narrowingUnionToUnion.js b/tests/baselines/reference/narrowingUnionToUnion.js index e6115dddf6a..e623d346c0b 100644 --- a/tests/baselines/reference/narrowingUnionToUnion.js +++ b/tests/baselines/reference/narrowingUnionToUnion.js @@ -212,6 +212,20 @@ function f1x(obj: (string | number)[] | null) { assertRelationIsNullOrStringArray(obj); obj; // string[] | null } + +// Repro from #55425 + +type MyDiscriminatedUnion = { type: 'A', aProp: number } | { type: 'B', bProp: string }; + +declare function isMyDiscriminatedUnion(item: unknown): item is MyDiscriminatedUnion; + +declare const working: unknown; +declare const broken: Record | undefined; +declare const workingAgain: Record | undefined | unknown; + +isMyDiscriminatedUnion(working) && working.type === 'A' && working.aProp; +isMyDiscriminatedUnion(broken) && broken.type === 'A' && broken.aProp; +isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgain.aProp; //// [narrowingUnionToUnion.js] @@ -370,6 +384,9 @@ function f1x(obj) { assertRelationIsNullOrStringArray(obj); obj; // string[] | null } +isMyDiscriminatedUnion(working) && working.type === 'A' && working.aProp; +isMyDiscriminatedUnion(broken) && broken.type === 'A' && broken.aProp; +isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgain.aProp; //// [narrowingUnionToUnion.d.ts] @@ -423,3 +440,14 @@ declare function check2(x: unknown): x is ("hello" | 0); declare function test3(x: unknown): void; declare function assertRelationIsNullOrStringArray(v: (string | number)[] | null): asserts v is string[] | null; declare function f1x(obj: (string | number)[] | null): void; +type MyDiscriminatedUnion = { + type: 'A'; + aProp: number; +} | { + type: 'B'; + bProp: string; +}; +declare function isMyDiscriminatedUnion(item: unknown): item is MyDiscriminatedUnion; +declare const working: unknown; +declare const broken: Record | undefined; +declare const workingAgain: Record | undefined | unknown; diff --git a/tests/baselines/reference/narrowingUnionToUnion.symbols b/tests/baselines/reference/narrowingUnionToUnion.symbols index afe6a1c6433..e6ad800b8fe 100644 --- a/tests/baselines/reference/narrowingUnionToUnion.symbols +++ b/tests/baselines/reference/narrowingUnionToUnion.symbols @@ -482,3 +482,59 @@ function f1x(obj: (string | number)[] | null) { >obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 207, 13)) } +// Repro from #55425 + +type MyDiscriminatedUnion = { type: 'A', aProp: number } | { type: 'B', bProp: string }; +>MyDiscriminatedUnion : Symbol(MyDiscriminatedUnion, Decl(narrowingUnionToUnion.ts, 210, 1)) +>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 214, 29)) +>aProp : Symbol(aProp, Decl(narrowingUnionToUnion.ts, 214, 40)) +>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 214, 60)) +>bProp : Symbol(bProp, Decl(narrowingUnionToUnion.ts, 214, 71)) + +declare function isMyDiscriminatedUnion(item: unknown): item is MyDiscriminatedUnion; +>isMyDiscriminatedUnion : Symbol(isMyDiscriminatedUnion, Decl(narrowingUnionToUnion.ts, 214, 88)) +>item : Symbol(item, Decl(narrowingUnionToUnion.ts, 216, 40)) +>item : Symbol(item, Decl(narrowingUnionToUnion.ts, 216, 40)) +>MyDiscriminatedUnion : Symbol(MyDiscriminatedUnion, Decl(narrowingUnionToUnion.ts, 210, 1)) + +declare const working: unknown; +>working : Symbol(working, Decl(narrowingUnionToUnion.ts, 218, 13)) + +declare const broken: Record | undefined; +>broken : Symbol(broken, Decl(narrowingUnionToUnion.ts, 219, 13)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + +declare const workingAgain: Record | undefined | unknown; +>workingAgain : Symbol(workingAgain, Decl(narrowingUnionToUnion.ts, 220, 13)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + +isMyDiscriminatedUnion(working) && working.type === 'A' && working.aProp; +>isMyDiscriminatedUnion : Symbol(isMyDiscriminatedUnion, Decl(narrowingUnionToUnion.ts, 214, 88)) +>working : Symbol(working, Decl(narrowingUnionToUnion.ts, 218, 13)) +>working.type : Symbol(type, Decl(narrowingUnionToUnion.ts, 214, 29), Decl(narrowingUnionToUnion.ts, 214, 60)) +>working : Symbol(working, Decl(narrowingUnionToUnion.ts, 218, 13)) +>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 214, 29), Decl(narrowingUnionToUnion.ts, 214, 60)) +>working.aProp : Symbol(aProp, Decl(narrowingUnionToUnion.ts, 214, 40)) +>working : Symbol(working, Decl(narrowingUnionToUnion.ts, 218, 13)) +>aProp : Symbol(aProp, Decl(narrowingUnionToUnion.ts, 214, 40)) + +isMyDiscriminatedUnion(broken) && broken.type === 'A' && broken.aProp; +>isMyDiscriminatedUnion : Symbol(isMyDiscriminatedUnion, Decl(narrowingUnionToUnion.ts, 214, 88)) +>broken : Symbol(broken, Decl(narrowingUnionToUnion.ts, 219, 13)) +>broken.type : Symbol(type, Decl(narrowingUnionToUnion.ts, 214, 29), Decl(narrowingUnionToUnion.ts, 214, 60)) +>broken : Symbol(broken, Decl(narrowingUnionToUnion.ts, 219, 13)) +>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 214, 29), Decl(narrowingUnionToUnion.ts, 214, 60)) +>broken.aProp : Symbol(aProp, Decl(narrowingUnionToUnion.ts, 214, 40)) +>broken : Symbol(broken, Decl(narrowingUnionToUnion.ts, 219, 13)) +>aProp : Symbol(aProp, Decl(narrowingUnionToUnion.ts, 214, 40)) + +isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgain.aProp; +>isMyDiscriminatedUnion : Symbol(isMyDiscriminatedUnion, Decl(narrowingUnionToUnion.ts, 214, 88)) +>workingAgain : Symbol(workingAgain, Decl(narrowingUnionToUnion.ts, 220, 13)) +>workingAgain.type : Symbol(type, Decl(narrowingUnionToUnion.ts, 214, 29), Decl(narrowingUnionToUnion.ts, 214, 60)) +>workingAgain : Symbol(workingAgain, Decl(narrowingUnionToUnion.ts, 220, 13)) +>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 214, 29), Decl(narrowingUnionToUnion.ts, 214, 60)) +>workingAgain.aProp : Symbol(aProp, Decl(narrowingUnionToUnion.ts, 214, 40)) +>workingAgain : Symbol(workingAgain, Decl(narrowingUnionToUnion.ts, 220, 13)) +>aProp : Symbol(aProp, Decl(narrowingUnionToUnion.ts, 214, 40)) + diff --git a/tests/baselines/reference/narrowingUnionToUnion.types b/tests/baselines/reference/narrowingUnionToUnion.types index f3e59893afa..0371411ea74 100644 --- a/tests/baselines/reference/narrowingUnionToUnion.types +++ b/tests/baselines/reference/narrowingUnionToUnion.types @@ -507,3 +507,70 @@ function f1x(obj: (string | number)[] | null) { >obj : string[] | null } +// Repro from #55425 + +type MyDiscriminatedUnion = { type: 'A', aProp: number } | { type: 'B', bProp: string }; +>MyDiscriminatedUnion : { type: 'A'; aProp: number; } | { type: 'B'; bProp: string; } +>type : "A" +>aProp : number +>type : "B" +>bProp : string + +declare function isMyDiscriminatedUnion(item: unknown): item is MyDiscriminatedUnion; +>isMyDiscriminatedUnion : (item: unknown) => item is MyDiscriminatedUnion +>item : unknown + +declare const working: unknown; +>working : unknown + +declare const broken: Record | undefined; +>broken : Record | undefined + +declare const workingAgain: Record | undefined | unknown; +>workingAgain : unknown + +isMyDiscriminatedUnion(working) && working.type === 'A' && working.aProp; +>isMyDiscriminatedUnion(working) && working.type === 'A' && working.aProp : number | false +>isMyDiscriminatedUnion(working) && working.type === 'A' : boolean +>isMyDiscriminatedUnion(working) : boolean +>isMyDiscriminatedUnion : (item: unknown) => item is MyDiscriminatedUnion +>working : unknown +>working.type === 'A' : boolean +>working.type : "A" | "B" +>working : MyDiscriminatedUnion +>type : "A" | "B" +>'A' : "A" +>working.aProp : number +>working : { type: "A"; aProp: number; } +>aProp : number + +isMyDiscriminatedUnion(broken) && broken.type === 'A' && broken.aProp; +>isMyDiscriminatedUnion(broken) && broken.type === 'A' && broken.aProp : number | false +>isMyDiscriminatedUnion(broken) && broken.type === 'A' : boolean +>isMyDiscriminatedUnion(broken) : boolean +>isMyDiscriminatedUnion : (item: unknown) => item is MyDiscriminatedUnion +>broken : Record | undefined +>broken.type === 'A' : boolean +>broken.type : "A" | "B" +>broken : MyDiscriminatedUnion +>type : "A" | "B" +>'A' : "A" +>broken.aProp : number +>broken : { type: "A"; aProp: number; } +>aProp : number + +isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgain.aProp; +>isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgain.aProp : number | false +>isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' : boolean +>isMyDiscriminatedUnion(workingAgain) : boolean +>isMyDiscriminatedUnion : (item: unknown) => item is MyDiscriminatedUnion +>workingAgain : unknown +>workingAgain.type === 'A' : boolean +>workingAgain.type : "A" | "B" +>workingAgain : MyDiscriminatedUnion +>type : "A" | "B" +>'A' : "A" +>workingAgain.aProp : number +>workingAgain : { type: "A"; aProp: number; } +>aProp : number + diff --git a/tests/cases/compiler/narrowingUnionToUnion.ts b/tests/cases/compiler/narrowingUnionToUnion.ts index f28a10060d1..c4e2682e3c9 100644 --- a/tests/cases/compiler/narrowingUnionToUnion.ts +++ b/tests/cases/compiler/narrowingUnionToUnion.ts @@ -212,3 +212,17 @@ function f1x(obj: (string | number)[] | null) { assertRelationIsNullOrStringArray(obj); obj; // string[] | null } + +// Repro from #55425 + +type MyDiscriminatedUnion = { type: 'A', aProp: number } | { type: 'B', bProp: string }; + +declare function isMyDiscriminatedUnion(item: unknown): item is MyDiscriminatedUnion; + +declare const working: unknown; +declare const broken: Record | undefined; +declare const workingAgain: Record | undefined | unknown; + +isMyDiscriminatedUnion(working) && working.type === 'A' && working.aProp; +isMyDiscriminatedUnion(broken) && broken.type === 'A' && broken.aProp; +isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgain.aProp;