From 0060964fba457f83eb77c48b60909e23184b59d7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 15 Sep 2019 08:25:07 -0700 Subject: [PATCH] Further CFA handling of exhaustive switch statements --- src/compiler/binder.ts | 3 ++- src/compiler/checker.ts | 17 +++++++++++++---- src/compiler/types.ts | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index e93fef838e8..6efc4ae05b0 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1224,7 +1224,8 @@ namespace ts { addAntecedent(postSwitchLabel, currentFlow); const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause); // We mark a switch statement as possibly exhaustive if it has no default clause and if all - // case clauses have unreachable end points (e.g. they all return). + // case clauses have unreachable end points (e.g. they all return). Note, we no longer need + // this property in control flow analysis, it's there only for backwards compatibility. node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents; if (!hasDefault) { addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0)); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0d9397eb6b3..e5204350742 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16999,7 +16999,7 @@ namespace ts { function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean { while (true) { const flags = flow.flags; - if (flags & FlowFlags.Shared | flags & FlowFlags.SwitchClause) { + if (flags & FlowFlags.Shared) { if (!noCacheCheck) { const id = getFlowNodeId(flow); const reachable = flowNodeReachable[id]; @@ -17018,12 +17018,16 @@ namespace ts { flow = (flow).antecedent; } else if (flags & FlowFlags.BranchLabel) { + // A branching point is reachable if any branch is reachable. return some((flow).antecedents!, isReachableFlowNode); } else if (flags & FlowFlags.LoopLabel) { + // A loop is reachable if the control flow path that leads to the top is reachable. flow = (flow).antecedents![0]; } else if (flags & FlowFlags.SwitchClause) { + // The control flow path representing an unmatched value in a switch statement with + // no default clause is unreachable if the switch statement is exhaustive. if ((flow).clauseStart === (flow).clauseEnd && isExhaustiveSwitchStatement((flow).switchStatement)) { return false; } @@ -17327,6 +17331,9 @@ namespace ts { } function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { + if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement((flow).switchStatement)) { + return neverType; + } const expr = flow.switchStatement.expression; const flowType = getTypeAtFlowNode(flow.antecedent); let type = getTypeFromFlowType(flowType); @@ -23793,9 +23800,11 @@ namespace ts { } function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { - if (!node.possiblyExhaustive) { - return false; - } + const links = getNodeLinks(node); + return links.isExhaustive !== undefined ? links.isExhaustive : (links.isExhaustive = computeExhaustiveSwitchStatement(node)); + } + + function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean { if (node.expression.kind === SyntaxKind.TypeOfExpression) { const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression); // This cast is safe because the switch is possibly exhaustive and does not contain a default case, so there can be no undefined. diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3ba88e3e45d..f80126b64c5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4016,6 +4016,7 @@ namespace ts { contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive deferredNodes?: Map; // Set of nodes whose checking has been deferred capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement + isExhaustive?: boolean; // Is node an exhaustive switch statement } export const enum TypeFlags {