From 9f150029590b80355ffb2c35b92b79ae97e702d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 13 Nov 2023 23:41:56 +0100 Subject: [PATCH] Fixed narrowing in case clauses that follow other clauses that return (#56358) --- src/compiler/binder.ts | 4 + ...aseClauseAfterCaseClauseWithReturn.symbols | 126 +++++++++++++ ...nCaseClauseAfterCaseClauseWithReturn.types | 170 ++++++++++++++++++ ...ngInCaseClauseAfterCaseClauseWithReturn.ts | 57 ++++++ 4 files changed, 357 insertions(+) create mode 100644 tests/baselines/reference/narrowingInCaseClauseAfterCaseClauseWithReturn.symbols create mode 100644 tests/baselines/reference/narrowingInCaseClauseAfterCaseClauseWithReturn.types create mode 100644 tests/cases/compiler/narrowingInCaseClauseAfterCaseClauseWithReturn.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index dfb37b2faf0..b26af72dcc4 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1689,9 +1689,13 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { const clauses = node.clauses; const isNarrowingSwitch = node.parent.expression.kind === SyntaxKind.TrueKeyword || isNarrowingExpression(node.parent.expression); let fallthroughFlow = unreachableFlow; + for (let i = 0; i < clauses.length; i++) { const clauseStart = i; while (!clauses[i].statements.length && i + 1 < clauses.length) { + if (fallthroughFlow === unreachableFlow) { + currentFlow = preSwitchCaseFlow!; + } bind(clauses[i]); i++; } diff --git a/tests/baselines/reference/narrowingInCaseClauseAfterCaseClauseWithReturn.symbols b/tests/baselines/reference/narrowingInCaseClauseAfterCaseClauseWithReturn.symbols new file mode 100644 index 00000000000..8825eefda18 --- /dev/null +++ b/tests/baselines/reference/narrowingInCaseClauseAfterCaseClauseWithReturn.symbols @@ -0,0 +1,126 @@ +//// [tests/cases/compiler/narrowingInCaseClauseAfterCaseClauseWithReturn.ts] //// + +=== narrowingInCaseClauseAfterCaseClauseWithReturn.ts === +// https://github.com/microsoft/TypeScript/issues/56352 + +function test1(arg: string | undefined) { +>test1 : Symbol(test1, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 0, 0)) +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15)) + + if (!arg) throw new Error(); +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + switch (true) { + case arg.toUpperCase() === "A": +>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + return "A"; + + case arg.toUpperCase() === "B": +>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + case arg.toUpperCase() === "C": +>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + case arg.toUpperCase() === "D": +>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + return "B, C or D"; + } + + return "Not A, B, C or D"; +} + +function test2(arg: string | undefined) { +>test2 : Symbol(test2, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 16, 1)) +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15)) + + if (!arg) throw new Error(); +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + switch (true) { + case arg.toUpperCase() === "A": +>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + case arg.toUpperCase() === "B": +>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + case arg.toUpperCase() === "C": +>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + return "A, B or C"; + case arg.toUpperCase() === "D": +>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + return "D"; + } + + return "Not A, B, C or D"; +} + +function test3( +>test3 : Symbol(test3, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 31, 1)) + + foo: +>foo : Symbol(foo, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 33, 15)) + + | { kind: "a"; prop: string } +>kind : Symbol(kind, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 35, 7)) +>prop : Symbol(prop, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 35, 18)) + + | { kind: "b"; prop: number } +>kind : Symbol(kind, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 36, 7)) +>prop : Symbol(prop, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 36, 18)) + + | { kind: "c"; prop: boolean }, +>kind : Symbol(kind, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 7)) +>prop : Symbol(prop, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 18)) + + bar?: { +>bar : Symbol(bar, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 35)) + + type: "b"; +>type : Symbol(type, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 38, 9)) + + }, +) { + if (!bar) { +>bar : Symbol(bar, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 35)) + + return; + } + + switch (foo.kind) { +>foo.kind : Symbol(kind, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 35, 7), Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 36, 7), Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 7)) +>foo : Symbol(foo, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 33, 15)) +>kind : Symbol(kind, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 35, 7), Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 36, 7), Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 7)) + + case "a": + return; + case bar.type: +>bar.type : Symbol(type, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 38, 9)) +>bar : Symbol(bar, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 35)) +>type : Symbol(type, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 38, 9)) + + case "c": + return; + } +} diff --git a/tests/baselines/reference/narrowingInCaseClauseAfterCaseClauseWithReturn.types b/tests/baselines/reference/narrowingInCaseClauseAfterCaseClauseWithReturn.types new file mode 100644 index 00000000000..56380c70586 --- /dev/null +++ b/tests/baselines/reference/narrowingInCaseClauseAfterCaseClauseWithReturn.types @@ -0,0 +1,170 @@ +//// [tests/cases/compiler/narrowingInCaseClauseAfterCaseClauseWithReturn.ts] //// + +=== narrowingInCaseClauseAfterCaseClauseWithReturn.ts === +// https://github.com/microsoft/TypeScript/issues/56352 + +function test1(arg: string | undefined) { +>test1 : (arg: string | undefined) => "A" | "B, C or D" | "Not A, B, C or D" +>arg : string | undefined + + if (!arg) throw new Error(); +>!arg : boolean +>arg : string | undefined +>new Error() : Error +>Error : ErrorConstructor + + switch (true) { +>true : true + + case arg.toUpperCase() === "A": +>arg.toUpperCase() === "A" : boolean +>arg.toUpperCase() : string +>arg.toUpperCase : () => string +>arg : string +>toUpperCase : () => string +>"A" : "A" + + return "A"; +>"A" : "A" + + case arg.toUpperCase() === "B": +>arg.toUpperCase() === "B" : boolean +>arg.toUpperCase() : string +>arg.toUpperCase : () => string +>arg : string +>toUpperCase : () => string +>"B" : "B" + + case arg.toUpperCase() === "C": +>arg.toUpperCase() === "C" : boolean +>arg.toUpperCase() : string +>arg.toUpperCase : () => string +>arg : string +>toUpperCase : () => string +>"C" : "C" + + case arg.toUpperCase() === "D": +>arg.toUpperCase() === "D" : boolean +>arg.toUpperCase() : string +>arg.toUpperCase : () => string +>arg : string +>toUpperCase : () => string +>"D" : "D" + + return "B, C or D"; +>"B, C or D" : "B, C or D" + } + + return "Not A, B, C or D"; +>"Not A, B, C or D" : "Not A, B, C or D" +} + +function test2(arg: string | undefined) { +>test2 : (arg: string | undefined) => "Not A, B, C or D" | "D" | "A, B or C" +>arg : string | undefined + + if (!arg) throw new Error(); +>!arg : boolean +>arg : string | undefined +>new Error() : Error +>Error : ErrorConstructor + + switch (true) { +>true : true + + case arg.toUpperCase() === "A": +>arg.toUpperCase() === "A" : boolean +>arg.toUpperCase() : string +>arg.toUpperCase : () => string +>arg : string +>toUpperCase : () => string +>"A" : "A" + + case arg.toUpperCase() === "B": +>arg.toUpperCase() === "B" : boolean +>arg.toUpperCase() : string +>arg.toUpperCase : () => string +>arg : string +>toUpperCase : () => string +>"B" : "B" + + case arg.toUpperCase() === "C": +>arg.toUpperCase() === "C" : boolean +>arg.toUpperCase() : string +>arg.toUpperCase : () => string +>arg : string +>toUpperCase : () => string +>"C" : "C" + + return "A, B or C"; +>"A, B or C" : "A, B or C" + + case arg.toUpperCase() === "D": +>arg.toUpperCase() === "D" : boolean +>arg.toUpperCase() : string +>arg.toUpperCase : () => string +>arg : string +>toUpperCase : () => string +>"D" : "D" + + return "D"; +>"D" : "D" + } + + return "Not A, B, C or D"; +>"Not A, B, C or D" : "Not A, B, C or D" +} + +function test3( +>test3 : (foo: { kind: "a"; prop: string;} | { kind: "b"; prop: number;} | { kind: "c"; prop: boolean;}, bar?: { type: "b";}) => void + + foo: +>foo : { kind: "a"; prop: string; } | { kind: "b"; prop: number; } | { kind: "c"; prop: boolean; } + + | { kind: "a"; prop: string } +>kind : "a" +>prop : string + + | { kind: "b"; prop: number } +>kind : "b" +>prop : number + + | { kind: "c"; prop: boolean }, +>kind : "c" +>prop : boolean + + bar?: { +>bar : { type: "b"; } | undefined + + type: "b"; +>type : "b" + + }, +) { + if (!bar) { +>!bar : boolean +>bar : { type: "b"; } | undefined + + return; + } + + switch (foo.kind) { +>foo.kind : "a" | "b" | "c" +>foo : { kind: "a"; prop: string; } | { kind: "b"; prop: number; } | { kind: "c"; prop: boolean; } +>kind : "a" | "b" | "c" + + case "a": +>"a" : "a" + + return; + case bar.type: +>bar.type : "b" +>bar : { type: "b"; } +>type : "b" + + case "c": +>"c" : "c" + + return; + } +} diff --git a/tests/cases/compiler/narrowingInCaseClauseAfterCaseClauseWithReturn.ts b/tests/cases/compiler/narrowingInCaseClauseAfterCaseClauseWithReturn.ts new file mode 100644 index 00000000000..b01a9dd9e2a --- /dev/null +++ b/tests/cases/compiler/narrowingInCaseClauseAfterCaseClauseWithReturn.ts @@ -0,0 +1,57 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/56352 + +function test1(arg: string | undefined) { + if (!arg) throw new Error(); + + switch (true) { + case arg.toUpperCase() === "A": + return "A"; + + case arg.toUpperCase() === "B": + case arg.toUpperCase() === "C": + case arg.toUpperCase() === "D": + return "B, C or D"; + } + + return "Not A, B, C or D"; +} + +function test2(arg: string | undefined) { + if (!arg) throw new Error(); + + switch (true) { + case arg.toUpperCase() === "A": + case arg.toUpperCase() === "B": + case arg.toUpperCase() === "C": + return "A, B or C"; + case arg.toUpperCase() === "D": + return "D"; + } + + return "Not A, B, C or D"; +} + +function test3( + foo: + | { kind: "a"; prop: string } + | { kind: "b"; prop: number } + | { kind: "c"; prop: boolean }, + bar?: { + type: "b"; + }, +) { + if (!bar) { + return; + } + + switch (foo.kind) { + case "a": + return; + case bar.type: + case "c": + return; + } +} \ No newline at end of file