Skip to content

Commit 95be956

Browse files
authored
Fix switch statement exhaustiveness checking (microsoft#34840)
* Don't optimize away CFA nodes representing missing default clauses * Add regression test * Accept new baselines
1 parent 966d986 commit 95be956

File tree

6 files changed

+145
-4
lines changed

6 files changed

+145
-4
lines changed

src/compiler/binder.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -965,9 +965,6 @@ namespace ts {
965965
}
966966

967967
function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode {
968-
if (!isNarrowingExpression(switchStatement.expression)) {
969-
return antecedent;
970-
}
971968
setFlowNodeReferenced(antecedent);
972969
return flowNodeCreated({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd });
973970
}
@@ -1323,6 +1320,7 @@ namespace ts {
13231320
const savedSubtreeTransformFlags = subtreeTransformFlags;
13241321
subtreeTransformFlags = 0;
13251322
const clauses = node.clauses;
1323+
const isNarrowingSwitch = isNarrowingExpression(node.parent.expression);
13261324
let fallthroughFlow = unreachableFlow;
13271325
for (let i = 0; i < clauses.length; i++) {
13281326
const clauseStart = i;
@@ -1331,7 +1329,7 @@ namespace ts {
13311329
i++;
13321330
}
13331331
const preCaseLabel = createBranchLabel();
1334-
addAntecedent(preCaseLabel, createFlowSwitchClause(preSwitchCaseFlow!, node.parent, clauseStart, i + 1));
1332+
addAntecedent(preCaseLabel, isNarrowingSwitch ? createFlowSwitchClause(preSwitchCaseFlow!, node.parent, clauseStart, i + 1) : preSwitchCaseFlow!);
13351333
addAntecedent(preCaseLabel, fallthroughFlow);
13361334
currentFlow = finishFlowLabel(preCaseLabel);
13371335
const clause = clauses[i];

tests/baselines/reference/exhaustiveSwitchStatements1.errors.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,17 @@ tests/cases/conformance/controlFlow/exhaustiveSwitchStatements1.ts(7,9): error T
199199
}
200200
return x;
201201
}
202+
203+
// Repro from #34661
204+
205+
enum Animal { DOG, CAT }
206+
207+
declare const zoo: { animal: Animal } | undefined;
208+
209+
function expression(): Animal {
210+
switch (zoo?.animal ?? Animal.DOG) {
211+
case Animal.DOG: return Animal.DOG
212+
case Animal.CAT: return Animal.CAT
213+
}
214+
}
202215

tests/baselines/reference/exhaustiveSwitchStatements1.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,19 @@ function test4(value: 1 | 2) {
194194
}
195195
return x;
196196
}
197+
198+
// Repro from #34661
199+
200+
enum Animal { DOG, CAT }
201+
202+
declare const zoo: { animal: Animal } | undefined;
203+
204+
function expression(): Animal {
205+
switch (zoo?.animal ?? Animal.DOG) {
206+
case Animal.DOG: return Animal.DOG
207+
case Animal.CAT: return Animal.CAT
208+
}
209+
}
197210

198211

199212
//// [exhaustiveSwitchStatements1.js]
@@ -379,6 +392,19 @@ function test4(value) {
379392
}
380393
return x;
381394
}
395+
// Repro from #34661
396+
var Animal;
397+
(function (Animal) {
398+
Animal[Animal["DOG"] = 0] = "DOG";
399+
Animal[Animal["CAT"] = 1] = "CAT";
400+
})(Animal || (Animal = {}));
401+
function expression() {
402+
var _a, _b;
403+
switch ((_b = (_a = zoo) === null || _a === void 0 ? void 0 : _a.animal, (_b !== null && _b !== void 0 ? _b : Animal.DOG))) {
404+
case Animal.DOG: return Animal.DOG;
405+
case Animal.CAT: return Animal.CAT;
406+
}
407+
}
382408

383409

384410
//// [exhaustiveSwitchStatements1.d.ts]
@@ -435,3 +461,11 @@ declare type Shape2 = Square2 | Circle2;
435461
declare function withDefault(s1: Shape2, s2: Shape2): string;
436462
declare function withoutDefault(s1: Shape2, s2: Shape2): string;
437463
declare function test4(value: 1 | 2): string;
464+
declare enum Animal {
465+
DOG = 0,
466+
CAT = 1
467+
}
468+
declare const zoo: {
469+
animal: Animal;
470+
} | undefined;
471+
declare function expression(): Animal;

tests/baselines/reference/exhaustiveSwitchStatements1.symbols

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,3 +501,45 @@ function test4(value: 1 | 2) {
501501
>x : Symbol(x, Decl(exhaustiveSwitchStatements1.ts, 188, 7))
502502
}
503503

