Skip to content

Commit 3b5c72c

Browse files
committed
Include outer function expressions in control flow analysis
1 parent df4ab08 commit 3b5c72c

3 files changed

Lines changed: 77 additions & 11 deletions

File tree

src/compiler/binder.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,9 @@ namespace ts {
472472

473473
hasExplicitReturn = false;
474474
currentFlow = { flags: FlowFlags.Start };
475+
if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) {
476+
(<FlowStart>currentFlow).container = <FunctionExpression | ArrowFunction>node;
477+
}
475478
currentBreakTarget = undefined;
476479
currentContinueTarget = undefined;
477480
activeLabels = undefined;
@@ -589,6 +592,9 @@ namespace ts {
589592
case SyntaxKind.VariableDeclaration:
590593
bindVariableDeclarationFlow(<VariableDeclaration>node);
591594
break;
595+
case SyntaxKind.CallExpression:
596+
bindCallExpressionFlow(<CallExpression>node);
597+
break;
592598
default:
593599
forEachChild(node, bind);
594600
break;
@@ -1098,6 +1104,20 @@ namespace ts {
10981104
}
10991105
}
11001106

1107+
function bindCallExpressionFlow(node: CallExpression) {
1108+
forEachChild(node, bind);
1109+
// If the target of the call expression is a function expression or arrow function we have
1110+
// an immediately invoked function expression (IIFE). Initialize the flowNode property to
1111+
// the current control flow (which includes evaluation of the IIFE arguments).
1112+
let expr: Expression = node.expression;
1113+
while (expr.kind === SyntaxKind.ParenthesizedExpression) {
1114+
expr = (<ParenthesizedExpression>expr).expression;
1115+
}
1116+
if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) {
1117+
node.flowNode = currentFlow;
1118+
}
1119+
}
1120+
11011121
function getContainerFlags(node: Node): ContainerFlags {
11021122
switch (node.kind) {
11031123
case SyntaxKind.ClassExpression:
@@ -2054,7 +2074,9 @@ namespace ts {
20542074
hasAsyncFunctions = true;
20552075
}
20562076
}
2057-
2077+
if (currentFlow) {
2078+
node.flowNode = currentFlow;
2079+
}
20582080
checkStrictModeFunctionName(<FunctionExpression>node);
20592081
const bindingName = (<FunctionExpression>node).name ? (<FunctionExpression>node).name.text : "__function";
20602082
return bindAnonymousDeclaration(<FunctionExpression>node, SymbolFlags.Function, bindingName);

src/compiler/checker.ts

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7648,7 +7648,7 @@ namespace ts {
76487648
getInitialTypeOfBindingElement(<BindingElement>node);
76497649
}
76507650

7651-
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean) {
7651+
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
76527652
let key: string;
76537653
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
76547654
return declaredType;
@@ -7694,15 +7694,30 @@ namespace ts {
76947694
getTypeAtFlowBranchLabel(<FlowLabel>flow) :
76957695
getTypeAtFlowLoopLabel(<FlowLabel>flow);
76967696
}
7697-
else if (flow.flags & FlowFlags.Unreachable) {
7697+
else if (flow.flags & FlowFlags.Start) {
7698+
let container = (<FlowStart>flow).container;
7699+
if (container) {
7700+
// If container is an IIFE continue with the control flow associated with the
7701+
// call expression node.
7702+
let iife = getImmediatelyInvokedFunctionExpression(<FunctionExpression>container);
7703+
if (iife && iife.flowNode) {
7704+
flow = iife.flowNode;
7705+
continue;
7706+
}
7707+
// Check if we should continue with the control flow of the containing function.
7708+
if (includeOuterFunctions && container.flowNode) {
7709+
flow = container.flowNode;
7710+
continue;
7711+
}
7712+
}
7713+
// At the top of the flow we have the initial type.
7714+
type = initialType;
7715+
}
7716+
else {
76987717
// Unreachable code errors are reported in the binding phase. Here we
76997718
// simply return the declared type to reduce follow-on errors.
77007719
type = declaredType;
77017720
}
7702-
else {
7703-
// At the top of the flow we have the initial type.
7704-
type = initialType;
7705-
}
77067721
if (flow.flags & FlowFlags.Shared) {
77077722
// Record visited node and the associated type in the cache.
77087723
visitedFlowNodes[visitedFlowCount] = flow;
@@ -8071,6 +8086,27 @@ namespace ts {
80718086
return expression;
80728087
}
80738088

8089+
function getControlFlowContainer(node: Identifier, declarationContainer: Node, skipFunctionExpressions: boolean) {
8090+
let container = getContainingFunctionOrModule(node);
8091+
while (container !== declarationContainer &&
8092+
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) &&
8093+
(skipFunctionExpressions || getImmediatelyInvokedFunctionExpression(<FunctionExpression>container))) {
8094+
container = getContainingFunctionOrModule(container);
8095+
}
8096+
return container;
8097+
}
8098+
8099+
function isDeclarationIncludedInFlow(reference: Node, declaration: Declaration, includeOuterFunctions: boolean) {
8100+
const declarationContainer = getContainingFunctionOrModule(declaration);
8101+
let container = getContainingFunctionOrModule(reference);
8102+
while (container !== declarationContainer &&
8103+
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) &&
8104+
(includeOuterFunctions || getImmediatelyInvokedFunctionExpression(<FunctionExpression>container))) {
8105+
container = getContainingFunctionOrModule(container);
8106+
}
8107+
return container === declarationContainer;
8108+
}
8109+
80748110
function checkIdentifier(node: Identifier): Type {
80758111
const symbol = getResolvedSymbol(node);
80768112

@@ -8127,10 +8163,11 @@ namespace ts {
81278163
return type;
81288164
}
81298165
const declaration = localOrExportSymbol.valueDeclaration;
8166+
const includeOuterFunctions = isReadonlySymbol(localOrExportSymbol);
81308167
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration ||
81318168
getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
8132-
getContainingFunctionOrModule(declaration) !== getContainingFunctionOrModule(node);
8133-
const flowType = getFlowTypeOfReference(node, type, assumeInitialized);
8169+
!isDeclarationIncludedInFlow(node, declaration, includeOuterFunctions);
8170+
const flowType = getFlowTypeOfReference(node, type, assumeInitialized, includeOuterFunctions);
81348171
if (!assumeInitialized && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) {
81358172
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
81368173
// Return the declared type to reduce follow-on errors
@@ -8379,7 +8416,7 @@ namespace ts {
83798416
if (isClassLike(container.parent)) {
83808417
const symbol = getSymbolOfNode(container.parent);
83818418
const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (<InterfaceType>getDeclaredTypeOfSymbol(symbol)).thisType;
8382-
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true);
8419+
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*includeOuterFunctions*/ true);
83838420
}
83848421

83858422
if (isInJavaScriptFile(node)) {
@@ -9991,7 +10028,7 @@ namespace ts {
999110028
return propType;
999210029
}
999310030
}
9994-
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true);
10031+
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false);
999510032
}
999610033

999710034
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean {

src/compiler/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,13 @@ namespace ts {
15371537
id?: number; // Node id used by flow type cache in checker
15381538
}
15391539

1540+
// FlowStart represents the start of a control flow. For a function expression or arrow
1541+
// function, the container property references the function (which in turn has a flowNode
1542+
// property for the containing control flow).
1543+
export interface FlowStart extends FlowNode {
1544+
container?: FunctionExpression | ArrowFunction;
1545+
}
1546+
15401547
// FlowLabel represents a junction with multiple possible preceding control flows.
15411548
export interface FlowLabel extends FlowNode {
15421549
antecedents: FlowNode[];

0 commit comments

Comments
 (0)