Skip to content

Commit 3a5230a

Browse files
authored
Defer switch exhaustiveness checks (#35000)
* Defer switch exhaustiveness checks until they're actually needed * Add regression test * Accept new baselines
1 parent 165b4bc commit 3a5230a

File tree

6 files changed

+126
-3
lines changed

6 files changed

+126
-3
lines changed

src/compiler/checker.ts

+25-3
Original file line numberDiff line numberDiff line change
@@ -19294,9 +19294,6 @@ namespace ts {
1929419294
else if (containsMatchingReferenceDiscriminant(reference, expr)) {
1929519295
type = declaredType;
1929619296
}
19297-
else if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement(flow.switchStatement)) {
19298-
return unreachableNeverType;
19299-
}
1930019297
}
1930119298
return createFlowType(type, isIncomplete(flowType));
1930219299
}
@@ -19305,13 +19302,19 @@ namespace ts {
1930519302
const antecedentTypes: Type[] = [];
1930619303
let subtypeReduction = false;
1930719304
let seenIncomplete = false;
19305+
let bypassFlow: FlowSwitchClause | undefined;
1930819306
for (const antecedent of flow.antecedents!) {
1930919307
if (antecedent.flags & FlowFlags.PreFinally && (<PreFinallyFlow>antecedent).lock.locked) {
1931019308
// if flow correspond to branch from pre-try to finally and this branch is locked - this means that
1931119309
// we initially have started following the flow outside the finally block.
1931219310
// in this case we should ignore this branch.
1931319311
continue;
1931419312
}
19313+
if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (<FlowSwitchClause>antecedent).clauseStart === (<FlowSwitchClause>antecedent).clauseEnd) {
19314+
// The antecedent is the bypass branch of a potentially exhaustive switch statement.
19315+
bypassFlow = <FlowSwitchClause>antecedent;
19316+
continue;
19317+
}
1931519318
const flowType = getTypeAtFlowNode(antecedent);
1931619319
const type = getTypeFromFlowType(flowType);
1931719320
// If the type at a particular antecedent path is the declared type and the
@@ -19332,6 +19335,25 @@ namespace ts {
1933219335
seenIncomplete = true;
1933319336
}
1933419337
}
19338+
if (bypassFlow) {
19339+
const flowType = getTypeAtFlowNode(bypassFlow.antecedent);
19340+
const type = getTypeFromFlowType(flowType);
19341+
// If the bypass flow contributes a type we haven't seen yet and the switch statement
19342+
// isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase
19343+
// the risk of circularities, we only want to perform them when they make a difference.
19344+
if (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) {
19345+
if (type === declaredType && declaredType === initialType) {
19346+
return type;
19347+
}
19348+
antecedentTypes.push(type);
19349+
if (!isTypeSubsetOf(type, declaredType)) {
19350+
subtypeReduction = true;
19351+
}
19352+
if (isIncomplete(flowType)) {
19353+
seenIncomplete = true;
19354+
}
19355+
}
19356+
}
1933519357
return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete);
1933619358
}
1933719359

tests/baselines/reference/exhaustiveSwitchStatements1.errors.txt

+13
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,17 @@ tests/cases/conformance/controlFlow/exhaustiveSwitchStatements1.ts(7,9): error T
212212
case Animal.CAT: return Animal.CAT
213213
}
214214
}
215+
216+
// Repro from #34840
217+
218+
function foo() {
219+
const foo: number | undefined = 0;
220+
while (true) {
221+
const stats = foo;
222+
switch (stats) {
223+
case 1: break;
224+
case 2: break;
225+
}
226+
}
227+
}
215228

tests/baselines/reference/exhaustiveSwitchStatements1.js

+25
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,19 @@ function expression(): Animal {
207207
case Animal.CAT: return Animal.CAT
208208
}
209209
}
210+
211+
// Repro from #34840
212+
213+
function foo() {
214+
const foo: number | undefined = 0;
215+
while (true) {
216+
const stats = foo;
217+
switch (stats) {
218+
case 1: break;
219+
case 2: break;
220+
}
221+
}
222+
}
210223

211224

212225
//// [exhaustiveSwitchStatements1.js]
@@ -405,6 +418,17 @@ function expression() {
405418
case Animal.CAT: return Animal.CAT;
406419
}
407420
}
421+
// Repro from #34840
422+
function foo() {
423+
var foo = 0;
424+
while (true) {
425+
var stats = foo;
426+
switch (stats) {
427+
case 1: break;
428+
case 2: break;
429+
}
430+
}
431+
}
408432

409433

410434
//// [exhaustiveSwitchStatements1.d.ts]
@@ -469,3 +493,4 @@ declare const zoo: {
469493
animal: Animal;
470494
} | undefined;
471495
declare function expression(): Animal;
496+
declare function foo(): void;

tests/baselines/reference/exhaustiveSwitchStatements1.symbols

+22
Original file line numberDiff line numberDiff line change
@@ -543,3 +543,25 @@ function expression(): Animal {
543543
}
544544
}
545545

546+
// Repro from #34840
547+
548+
function foo() {
549+
>foo : Symbol(foo, Decl(exhaustiveSwitchStatements1.ts, 207, 1))
550+
551+
const foo: number | undefined = 0;
552+
>foo : Symbol(foo, Decl(exhaustiveSwitchStatements1.ts, 212, 9))
553+
554+
while (true) {
555+
const stats = foo;
556+
>stats : Symbol(stats, Decl(exhaustiveSwitchStatements1.ts, 214, 13))
557+
>foo : Symbol(foo, Decl(exhaustiveSwitchStatements1.ts, 212, 9))
558+
559+
switch (stats) {
560+
>stats : Symbol(stats, Decl(exhaustiveSwitchStatements1.ts, 214, 13))
561+
562+
case 1: break;
563+
case 2: break;
564+
}
565+
}
566+
}
567+

tests/baselines/reference/exhaustiveSwitchStatements1.types

+28
Original file line numberDiff line numberDiff line change
@@ -634,3 +634,31 @@ function expression(): Animal {
634634
}
635635
}
636636

637+
// Repro from #34840
638+
639+
function foo() {
640+
>foo : () => void
641+
642+
const foo: number | undefined = 0;
643+
>foo : number | undefined
644+
>0 : 0
645+
646+
while (true) {
647+
>true : true
648+
649+
const stats = foo;
650+
>stats : number
651+
>foo : number
652+
653+
switch (stats) {
654+
>stats : number
655+
656+
case 1: break;
657+
>1 : 1
658+
659+
case 2: break;
660+
>2 : 2
661+
}
662+
}
663+
}
664+

tests/cases/conformance/controlFlow/exhaustiveSwitchStatements1.ts

+13
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,16 @@ function expression(): Animal {
210210
case Animal.CAT: return Animal.CAT
211211
}
212212
}
213+
214+
// Repro from #34840
215+
216+
function foo() {
217+
const foo: number | undefined = 0;
218+
while (true) {
219+
const stats = foo;
220+
switch (stats) {
221+
case 1: break;
222+
case 2: break;
223+
}
224+
}
225+
}

0 commit comments

Comments
 (0)