Skip to content

Commit cc6e493

Browse files
committed
Treat exhaustive switch statements like non-returning functions in CFA
1 parent 3a89c8c commit cc6e493

File tree

3 files changed

+32
-17
lines changed

3 files changed

+32
-17
lines changed

src/compiler/binder.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ namespace ts {
569569
}
570570
// We create a return control flow graph for IIFEs and constructors. For constructors
571571
// we use the return control flow graph in strict property initialization checks.
572-
currentReturnTarget = containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((<FunctionLikeDeclaration>node).body) ? createBranchLabel() : undefined;
572+
currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined;
573573
currentBreakTarget = undefined;
574574
currentContinueTarget = undefined;
575575
activeLabels = undefined;
@@ -581,6 +581,7 @@ namespace ts {
581581
if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((<FunctionLikeDeclaration>node).body)) {
582582
node.flags |= NodeFlags.HasImplicitReturn;
583583
if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn;
584+
(<FunctionLikeDeclaration>node).endFlowNode = currentFlow;
584585
}
585586
if (node.kind === SyntaxKind.SourceFile) {
586587
node.flags |= emitFlags;
@@ -589,7 +590,9 @@ namespace ts {
589590
if (currentReturnTarget) {
590591
addAntecedent(currentReturnTarget, currentFlow);
591592
currentFlow = finishFlowLabel(currentReturnTarget);
592-
(<FunctionLikeDeclaration>node).returnFlowNode = currentFlow;
593+
if (node.kind === SyntaxKind.Constructor) {
594+
(<ConstructorDeclaration>node).returnFlowNode = currentFlow;
595+
}
593596
}
594597
if (!isIIFE) {
595598
currentFlow = saveCurrentFlow;

src/compiler/checker.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16996,16 +16996,19 @@ namespace ts {
1699616996
return isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ false);
1699716997
}
1699816998

16999-
function isReachableFlowNodeWorker(flow: FlowNode, skipCacheCheck: boolean): boolean {
16999+
function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean {
1700017000
while (true) {
1700117001
const flags = flow.flags;
17002-
if (flags & FlowFlags.Shared && !skipCacheCheck) {
17003-
const id = getFlowNodeId(flow);
17004-
const reachable = flowNodeReachable[id];
17005-
return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ true));
17002+
if (flags & FlowFlags.Shared | flags & FlowFlags.SwitchClause) {
17003+
if (!noCacheCheck) {
17004+
const id = getFlowNodeId(flow);
17005+
const reachable = flowNodeReachable[id];
17006+
return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*skipCacheCheck*/ true));
17007+
}
17008+
noCacheCheck = false;
1700617009
}
17007-
if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.SwitchClause | FlowFlags.ArrayMutation | FlowFlags.PreFinally | FlowFlags.AfterFinally)) {
17008-
flow = (<FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation | PreFinallyFlow | AfterFinallyFlow>flow).antecedent;
17010+
if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.PreFinally | FlowFlags.AfterFinally)) {
17011+
flow = (<FlowAssignment | FlowCondition | FlowArrayMutation | PreFinallyFlow | AfterFinallyFlow>flow).antecedent;
1700917012
}
1701017013
else if (flags & FlowFlags.Call) {
1701117014
const signature = getEffectsSignature((<FlowCall>flow).node);
@@ -17014,14 +17017,20 @@ namespace ts {
1701417017
}
1701517018
flow = (<FlowCall>flow).antecedent;
1701617019
}
17020+
else if (flags & FlowFlags.BranchLabel) {
17021+
return some((<FlowLabel>flow).antecedents!, isReachableFlowNode);
17022+
}
1701717023
else if (flags & FlowFlags.LoopLabel) {
1701817024
flow = (<FlowLabel>flow).antecedents![0];
1701917025
}
17020-
else if (flags & FlowFlags.BranchLabel) {
17021-
return every((<FlowLabel>flow).antecedents!, isReachableFlowNode);
17026+
else if (flags & FlowFlags.SwitchClause) {
17027+
if ((<FlowSwitchClause>flow).clauseStart === (<FlowSwitchClause>flow).clauseEnd && isExhaustiveSwitchStatement((<FlowSwitchClause>flow).switchStatement)) {
17028+
return false;
17029+
}
17030+
flow = (<FlowSwitchClause>flow).antecedent;
1702217031
}
1702317032
else {
17024-
return true;
17033+
return !(flags & FlowFlags.Unreachable);
1702517034
}
1702617035
}
1702717036
}
@@ -17044,7 +17053,11 @@ namespace ts {
1704417053
// on empty arrays are possible without implicit any errors and new element types can be inferred without
1704517054
// type mismatch errors.
1704617055
const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType);
17047-
if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
17056+
if (resultType === unreachableNeverType) {
17057+
error(reference, Diagnostics.Unreachable_code_detected);
17058+
return declaredType;
17059+
}
17060+
if (reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
1704817061
return declaredType;
1704917062
}
1705017063
return resultType;
@@ -23804,9 +23817,7 @@ namespace ts {
2380423817
}
2380523818

2380623819
function functionHasImplicitReturn(func: FunctionLikeDeclaration) {
23807-
return !!(func.flags & NodeFlags.HasImplicitReturn &&
23808-
!some((<Block>func.body).statements, s => s.kind === SyntaxKind.SwitchStatement && isExhaustiveSwitchStatement(<SwitchStatement>s)) &&
23809-
!(func.returnFlowNode && !isReachableFlowNode(func.returnFlowNode)));
23820+
return func.endFlowNode && isReachableFlowNode(func.endFlowNode);
2381023821
}
2381123822

2381223823
/** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */

src/compiler/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1040,7 +1040,7 @@ namespace ts {
10401040
questionToken?: QuestionToken;
10411041
exclamationToken?: ExclamationToken;
10421042
body?: Block | Expression;
1043-
/* @internal */ returnFlowNode?: FlowNode;
1043+
/* @internal */ endFlowNode?: FlowNode;
10441044
}
10451045

10461046
export type FunctionLikeDeclaration =
@@ -1086,6 +1086,7 @@ namespace ts {
10861086
kind: SyntaxKind.Constructor;
10871087
parent: ClassLikeDeclaration;
10881088
body?: FunctionBody;
1089+
/* @internal */ returnFlowNode?: FlowNode;
10891090
}
10901091

10911092
/** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */

0 commit comments

Comments
 (0)