Skip to content

Defer switch exhaustiveness checks #35000

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19294,9 +19294,6 @@ namespace ts {
else if (containsMatchingReferenceDiscriminant(reference, expr)) {
type = declaredType;
}
else if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement(flow.switchStatement)) {
return unreachableNeverType;
}
}
return createFlowType(type, isIncomplete(flowType));
}
Expand All @@ -19305,13 +19302,19 @@ namespace ts {
const antecedentTypes: Type[] = [];
let subtypeReduction = false;
let seenIncomplete = false;
let bypassFlow: FlowSwitchClause | undefined;
for (const antecedent of flow.antecedents!) {
if (antecedent.flags & FlowFlags.PreFinally && (<PreFinallyFlow>antecedent).lock.locked) {
// if flow correspond to branch from pre-try to finally and this branch is locked - this means that
// we initially have started following the flow outside the finally block.
// in this case we should ignore this branch.
continue;
}
if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (<FlowSwitchClause>antecedent).clauseStart === (<FlowSwitchClause>antecedent).clauseEnd) {
// The antecedent is the bypass branch of a potentially exhaustive switch statement.
bypassFlow = <FlowSwitchClause>antecedent;
continue;
}
const flowType = getTypeAtFlowNode(antecedent);
const type = getTypeFromFlowType(flowType);
// If the type at a particular antecedent path is the declared type and the
Expand All @@ -19332,6 +19335,25 @@ namespace ts {
seenIncomplete = true;
}
}
if (bypassFlow) {
const flowType = getTypeAtFlowNode(bypassFlow.antecedent);
const type = getTypeFromFlowType(flowType);
// If the bypass flow contributes a type we haven't seen yet and the switch statement
// isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase
// the risk of circularities, we only want to perform them when they make a difference.
if (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) {
if (type === declaredType && declaredType === initialType) {
return type;
}
antecedentTypes.push(type);
if (!isTypeSubsetOf(type, declaredType)) {
subtypeReduction = true;
}
if (isIncomplete(flowType)) {
seenIncomplete = true;
}
}
}
return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete);
}

Expand Down
13 changes: 13 additions & 0 deletions tests/baselines/reference/exhaustiveSwitchStatements1.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,17 @@ tests/cases/conformance/controlFlow/exhaustiveSwitchStatements1.ts(7,9): error T
case Animal.CAT: return Animal.CAT
}
}

// Repro from #34840

function foo() {
const foo: number | undefined = 0;
while (true) {
const stats = foo;
switch (stats) {
case 1: break;
case 2: break;
}
}
}

25 changes: 25 additions & 0 deletions tests/baselines/reference/exhaustiveSwitchStatements1.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,19 @@ function expression(): Animal {
case Animal.CAT: return Animal.CAT
}
}

// Repro from #34840

function foo() {
const foo: number | undefined = 0;
while (true) {
const stats = foo;
switch (stats) {
case 1: break;
case 2: break;
}
}
}


//// [exhaustiveSwitchStatements1.js]
Expand Down Expand Up @@ -405,6 +418,17 @@ function expression() {
case Animal.CAT: return Animal.CAT;
}
}
// Repro from #34840
function foo() {
var foo = 0;
while (true) {
var stats = foo;
switch (stats) {
case 1: break;
case 2: break;
}
}
}


//// [exhaustiveSwitchStatements1.d.ts]
Expand Down Expand Up @@ -469,3 +493,4 @@ declare const zoo: {
animal: Animal;
} | undefined;
declare function expression(): Animal;
declare function foo(): void;
22 changes: 22 additions & 0 deletions tests/baselines/reference/exhaustiveSwitchStatements1.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -543,3 +543,25 @@ function expression(): Animal {
}
}

// Repro from #34840

function foo() {
>foo : Symbol(foo, Decl(exhaustiveSwitchStatements1.ts, 207, 1))

const foo: number | undefined = 0;
>foo : Symbol(foo, Decl(exhaustiveSwitchStatements1.ts, 212, 9))

while (true) {
const stats = foo;
>stats : Symbol(stats, Decl(exhaustiveSwitchStatements1.ts, 214, 13))
>foo : Symbol(foo, Decl(exhaustiveSwitchStatements1.ts, 212, 9))

switch (stats) {
>stats : Symbol(stats, Decl(exhaustiveSwitchStatements1.ts, 214, 13))

case 1: break;
case 2: break;
}
}
}

28 changes: 28 additions & 0 deletions tests/baselines/reference/exhaustiveSwitchStatements1.types
Original file line number Diff line number Diff line change
Expand Up @@ -634,3 +634,31 @@ function expression(): Animal {
}
}

// Repro from #34840

function foo() {
>foo : () => void

const foo: number | undefined = 0;
>foo : number | undefined
>0 : 0

while (true) {
>true : true

const stats = foo;
>stats : number
>foo : number

switch (stats) {
>stats : number

case 1: break;
>1 : 1

case 2: break;
>2 : 2
}
}
}

13 changes: 13 additions & 0 deletions tests/cases/conformance/controlFlow/exhaustiveSwitchStatements1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,16 @@ function expression(): Animal {
case Animal.CAT: return Animal.CAT
}
}

// Repro from #34840

function foo() {
const foo: number | undefined = 0;
while (true) {
const stats = foo;
switch (stats) {
case 1: break;
case 2: break;
}
}
}