Skip to content

Commit 9f15002

Browse files
authored
Fixed narrowing in case clauses that follow other clauses that return (microsoft#56358)
1 parent 70becf9 commit 9f15002

4 files changed

+357
-0
lines changed

src/compiler/binder.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1689,9 +1689,13 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
16891689
const clauses = node.clauses;
16901690
const isNarrowingSwitch = node.parent.expression.kind === SyntaxKind.TrueKeyword || isNarrowingExpression(node.parent.expression);
16911691
let fallthroughFlow = unreachableFlow;
1692+
16921693
for (let i = 0; i < clauses.length; i++) {
16931694
const clauseStart = i;
16941695
while (!clauses[i].statements.length && i + 1 < clauses.length) {
1696+
if (fallthroughFlow === unreachableFlow) {
1697+
currentFlow = preSwitchCaseFlow!;
1698+
}
16951699
bind(clauses[i]);
16961700
i++;
16971701
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//// [tests/cases/compiler/narrowingInCaseClauseAfterCaseClauseWithReturn.ts] ////
2+
3+
=== narrowingInCaseClauseAfterCaseClauseWithReturn.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/56352
5+
6+
function test1(arg: string | undefined) {
7+
>test1 : Symbol(test1, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 0, 0))
8+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15))
9+
10+
if (!arg) throw new Error();
11+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15))
12+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
13+
14+
switch (true) {
15+
case arg.toUpperCase() === "A":
16+
>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
17+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15))
18+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
19+
20+
return "A";
21+
22+
case arg.toUpperCase() === "B":
23+
>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
24+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15))
25+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
26+
27+
case arg.toUpperCase() === "C":
28+
>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
29+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15))
30+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
31+
32+
case arg.toUpperCase() === "D":
33+
>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
34+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 2, 15))
35+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
36+
37+
return "B, C or D";
38+
}
39+
40+
return "Not A, B, C or D";
41+
}
42+
43+
function test2(arg: string | undefined) {
44+
>test2 : Symbol(test2, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 16, 1))
45+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15))
46+
47+
if (!arg) throw new Error();
48+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15))
49+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
50+
51+
switch (true) {
52+
case arg.toUpperCase() === "A":
53+
>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
54+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15))
55+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
56+
57+
case arg.toUpperCase() === "B":
58+
>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
59+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15))
60+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
61+
62+
case arg.toUpperCase() === "C":
63+
>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
64+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15))
65+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
66+
67+
return "A, B or C";
68+
case arg.toUpperCase() === "D":
69+
>arg.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
70+
>arg : Symbol(arg, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 18, 15))
71+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
72+
73+
return "D";
74+
}
75+
76+
return "Not A, B, C or D";
77+
}
78+
79+
function test3(
80+
>test3 : Symbol(test3, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 31, 1))
81+
82+
foo:
83+
>foo : Symbol(foo, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 33, 15))
84+
85+
| { kind: "a"; prop: string }
86+
>kind : Symbol(kind, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 35, 7))
87+
>prop : Symbol(prop, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 35, 18))
88+
89+
| { kind: "b"; prop: number }
90+
>kind : Symbol(kind, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 36, 7))
91+
>prop : Symbol(prop, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 36, 18))
92+
93+
| { kind: "c"; prop: boolean },
94+
>kind : Symbol(kind, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 7))
95+
>prop : Symbol(prop, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 18))
96+
97+
bar?: {
98+
>bar : Symbol(bar, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 35))
99+
100+
type: "b";
101+
>type : Symbol(type, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 38, 9))
102+
103+
},
104+
) {
105+
if (!bar) {
106+
>bar : Symbol(bar, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 35))
107+
108+
return;
109+
}
110+
111+
switch (foo.kind) {
112+
>foo.kind : Symbol(kind, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 35, 7), Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 36, 7), Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 7))
113+
>foo : Symbol(foo, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 33, 15))
114+
>kind : Symbol(kind, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 35, 7), Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 36, 7), Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 7))
115+
116+
case "a":
117+
return;
118+
case bar.type:
119+
>bar.type : Symbol(type, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 38, 9))
120+
>bar : Symbol(bar, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 37, 35))
121+
>type : Symbol(type, Decl(narrowingInCaseClauseAfterCaseClauseWithReturn.ts, 38, 9))
122+
123+
case "c":
124+
return;
125+
}
126+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//// [tests/cases/compiler/narrowingInCaseClauseAfterCaseClauseWithReturn.ts] ////
2+
3+
=== narrowingInCaseClauseAfterCaseClauseWithReturn.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/56352
5+
6+
function test1(arg: string | undefined) {
7+
>test1 : (arg: string | undefined) => "A" | "B, C or D" | "Not A, B, C or D"
8+
>arg : string | undefined
9+
10+
if (!arg) throw new Error();
11+
>!arg : boolean
12+
>arg : string | undefined
13+
>new Error() : Error
14+
>Error : ErrorConstructor
15+
16+
switch (true) {
17+
>true : true
18+
19+
case arg.toUpperCase() === "A":
20+
>arg.toUpperCase() === "A" : boolean
21+
>arg.toUpperCase() : string
22+
>arg.toUpperCase : () => string
23+
>arg : string
24+
>toUpperCase : () => string
25+
>"A" : "A"
26+
27+
return "A";
28+
>"A" : "A"
29+
30+
case arg.toUpperCase() === "B":
31+
>arg.toUpperCase() === "B" : boolean
32+
>arg.toUpperCase() : string
33+
>arg.toUpperCase : () => string
34+
>arg : string
35+
>toUpperCase : () => string
36+
>"B" : "B"
37+
38+
case arg.toUpperCase() === "C":
39+
>arg.toUpperCase() === "C" : boolean
40+
>arg.toUpperCase() : string
41+
>arg.toUpperCase : () => string
42+
>arg : string
43+
>toUpperCase : () => string
44+
>"C" : "C"
45+
46+
case arg.toUpperCase() === "D":
47+
>arg.toUpperCase() === "D" : boolean
48+
>arg.toUpperCase() : string
49+
>arg.toUpperCase : () => string
50+
>arg : string
51+
>toUpperCase : () => string
52+
>"D" : "D"
53+
54+
return "B, C or D";
55+
>"B, C or D" : "B, C or D"
56+
}
57+
58+
return "Not A, B, C or D";
59+
>"Not A, B, C or D" : "Not A, B, C or D"
60+
}
61+
62+
function test2(arg: string | undefined) {
63+
>test2 : (arg: string | undefined) => "Not A, B, C or D" | "D" | "A, B or C"
64+
>arg : string | undefined
65+
66+
if (!arg) throw new Error();
67+
>!arg : boolean
68+
>arg : string | undefined
69+
>new Error() : Error
70+
>Error : ErrorConstructor
71+
72+
switch (true) {
73+
>true : true
74+
75+
case arg.toUpperCase() === "A":
76+
>arg.toUpperCase() === "A" : boolean
77+
>arg.toUpperCase() : string
78+
>arg.toUpperCase : () => string
79+
>arg : string
80+
>toUpperCase : () => string
81+
>"A" : "A"
82+
83+
case arg.toUpperCase() === "B":
84+
>arg.toUpperCase() === "B" : boolean
85+
>arg.toUpperCase() : string
86+
>arg.toUpperCase : () => string
87+
>arg : string
88+
>toUpperCase : () => string
89+
>"B" : "B"
90+
91+
case arg.toUpperCase() === "C":
92+
>arg.toUpperCase() === "C" : boolean
93+
>arg.toUpperCase() : string
94+
>arg.toUpperCase : () => string
95+
>arg : string
96+
>toUpperCase : () => string
97+
>"C" : "C"
98+
99+
return "A, B or C";
100+
>"A, B or C" : "A, B or C"
101+
102+
case arg.toUpperCase() === "D":
103+
>arg.toUpperCase() === "D" : boolean
104+
>arg.toUpperCase() : string
105+
>arg.toUpperCase : () => string
106+
>arg : string
107+
>toUpperCase : () => string
108+
>"D" : "D"
109+
110+
return "D";
111+
>"D" : "D"
112+
}
113+
114+
return "Not A, B, C or D";
115+
>"Not A, B, C or D" : "Not A, B, C or D"
116+
}
117+
118+
function test3(
119+
>test3 : (foo: { kind: "a"; prop: string;} | { kind: "b"; prop: number;} | { kind: "c"; prop: boolean;}, bar?: { type: "b";}) => void
120+
121+
foo:
122+
>foo : { kind: "a"; prop: string; } | { kind: "b"; prop: number; } | { kind: "c"; prop: boolean; }
123+
124+
| { kind: "a"; prop: string }
125+
>kind : "a"
126+
>prop : string
127+
128+
| { kind: "b"; prop: number }
129+
>kind : "b"
130+
>prop : number
131+
132+
| { kind: "c"; prop: boolean },
133+
>kind : "c"
134+
>prop : boolean
135+
136+
bar?: {
137+
>bar : { type: "b"; } | undefined
138+
139+
type: "b";
140+
>type : "b"
141+
142+
},
143+
) {
144+
if (!bar) {
145+
>!bar : boolean
146+
>bar : { type: "b"; } | undefined
147+
148+
return;
149+
}
150+
151+
switch (foo.kind) {
152+
>foo.kind : "a" | "b" | "c"
153+
>foo : { kind: "a"; prop: string; } | { kind: "b"; prop: number; } | { kind: "c"; prop: boolean; }
154+
>kind : "a" | "b" | "c"
155+
156+
case "a":
157+
>"a" : "a"
158+
159+
return;
160+
case bar.type:
161+
>bar.type : "b"
162+
>bar : { type: "b"; }
163+
>type : "b"
164+
165+
case "c":
166+
>"c" : "c"
167+
168+
return;
169+
}
170+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// https://github.com/microsoft/TypeScript/issues/56352
5+
6+
function test1(arg: string | undefined) {
7+
if (!arg) throw new Error();
8+
9+
switch (true) {
10+
case arg.toUpperCase() === "A":
11+
return "A";
12+
13+
case arg.toUpperCase() === "B":
14+
case arg.toUpperCase() === "C":
15+
case arg.toUpperCase() === "D":
16+
return "B, C or D";
17+
}
18+
19+
return "Not A, B, C or D";
20+
}
21+
22+
function test2(arg: string | undefined) {
23+
if (!arg) throw new Error();
24+
25+
switch (true) {
26+
case arg.toUpperCase() === "A":
27+
case arg.toUpperCase() === "B":
28+
case arg.toUpperCase() === "C":
29+
return "A, B or C";
30+
case arg.toUpperCase() === "D":
31+
return "D";
32+
}
33+
34+
return "Not A, B, C or D";
35+
}
36+
37+
function test3(
38+
foo:
39+
| { kind: "a"; prop: string }
40+
| { kind: "b"; prop: number }
41+
| { kind: "c"; prop: boolean },
42+
bar?: {
43+
type: "b";
44+
},
45+
) {
46+
if (!bar) {
47+
return;
48+
}
49+
50+
switch (foo.kind) {
51+
case "a":
52+
return;
53+
case bar.type:
54+
case "c":
55+
return;
56+
}
57+
}

0 commit comments

Comments
 (0)