Defer switch exhaustiveness checks (#35000)

* Defer switch exhaustiveness checks until they're actually needed

* Add regression test

* Accept new baselines
This commit is contained in:
Anders Hejlsberg 2019-11-09 07:52:39 -08:00 committed by GitHub
parent 165b4bc100
commit 3a5230ab3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 126 additions and 3 deletions

View File

@ -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));
}
@ -19305,6 +19302,7 @@ 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
@ -19312,6 +19310,11 @@ namespace ts {
// 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
@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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]
@ -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]
@ -469,3 +493,4 @@ declare const zoo: {
animal: Animal;
} | undefined;
declare function expression(): Animal;
declare function foo(): void;

View File

@ -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;
}
}
}

View File

@ -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
}
}
}

View File

@ -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;
}
}
}