Skip to content

Commit 70b7de1

Browse files
authored
Narrow by clause expressions in switches with true condition (#53681)
1 parent 686cb1b commit 70b7de1

File tree

5 files changed

+366
-1
lines changed

5 files changed

+366
-1
lines changed

src/compiler/binder.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1680,7 +1680,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
16801680

16811681
function bindCaseBlock(node: CaseBlock): void {
16821682
const clauses = node.clauses;
1683-
const isNarrowingSwitch = isNarrowingExpression(node.parent.expression);
1683+
const isNarrowingSwitch = node.parent.expression.kind === SyntaxKind.TrueKeyword || isNarrowingExpression(node.parent.expression);
16841684
let fallthroughFlow = unreachableFlow;
16851685
for (let i = 0; i < clauses.length; i++) {
16861686
const clauseStart = i;

src/compiler/checker.ts

+7
Original file line numberDiff line numberDiff line change
@@ -27342,6 +27342,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2734227342
else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
2734327343
type = narrowTypeBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
2734427344
}
27345+
else if (expr.kind === SyntaxKind.TrueKeyword) {
27346+
const clause = flow.switchStatement.caseBlock.clauses.find((_, index) => index === flow.clauseStart);
27347+
const clauseExpression = clause && clause.kind === SyntaxKind.CaseClause ? clause.expression : undefined;
27348+
if (clauseExpression) {
27349+
type = narrowType(type, clauseExpression, /*assumeTrue*/ true);
27350+
}
27351+
}
2734527352
else {
2734627353
if (strictNullChecks) {
2734727354
if (optionalChainContainsReference(expr, reference)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//// [tests/cases/compiler/narrowByClauseExpressionInSwitchTrue.ts] ////
2+
3+
=== narrowByClauseExpressionInSwitchTrue.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/37178
5+
6+
type A = { type: "A" };
7+
>A : Symbol(A, Decl(narrowByClauseExpressionInSwitchTrue.ts, 0, 0))
8+
>type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 10))
9+
10+
type B = { type: "B" };
11+
>B : Symbol(B, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 23))
12+
>type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 10))
13+
14+
type AorB = A | B;
15+
>AorB : Symbol(AorB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 23))
16+
>A : Symbol(A, Decl(narrowByClauseExpressionInSwitchTrue.ts, 0, 0))
17+
>B : Symbol(B, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 23))
18+
19+
const isA = (x: AorB): x is A => x.type === "A";
20+
>isA : Symbol(isA, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 5))
21+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 13))
22+
>AorB : Symbol(AorB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 23))
23+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 13))
24+
>A : Symbol(A, Decl(narrowByClauseExpressionInSwitchTrue.ts, 0, 0))
25+
>x.type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 10), Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 10))
26+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 13))
27+
>type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 10), Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 10))
28+
29+
const isB = (x: AorB): x is B => x.type === "B";
30+
>isB : Symbol(isB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 5))
31+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 13))
32+
>AorB : Symbol(AorB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 23))
33+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 13))
34+
>B : Symbol(B, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 23))
35+
>x.type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 10), Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 10))
36+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 13))
37+
>type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 10), Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 10))
38+
39+
function test1(x: AorB) {
40+
>test1 : Symbol(test1, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 48))
41+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 9, 15))
42+
>AorB : Symbol(AorB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 23))
43+
44+
switch (true) {
45+
case isA(x):
46+
>isA : Symbol(isA, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 5))
47+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 9, 15))
48+
49+
x;
50+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 9, 15))
51+
52+
break;
53+
case isB(x):
54+
>isB : Symbol(isB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 5))
55+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 9, 15))
56+
57+
x;
58+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 9, 15))
59+
60+
break;
61+
}
62+
}
63+
64+
function test2(x: AorB) {
65+
>test2 : Symbol(test2, Decl(narrowByClauseExpressionInSwitchTrue.ts, 18, 1))
66+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 20, 15))
67+
>AorB : Symbol(AorB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 23))
68+
69+
switch (true) {
70+
case isA(x):
71+
>isA : Symbol(isA, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 5))
72+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 20, 15))
73+
74+
x;
75+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 20, 15))
76+
77+
// fallthrough
78+
case isB(x):
79+
>isB : Symbol(isB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 5))
80+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 20, 15))
81+
82+
x;
83+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 20, 15))
84+
85+
break;
86+
}
87+
}
88+
89+
let x: string | undefined;
90+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 31, 3))
91+
92+
switch (true) {
93+
case typeof x !== "undefined":
94+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 31, 3))
95+
96+
x.trim();
97+
>x.trim : Symbol(String.trim, Decl(lib.es5.d.ts, --, --))
98+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 31, 3))
99+
>trim : Symbol(String.trim, Decl(lib.es5.d.ts, --, --))
100+
}
101+
102+
type SomeType = { type: "SomeType" };
103+
>SomeType : Symbol(SomeType, Decl(narrowByClauseExpressionInSwitchTrue.ts, 36, 1))
104+
>type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 38, 17))
105+
106+
declare function isSomeType(x: unknown): x is SomeType;
107+
>isSomeType : Symbol(isSomeType, Decl(narrowByClauseExpressionInSwitchTrue.ts, 38, 37))
108+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 39, 28))
109+
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 39, 28))
110+
>SomeType : Symbol(SomeType, Decl(narrowByClauseExpressionInSwitchTrue.ts, 36, 1))
111+
112+
function processInput(input: string | RegExp | SomeType) {
113+
>processInput : Symbol(processInput, Decl(narrowByClauseExpressionInSwitchTrue.ts, 39, 55))
114+
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))
115+
>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
116+
>SomeType : Symbol(SomeType, Decl(narrowByClauseExpressionInSwitchTrue.ts, 36, 1))
117+
118+
switch (true) {
119+
case typeof input === "string":
120+
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))
121+
122+
input;
123+
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))
124+
125+
break;
126+
case input instanceof RegExp:
127+
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))
128+
>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
129+
130+
input;
131+
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))
132+
133+
break;
134+
case isSomeType(input):
135+
>isSomeType : Symbol(isSomeType, Decl(narrowByClauseExpressionInSwitchTrue.ts, 38, 37))
136+
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))
137+
138+
input;
139+
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))
140+
141+
break;
142+
}
143+
}
144+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//// [tests/cases/compiler/narrowByClauseExpressionInSwitchTrue.ts] ////
2+
3+
=== narrowByClauseExpressionInSwitchTrue.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/37178
5+
6+
type A = { type: "A" };
7+
>A : { type: "A"; }
8+
>type : "A"
9+
10+
type B = { type: "B" };
11+
>B : { type: "B"; }
12+
>type : "B"
13+
14+
type AorB = A | B;
15+
>AorB : A | B
16+
17+
const isA = (x: AorB): x is A => x.type === "A";
18+
>isA : (x: AorB) => x is A
19+
>(x: AorB): x is A => x.type === "A" : (x: AorB) => x is A
20+
>x : AorB
21+
>x.type === "A" : boolean
22+
>x.type : "A" | "B"
23+
>x : AorB
24+
>type : "A" | "B"
25+
>"A" : "A"
26+
27+
const isB = (x: AorB): x is B => x.type === "B";
28+
>isB : (x: AorB) => x is B
29+
>(x: AorB): x is B => x.type === "B" : (x: AorB) => x is B
30+
>x : AorB
31+
>x.type === "B" : boolean
32+
>x.type : "A" | "B"
33+
>x : AorB
34+
>type : "A" | "B"
35+
>"B" : "B"
36+
37+
function test1(x: AorB) {
38+
>test1 : (x: AorB) => void
39+
>x : AorB
40+
41+
switch (true) {
42+
>true : true
43+
44+
case isA(x):
45+
>isA(x) : boolean
46+
>isA : (x: AorB) => x is A
47+
>x : AorB
48+
49+
x;
50+
>x : A
51+
52+
break;
53+
case isB(x):
54+
>isB(x) : boolean
55+
>isB : (x: AorB) => x is B
56+
>x : AorB
57+
58+
x;
59+
>x : B
60+
61+
break;
62+
}
63+
}
64+
65+
function test2(x: AorB) {
66+
>test2 : (x: AorB) => void
67+
>x : AorB
68+
69+
switch (true) {
70+
>true : true
71+
72+
case isA(x):
73+
>isA(x) : boolean
74+
>isA : (x: AorB) => x is A
75+
>x : AorB
76+
77+
x;
78+
>x : A
79+
80+
// fallthrough
81+
case isB(x):
82+
>isB(x) : boolean
83+
>isB : (x: AorB) => x is B
84+
>x : AorB
85+
86+
x;
87+
>x : AorB
88+
89+
break;
90+
}
91+
}
92+
93+
let x: string | undefined;
94+
>x : string | undefined
95+
96+
switch (true) {
97+
>true : true
98+
99+
case typeof x !== "undefined":
100+
>typeof x !== "undefined" : boolean
101+
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
102+
>x : string | undefined
103+
>"undefined" : "undefined"
104+
105+
x.trim();
106+
>x.trim() : string
107+
>x.trim : () => string
108+
>x : string
109+
>trim : () => string
110+
}
111+
112+
type SomeType = { type: "SomeType" };
113+
>SomeType : { type: "SomeType"; }
114+
>type : "SomeType"
115+
116+
declare function isSomeType(x: unknown): x is SomeType;
117+
>isSomeType : (x: unknown) => x is SomeType
118+
>x : unknown
119+
120+
function processInput(input: string | RegExp | SomeType) {
121+
>processInput : (input: string | RegExp | SomeType) => void
122+
>input : string | RegExp | SomeType
123+
124+
switch (true) {
125+
>true : true
126+
127+
case typeof input === "string":
128+
>typeof input === "string" : boolean
129+
>typeof input : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
130+
>input : string | RegExp | SomeType
131+
>"string" : "string"
132+
133+
input;
134+
>input : string
135+
136+
break;
137+
case input instanceof RegExp:
138+
>input instanceof RegExp : boolean
139+
>input : string | RegExp | SomeType
140+
>RegExp : RegExpConstructor
141+
142+
input;
143+
>input : RegExp
144+
145+
break;
146+
case isSomeType(input):
147+
>isSomeType(input) : boolean
148+
>isSomeType : (x: unknown) => x is SomeType
149+
>input : string | RegExp | SomeType
150+
151+
input;
152+
>input : SomeType
153+
154+
break;
155+
}
156+
}
157+
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/37178
5+
6+
type A = { type: "A" };
7+
type B = { type: "B" };
8+
type AorB = A | B;
9+
10+
const isA = (x: AorB): x is A => x.type === "A";
11+
const isB = (x: AorB): x is B => x.type === "B";
12+
13+
function test1(x: AorB) {
14+
switch (true) {
15+
case isA(x):
16+
x;
17+
break;
18+
case isB(x):
19+
x;
20+
break;
21+
}
22+
}
23+
24+
function test2(x: AorB) {
25+
switch (true) {
26+
case isA(x):
27+
x;
28+
// fallthrough
29+
case isB(x):
30+
x;
31+
break;
32+
}
33+
}
34+
35+
let x: string | undefined;
36+
37+
switch (true) {
38+
case typeof x !== "undefined":
39+
x.trim();
40+
}
41+
42+
type SomeType = { type: "SomeType" };
43+
declare function isSomeType(x: unknown): x is SomeType;
44+
45+
function processInput(input: string | RegExp | SomeType) {
46+
switch (true) {
47+
case typeof input === "string":
48+
input;
49+
break;
50+
case input instanceof RegExp:
51+
input;
52+
break;
53+
case isSomeType(input):
54+
input;
55+
break;
56+
}
57+
}

0 commit comments

Comments
 (0)