mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-11 17:41:26 -06:00
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:
parent
165b4bc100
commit
3a5230ab3d
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user