From b4d382b9b12460adf2da4cc0d1429cf19f8dc8be Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 2 Dec 2022 10:55:03 -0800 Subject: [PATCH] Cherry-pick changes for narrowing to tagged literal types. --- src/compiler/checker.ts | 5 +- .../reference/unknownControlFlow.errors.txt | 22 ++++++++ .../baselines/reference/unknownControlFlow.js | 43 +++++++++++++++ .../reference/unknownControlFlow.symbols | 48 +++++++++++++++++ .../reference/unknownControlFlow.types | 52 +++++++++++++++++++ .../types/unknown/unknownControlFlow.ts | 22 ++++++++ 6 files changed, 191 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c4a19cb8e8c2d..73797735e2ffe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21630,7 +21630,10 @@ namespace ts { } function isUnitLikeType(type: Type): boolean { - return isUnitType(getBaseConstraintOrType(type)); + // Intersections that reduce to 'never' (e.g. 'T & null' where 'T extends {}') are not unit types. + const t = getBaseConstraintOrType(type); + // Scan intersections such that tagged literal types are considered unit types. + return t.flags & TypeFlags.Intersection ? some((t as IntersectionType).types, isUnitType) : isUnitType(t); } function extractUnitType(type: Type) { diff --git a/tests/baselines/reference/unknownControlFlow.errors.txt b/tests/baselines/reference/unknownControlFlow.errors.txt index 410237e3133a7..6056f775acf0a 100644 --- a/tests/baselines/reference/unknownControlFlow.errors.txt +++ b/tests/baselines/reference/unknownControlFlow.errors.txt @@ -444,4 +444,26 @@ tests/cases/conformance/types/unknown/unknownControlFlow.ts(293,5): error TS2345 function x(x: T_AB & undefined, y: any) { let r2: never = y as T_AB & undefined; } + + // Repro from #51538 + + type Left = 'left'; + type Right = 'right' & { right: 'right' }; + type Either = Left | Right; + + function assertNever(v: never): never { + throw new Error('never'); + } + + function fx20(value: Either) { + if (value === 'left') { + const foo: 'left' = value; + } + else if (value === 'right') { + const bar: 'right' = value; + } + else { + assertNever(value); + } + } \ No newline at end of file diff --git a/tests/baselines/reference/unknownControlFlow.js b/tests/baselines/reference/unknownControlFlow.js index a4979179cfb7a..68233905e13b9 100644 --- a/tests/baselines/reference/unknownControlFlow.js +++ b/tests/baselines/reference/unknownControlFlow.js @@ -427,6 +427,28 @@ type AB = "A" | "B"; function x(x: T_AB & undefined, y: any) { let r2: never = y as T_AB & undefined; } + +// Repro from #51538 + +type Left = 'left'; +type Right = 'right' & { right: 'right' }; +type Either = Left | Right; + +function assertNever(v: never): never { + throw new Error('never'); +} + +function fx20(value: Either) { + if (value === 'left') { + const foo: 'left' = value; + } + else if (value === 'right') { + const bar: 'right' = value; + } + else { + assertNever(value); + } +} //// [unknownControlFlow.js] @@ -772,6 +794,20 @@ function doSomething2(value) { function x(x, y) { var r2 = y; } +function assertNever(v) { + throw new Error('never'); +} +function fx20(value) { + if (value === 'left') { + var foo_1 = value; + } + else if (value === 'right') { + var bar = value; + } + else { + assertNever(value); + } +} //// [unknownControlFlow.d.ts] @@ -844,3 +880,10 @@ type R = T extends keyof TypeB ? [TypeA[T], TypeB[T]] : n type R2 = T extends keyof TypeA ? T extends keyof TypeB ? [TypeA[T], TypeB[T]] : never : never; type AB = "A" | "B"; declare function x(x: T_AB & undefined, y: any): void; +type Left = 'left'; +type Right = 'right' & { + right: 'right'; +}; +type Either = Left | Right; +declare function assertNever(v: never): never; +declare function fx20(value: Either): void; diff --git a/tests/baselines/reference/unknownControlFlow.symbols b/tests/baselines/reference/unknownControlFlow.symbols index d69e69fe08ee6..762505aba1907 100644 --- a/tests/baselines/reference/unknownControlFlow.symbols +++ b/tests/baselines/reference/unknownControlFlow.symbols @@ -995,3 +995,51 @@ function x(x: T_AB & undefined, y: any) { >T_AB : Symbol(T_AB, Decl(unknownControlFlow.ts, 425, 11)) } +// Repro from #51538 + +type Left = 'left'; +>Left : Symbol(Left, Decl(unknownControlFlow.ts, 427, 1)) + +type Right = 'right' & { right: 'right' }; +>Right : Symbol(Right, Decl(unknownControlFlow.ts, 431, 19)) +>right : Symbol(right, Decl(unknownControlFlow.ts, 432, 24)) + +type Either = Left | Right; +>Either : Symbol(Either, Decl(unknownControlFlow.ts, 432, 42)) +>Left : Symbol(Left, Decl(unknownControlFlow.ts, 427, 1)) +>Right : Symbol(Right, Decl(unknownControlFlow.ts, 431, 19)) + +function assertNever(v: never): never { +>assertNever : Symbol(assertNever, Decl(unknownControlFlow.ts, 433, 27)) +>v : Symbol(v, Decl(unknownControlFlow.ts, 435, 21)) + + throw new Error('never'); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +} + +function fx20(value: Either) { +>fx20 : Symbol(fx20, Decl(unknownControlFlow.ts, 437, 1)) +>value : Symbol(value, Decl(unknownControlFlow.ts, 439, 14)) +>Either : Symbol(Either, Decl(unknownControlFlow.ts, 432, 42)) + + if (value === 'left') { +>value : Symbol(value, Decl(unknownControlFlow.ts, 439, 14)) + + const foo: 'left' = value; +>foo : Symbol(foo, Decl(unknownControlFlow.ts, 441, 13)) +>value : Symbol(value, Decl(unknownControlFlow.ts, 439, 14)) + } + else if (value === 'right') { +>value : Symbol(value, Decl(unknownControlFlow.ts, 439, 14)) + + const bar: 'right' = value; +>bar : Symbol(bar, Decl(unknownControlFlow.ts, 444, 13)) +>value : Symbol(value, Decl(unknownControlFlow.ts, 439, 14)) + } + else { + assertNever(value); +>assertNever : Symbol(assertNever, Decl(unknownControlFlow.ts, 433, 27)) +>value : Symbol(value, Decl(unknownControlFlow.ts, 439, 14)) + } +} + diff --git a/tests/baselines/reference/unknownControlFlow.types b/tests/baselines/reference/unknownControlFlow.types index 2cf2c101c9dad..24cbd3b162850 100644 --- a/tests/baselines/reference/unknownControlFlow.types +++ b/tests/baselines/reference/unknownControlFlow.types @@ -1076,3 +1076,55 @@ function x(x: T_AB & undefined, y: any) { >y : any } +// Repro from #51538 + +type Left = 'left'; +>Left : "left" + +type Right = 'right' & { right: 'right' }; +>Right : "right" & { right: 'right'; } +>right : "right" + +type Either = Left | Right; +>Either : Right | "left" + +function assertNever(v: never): never { +>assertNever : (v: never) => never +>v : never + + throw new Error('never'); +>new Error('never') : Error +>Error : ErrorConstructor +>'never' : "never" +} + +function fx20(value: Either) { +>fx20 : (value: Either) => void +>value : Either + + if (value === 'left') { +>value === 'left' : boolean +>value : Either +>'left' : "left" + + const foo: 'left' = value; +>foo : "left" +>value : "left" + } + else if (value === 'right') { +>value === 'right' : boolean +>value : Right +>'right' : "right" + + const bar: 'right' = value; +>bar : "right" +>value : Right + } + else { + assertNever(value); +>assertNever(value) : never +>assertNever : (v: never) => never +>value : never + } +} + diff --git a/tests/cases/conformance/types/unknown/unknownControlFlow.ts b/tests/cases/conformance/types/unknown/unknownControlFlow.ts index 082ebad7bc1f5..1f82ab4944da5 100644 --- a/tests/cases/conformance/types/unknown/unknownControlFlow.ts +++ b/tests/cases/conformance/types/unknown/unknownControlFlow.ts @@ -429,3 +429,25 @@ type AB = "A" | "B"; function x(x: T_AB & undefined, y: any) { let r2: never = y as T_AB & undefined; } + +// Repro from #51538 + +type Left = 'left'; +type Right = 'right' & { right: 'right' }; +type Either = Left | Right; + +function assertNever(v: never): never { + throw new Error('never'); +} + +function fx20(value: Either) { + if (value === 'left') { + const foo: 'left' = value; + } + else if (value === 'right') { + const bar: 'right' = value; + } + else { + assertNever(value); + } +}