504+
// Repro from #34661
505+
506+
enum Animal { DOG, CAT }
507+
>Animal : Symbol(Animal, Decl(exhaustiveSwitchStatements1.ts, 194, 1))
508+
>DOG : Symbol(Animal.DOG, Decl(exhaustiveSwitchStatements1.ts, 198, 13))
509+
>CAT : Symbol(Animal.CAT, Decl(exhaustiveSwitchStatements1.ts, 198, 18))
510+
511+
declare const zoo: { animal: Animal } | undefined;
512+
>zoo : Symbol(zoo, Decl(exhaustiveSwitchStatements1.ts, 200, 13))
513+
>animal : Symbol(animal, Decl(exhaustiveSwitchStatements1.ts, 200, 20))
514+
>Animal : Symbol(Animal, Decl(exhaustiveSwitchStatements1.ts, 194, 1))
515+
516+
function expression(): Animal {
517+
>expression : Symbol(expression, Decl(exhaustiveSwitchStatements1.ts, 200, 50))
518+
>Animal : Symbol(Animal, Decl(exhaustiveSwitchStatements1.ts, 194, 1))
519+
520+
switch (zoo?.animal ?? Animal.DOG) {
521+
>zoo?.animal : Symbol(animal, Decl(exhaustiveSwitchStatements1.ts, 200, 20))
522+
>zoo : Symbol(zoo, Decl(exhaustiveSwitchStatements1.ts, 200, 13))
523+
>animal : Symbol(animal, Decl(exhaustiveSwitchStatements1.ts, 200, 20))
524+
>Animal.DOG : Symbol(Animal.DOG, Decl(exhaustiveSwitchStatements1.ts, 198, 13))
525+
>Animal : Symbol(Animal, Decl(exhaustiveSwitchStatements1.ts, 194, 1))
526+
>DOG : Symbol(Animal.DOG, Decl(exhaustiveSwitchStatements1.ts, 198, 13))
527+
528+
case Animal.DOG: return Animal.DOG
529+
>Animal.DOG : Symbol(Animal.DOG, Decl(exhaustiveSwitchStatements1.ts, 198, 13))
530+
>Animal : Symbol(Animal, Decl(exhaustiveSwitchStatements1.ts, 194, 1))
531+
>DOG : Symbol(Animal.DOG, Decl(exhaustiveSwitchStatements1.ts, 198, 13))
532+
>Animal.DOG : Symbol(Animal.DOG, Decl(exhaustiveSwitchStatements1.ts, 198, 13))
533+
>Animal : Symbol(Animal, Decl(exhaustiveSwitchStatements1.ts, 194, 1))
534+
>DOG : Symbol(Animal.DOG, Decl(exhaustiveSwitchStatements1.ts, 198, 13))
535+
536+
case Animal.CAT: return Animal.CAT
537+
>Animal.CAT : Symbol(Animal.CAT, Decl(exhaustiveSwitchStatements1.ts, 198, 18))
538+
>Animal : Symbol(Animal, Decl(exhaustiveSwitchStatements1.ts, 194, 1))
539+
>CAT : Symbol(Animal.CAT, Decl(exhaustiveSwitchStatements1.ts, 198, 18))
540+
>Animal.CAT : Symbol(Animal.CAT, Decl(exhaustiveSwitchStatements1.ts, 198, 18))
541+
>Animal : Symbol(Animal, Decl(exhaustiveSwitchStatements1.ts, 194, 1))
542+
>CAT : Symbol(Animal.CAT, Decl(exhaustiveSwitchStatements1.ts, 198, 18))
543+
}
544+
}
545+

tests/baselines/reference/exhaustiveSwitchStatements1.types

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,3 +593,44 @@ function test4(value: 1 | 2) {
593593
>x : string
594594
}
595595

596+
// Repro from #34661
597+
598+
enum Animal { DOG, CAT }
599+
>Animal : Animal
600+
>DOG : Animal.DOG
601+
>CAT : Animal.CAT
602+
603+
declare const zoo: { animal: Animal } | undefined;
604+
>zoo : { animal: Animal; } | undefined
605+
>animal : Animal
606+
607+
function expression(): Animal {
608+
>expression : () => Animal
609+
610+
switch (zoo?.animal ?? Animal.DOG) {
611+
>zoo?.animal ?? Animal.DOG : Animal
612+
>zoo?.animal : Animal | undefined
613+
>zoo : { animal: Animal; } | undefined
614+
>animal : Animal | undefined
615+
>Animal.DOG : Animal.DOG
616+
>Animal : typeof Animal
617+
>DOG : Animal.DOG
618+
619+
case Animal.DOG: return Animal.DOG
620+
>Animal.DOG : Animal.DOG
621+
>Animal : typeof Animal
622+
>DOG : Animal.DOG
623+
>Animal.DOG : Animal.DOG
624+
>Animal : typeof Animal
625+
>DOG : Animal.DOG
626+
627+
case Animal.CAT: return Animal.CAT
628+
>Animal.CAT : Animal.CAT
629+
>Animal : typeof Animal
630+
>CAT : Animal.CAT
631+
>Animal.CAT : Animal.CAT
632+
>Animal : typeof Animal
633+
>CAT : Animal.CAT
634+
}
635+
}
636+

tests/cases/conformance/controlFlow/exhaustiveSwitchStatements1.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,16 @@ function test4(value: 1 | 2) {
197197
}
198198
return x;
199199
}
200+
201+
// Repro from #34661
202+
203+
enum Animal { DOG, CAT }
204+
205+
declare const zoo: { animal: Animal } | undefined;
206+
207+
function expression(): Animal {
208+
switch (zoo?.animal ?? Animal.DOG) {
209+
case Animal.DOG: return Animal.DOG
210+
case Animal.CAT: return Animal.CAT
211+
}
212+
}

0 commit comments

Comments
 (0)