Skip to content

Commit 0060964

Browse files
committed
Further CFA handling of exhaustive switch statements
1 parent cc6e493 commit 0060964

File tree

3 files changed

+16
-5
lines changed

3 files changed

+16
-5
lines changed

src/compiler/binder.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1224,7 +1224,8 @@ namespace ts {
12241224
addAntecedent(postSwitchLabel, currentFlow);
12251225
const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause);
12261226
// We mark a switch statement as possibly exhaustive if it has no default clause and if all
1227-
// case clauses have unreachable end points (e.g. they all return).
1227+
// case clauses have unreachable end points (e.g. they all return). Note, we no longer need
1228+
// this property in control flow analysis, it's there only for backwards compatibility.
12281229
node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents;
12291230
if (!hasDefault) {
12301231
addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0));

src/compiler/checker.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16999,7 +16999,7 @@ namespace ts {
1699916999
function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean {
1700017000
while (true) {
1700117001
const flags = flow.flags;
17002-
if (flags & FlowFlags.Shared | flags & FlowFlags.SwitchClause) {
17002+
if (flags & FlowFlags.Shared) {
1700317003
if (!noCacheCheck) {
1700417004
const id = getFlowNodeId(flow);
1700517005
const reachable = flowNodeReachable[id];
@@ -17018,12 +17018,16 @@ namespace ts {
1701817018
flow = (<FlowCall>flow).antecedent;
1701917019
}
1702017020
else if (flags & FlowFlags.BranchLabel) {
17021+
// A branching point is reachable if any branch is reachable.
1702117022
return some((<FlowLabel>flow).antecedents!, isReachableFlowNode);
1702217023
}
1702317024
else if (flags & FlowFlags.LoopLabel) {
17025+
// A loop is reachable if the control flow path that leads to the top is reachable.
1702417026
flow = (<FlowLabel>flow).antecedents![0];
1702517027
}
1702617028
else if (flags & FlowFlags.SwitchClause) {
17029+
// The control flow path representing an unmatched value in a switch statement with
17030+
// no default clause is unreachable if the switch statement is exhaustive.
1702717031
if ((<FlowSwitchClause>flow).clauseStart === (<FlowSwitchClause>flow).clauseEnd && isExhaustiveSwitchStatement((<FlowSwitchClause>flow).switchStatement)) {
1702817032
return false;
1702917033
}
@@ -17327,6 +17331,9 @@ namespace ts {
1732717331
}
1732817332

1732917333
function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType {
17334+
if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement((<FlowSwitchClause>flow).switchStatement)) {
17335+
return neverType;
17336+
}
1733017337
const expr = flow.switchStatement.expression;
1733117338
const flowType = getTypeAtFlowNode(flow.antecedent);
1733217339
let type = getTypeFromFlowType(flowType);
@@ -23793,9 +23800,11 @@ namespace ts {
2379323800
}
2379423801

2379523802
function isExhaustiveSwitchStatement(node: SwitchStatement): boolean {
23796-
if (!node.possiblyExhaustive) {
23797-
return false;
23798-
}
23803+
const links = getNodeLinks(node);
23804+
return links.isExhaustive !== undefined ? links.isExhaustive : (links.isExhaustive = computeExhaustiveSwitchStatement(node));
23805+
}
23806+
23807+
function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean {
2379923808
if (node.expression.kind === SyntaxKind.TypeOfExpression) {
2380023809
const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression);
2380123810
// This cast is safe because the switch is possibly exhaustive and does not contain a default case, so there can be no undefined.

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4016,6 +4016,7 @@ namespace ts {
40164016
contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive
40174017
deferredNodes?: Map<Node>; // Set of nodes whose checking has been deferred
40184018
capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement
4019+
isExhaustive?: boolean; // Is node an exhaustive switch statement
40194020
}
40204021

40214022
export const enum TypeFlags {

0 commit comments

Comments
 (0)