Further CFA handling of exhaustive switch statements

This commit is contained in:
Anders Hejlsberg
2019-09-15 08:25:07 -07:00
parent cc6e4938ae
commit 0060964fba
3 changed files with 16 additions and 5 deletions

View File

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

View File

@@ -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 = (<FlowCall>flow).antecedent;
}
else if (flags & FlowFlags.BranchLabel) {
// A branching point is reachable if any branch is reachable.
return some((<FlowLabel>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 = (<FlowLabel>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 ((<FlowSwitchClause>flow).clauseStart === (<FlowSwitchClause>flow).clauseEnd && isExhaustiveSwitchStatement((<FlowSwitchClause>flow).switchStatement)) {
return false;
}
@@ -17327,6 +17331,9 @@ namespace ts {
}
function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType {
if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement((<FlowSwitchClause>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.

View File

@@ -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<Node>; // 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 {