From 53341526e25f747005b72752afab6192ed72d2d7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 8 Nov 2019 09:01:25 -1000 Subject: [PATCH 1/3] Defer switch exhaustiveness checks until they're actually needed --- src/compiler/checker.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c3faa45cd5420..824836e4db7c6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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 && (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 && (antecedent).clauseStart === (antecedent).clauseEnd) { + // The antecedent is the bypass branch of a potentially exhaustive switch statement. + bypassFlow = 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); } From ec80302d351b9cfca42ba5c4f8540cec2b8992d9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 8 Nov 2019 09:18:07 -1000 Subject: [PATCH 2/3] Add regression test --- .../controlFlow/exhaustiveSwitchStatements1.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/cases/conformance/controlFlow/exhaustiveSwitchStatements1.ts b/tests/cases/conformance/controlFlow/exhaustiveSwitchStatements1.ts index 72de718411f54..5f59a6d00df57 100644 --- a/tests/cases/conformance/controlFlow/exhaustiveSwitchStatements1.ts +++ b/tests/cases/conformance/controlFlow/exhaustiveSwitchStatements1.ts @@ -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; + } + } +} From be5d09e88ad459ba5b60cfa561fb704b309c6f85 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 8 Nov 2019 09:18:13 -1000 Subject: [PATCH 3/3] Accept new baselines --- .../exhaustiveSwitchStatements1.errors.txt | 13 +++++++++ .../reference/exhaustiveSwitchStatements1.js | 25 +++++++++++++++++ .../exhaustiveSwitchStatements1.symbols | 22 +++++++++++++++ .../exhaustiveSwitchStatements1.types | 28 +++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/tests/baselines/reference/exhaustiveSwitchStatements1.errors.txt b/tests/baselines/reference/exhaustiveSwitchStatements1.errors.txt index d6be1215e164d..61f80f6ed3d49 100644 --- a/tests/baselines/reference/exhaustiveSwitchStatements1.errors.txt +++ b/tests/baselines/reference/exhaustiveSwitchStatements1.errors.txt @@ -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; + } + } + } \ No newline at end of file diff --git a/tests/baselines/reference/exhaustiveSwitchStatements1.js b/tests/baselines/reference/exhaustiveSwitchStatements1.js index b7da0e008b303..c29ecfe8af330 100644 --- a/tests/baselines/reference/exhaustiveSwitchStatements1.js +++ b/tests/baselines/reference/exhaustiveSwitchStatements1.js @@ -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; diff --git a/tests/baselines/reference/exhaustiveSwitchStatements1.symbols b/tests/baselines/reference/exhaustiveSwitchStatements1.symbols index 3797f342228b6..9dade3d21c6e3 100644 --- a/tests/baselines/reference/exhaustiveSwitchStatements1.symbols +++ b/tests/baselines/reference/exhaustiveSwitchStatements1.symbols @@ -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; + } + } +} + diff --git a/tests/baselines/reference/exhaustiveSwitchStatements1.types b/tests/baselines/reference/exhaustiveSwitchStatements1.types index 0eb327c9d6de8..6a9f9109d12b8 100644 --- a/tests/baselines/reference/exhaustiveSwitchStatements1.types +++ b/tests/baselines/reference/exhaustiveSwitchStatements1.types @@ -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 + } + } +} +