Fixed narrowing in case clauses that follow other clauses that return (#56358)

This commit is contained in:
Mateusz Burzyński 2023-11-13 23:41:56 +01:00 committed by GitHub
parent 70becf9af9
commit 9f15002959
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 357 additions and 0 deletions

View File

@ -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++;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}