Treat exhaustive switch statements like non-returning functions in CFA

This commit is contained in:
Anders Hejlsberg 2019-09-14 15:30:09 -07:00
parent 3a89c8cc5c
commit cc6e4938ae
3 changed files with 32 additions and 17 deletions

View File

@ -569,7 +569,7 @@ namespace ts {
}
// We create a return control flow graph for IIFEs and constructors. For constructors
// we use the return control flow graph in strict property initialization checks.
currentReturnTarget = containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((<FunctionLikeDeclaration>node).body) ? createBranchLabel() : undefined;
currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined;
currentBreakTarget = undefined;
currentContinueTarget = undefined;
activeLabels = undefined;
@ -581,6 +581,7 @@ namespace ts {
if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((<FunctionLikeDeclaration>node).body)) {
node.flags |= NodeFlags.HasImplicitReturn;
if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn;
(<FunctionLikeDeclaration>node).endFlowNode = currentFlow;
}
if (node.kind === SyntaxKind.SourceFile) {
node.flags |= emitFlags;
@ -589,7 +590,9 @@ namespace ts {
if (currentReturnTarget) {
addAntecedent(currentReturnTarget, currentFlow);
currentFlow = finishFlowLabel(currentReturnTarget);
(<FunctionLikeDeclaration>node).returnFlowNode = currentFlow;
if (node.kind === SyntaxKind.Constructor) {
(<ConstructorDeclaration>node).returnFlowNode = currentFlow;
}
}
if (!isIIFE) {
currentFlow = saveCurrentFlow;

View File

@ -16996,16 +16996,19 @@ namespace ts {
return isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ false);
}
function isReachableFlowNodeWorker(flow: FlowNode, skipCacheCheck: boolean): boolean {
function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean {
while (true) {
const flags = flow.flags;
if (flags & FlowFlags.Shared && !skipCacheCheck) {
const id = getFlowNodeId(flow);
const reachable = flowNodeReachable[id];
return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ true));
if (flags & FlowFlags.Shared | flags & FlowFlags.SwitchClause) {
if (!noCacheCheck) {
const id = getFlowNodeId(flow);
const reachable = flowNodeReachable[id];
return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ true));
}
noCacheCheck = false;
}
if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.SwitchClause | FlowFlags.ArrayMutation | FlowFlags.PreFinally | FlowFlags.AfterFinally)) {
flow = (<FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation | PreFinallyFlow | AfterFinallyFlow>flow).antecedent;
if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.PreFinally | FlowFlags.AfterFinally)) {
flow = (<FlowAssignment | FlowCondition | FlowArrayMutation | PreFinallyFlow | AfterFinallyFlow>flow).antecedent;
}
else if (flags & FlowFlags.Call) {
const signature = getEffectsSignature((<FlowCall>flow).node);
@ -17014,14 +17017,20 @@ namespace ts {
}
flow = (<FlowCall>flow).antecedent;
}
else if (flags & FlowFlags.BranchLabel) {
return some((<FlowLabel>flow).antecedents!, isReachableFlowNode);
}
else if (flags & FlowFlags.LoopLabel) {
flow = (<FlowLabel>flow).antecedents![0];
}
else if (flags & FlowFlags.BranchLabel) {
return every((<FlowLabel>flow).antecedents!, isReachableFlowNode);
else if (flags & FlowFlags.SwitchClause) {
if ((<FlowSwitchClause>flow).clauseStart === (<FlowSwitchClause>flow).clauseEnd && isExhaustiveSwitchStatement((<FlowSwitchClause>flow).switchStatement)) {
return false;
}
flow = (<FlowSwitchClause>flow).antecedent;
}
else {
return true;
return !(flags & FlowFlags.Unreachable);
}
}
}
@ -17044,7 +17053,11 @@ namespace ts {
// on empty arrays are possible without implicit any errors and new element types can be inferred without
// type mismatch errors.
const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType);
if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
if (resultType === unreachableNeverType) {
error(reference, Diagnostics.Unreachable_code_detected);
return declaredType;
}
if (reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
return declaredType;
}
return resultType;
@ -23804,9 +23817,7 @@ namespace ts {
}
function functionHasImplicitReturn(func: FunctionLikeDeclaration) {
return !!(func.flags & NodeFlags.HasImplicitReturn &&
!some((<Block>func.body).statements, s => s.kind === SyntaxKind.SwitchStatement && isExhaustiveSwitchStatement(<SwitchStatement>s)) &&
!(func.returnFlowNode && !isReachableFlowNode(func.returnFlowNode)));
return func.endFlowNode && isReachableFlowNode(func.endFlowNode);
}
/** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */

View File

@ -1040,7 +1040,7 @@ namespace ts {
questionToken?: QuestionToken;
exclamationToken?: ExclamationToken;
body?: Block | Expression;
/* @internal */ returnFlowNode?: FlowNode;
/* @internal */ endFlowNode?: FlowNode;
}
export type FunctionLikeDeclaration =
@ -1086,6 +1086,7 @@ namespace ts {
kind: SyntaxKind.Constructor;
parent: ClassLikeDeclaration;
body?: FunctionBody;
/* @internal */ returnFlowNode?: FlowNode;
}
/** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */