Include outer function expressions in control flow analysis

This commit is contained in:
Anders Hejlsberg
2016-05-26 15:30:31 -07:00
parent df4ab083f4
commit 3b5c72c4bc
3 changed files with 77 additions and 11 deletions

View File

@@ -472,6 +472,9 @@ namespace ts {
hasExplicitReturn = false;
currentFlow = { flags: FlowFlags.Start };
if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) {
(<FlowStart>currentFlow).container = <FunctionExpression | ArrowFunction>node;
}
currentBreakTarget = undefined;
currentContinueTarget = undefined;
activeLabels = undefined;
@@ -589,6 +592,9 @@ namespace ts {
case SyntaxKind.VariableDeclaration:
bindVariableDeclarationFlow(<VariableDeclaration>node);
break;
case SyntaxKind.CallExpression:
bindCallExpressionFlow(<CallExpression>node);
break;
default:
forEachChild(node, bind);
break;
@@ -1098,6 +1104,20 @@ namespace ts {
}
}
function bindCallExpressionFlow(node: CallExpression) {
forEachChild(node, bind);
// If the target of the call expression is a function expression or arrow function we have
// an immediately invoked function expression (IIFE). Initialize the flowNode property to
// the current control flow (which includes evaluation of the IIFE arguments).
let expr: Expression = node.expression;
while (expr.kind === SyntaxKind.ParenthesizedExpression) {
expr = (<ParenthesizedExpression>expr).expression;
}
if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) {
node.flowNode = currentFlow;
}
}
function getContainerFlags(node: Node): ContainerFlags {
switch (node.kind) {
case SyntaxKind.ClassExpression:
@@ -2054,7 +2074,9 @@ namespace ts {
hasAsyncFunctions = true;
}
}
if (currentFlow) {
node.flowNode = currentFlow;
}
checkStrictModeFunctionName(<FunctionExpression>node);
const bindingName = (<FunctionExpression>node).name ? (<FunctionExpression>node).name.text : "__function";
return bindAnonymousDeclaration(<FunctionExpression>node, SymbolFlags.Function, bindingName);

View File

@@ -7648,7 +7648,7 @@ namespace ts {
getInitialTypeOfBindingElement(<BindingElement>node);
}
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean) {
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
let key: string;
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
return declaredType;
@@ -7694,15 +7694,30 @@ namespace ts {
getTypeAtFlowBranchLabel(<FlowLabel>flow) :
getTypeAtFlowLoopLabel(<FlowLabel>flow);
}
else if (flow.flags & FlowFlags.Unreachable) {
else if (flow.flags & FlowFlags.Start) {
let container = (<FlowStart>flow).container;
if (container) {
// If container is an IIFE continue with the control flow associated with the
// call expression node.
let iife = getImmediatelyInvokedFunctionExpression(<FunctionExpression>container);
if (iife && iife.flowNode) {
flow = iife.flowNode;
continue;
}
// Check if we should continue with the control flow of the containing function.
if (includeOuterFunctions && container.flowNode) {
flow = container.flowNode;
continue;
}
}
// At the top of the flow we have the initial type.
type = initialType;
}
else {
// Unreachable code errors are reported in the binding phase. Here we
// simply return the declared type to reduce follow-on errors.
type = declaredType;
}
else {
// At the top of the flow we have the initial type.
type = initialType;
}
if (flow.flags & FlowFlags.Shared) {
// Record visited node and the associated type in the cache.
visitedFlowNodes[visitedFlowCount] = flow;
@@ -8071,6 +8086,27 @@ namespace ts {
return expression;
}
function getControlFlowContainer(node: Identifier, declarationContainer: Node, skipFunctionExpressions: boolean) {
let container = getContainingFunctionOrModule(node);
while (container !== declarationContainer &&
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) &&
(skipFunctionExpressions || getImmediatelyInvokedFunctionExpression(<FunctionExpression>container))) {
container = getContainingFunctionOrModule(container);
}
return container;
}
function isDeclarationIncludedInFlow(reference: Node, declaration: Declaration, includeOuterFunctions: boolean) {
const declarationContainer = getContainingFunctionOrModule(declaration);
let container = getContainingFunctionOrModule(reference);
while (container !== declarationContainer &&
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) &&
(includeOuterFunctions || getImmediatelyInvokedFunctionExpression(<FunctionExpression>container))) {
container = getContainingFunctionOrModule(container);
}
return container === declarationContainer;
}
function checkIdentifier(node: Identifier): Type {
const symbol = getResolvedSymbol(node);
@@ -8127,10 +8163,11 @@ namespace ts {
return type;
}
const declaration = localOrExportSymbol.valueDeclaration;
const includeOuterFunctions = isReadonlySymbol(localOrExportSymbol);
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration ||
getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
getContainingFunctionOrModule(declaration) !== getContainingFunctionOrModule(node);
const flowType = getFlowTypeOfReference(node, type, assumeInitialized);
!isDeclarationIncludedInFlow(node, declaration, includeOuterFunctions);
const flowType = getFlowTypeOfReference(node, type, assumeInitialized, includeOuterFunctions);
if (!assumeInitialized && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) {
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
// Return the declared type to reduce follow-on errors
@@ -8379,7 +8416,7 @@ namespace ts {
if (isClassLike(container.parent)) {
const symbol = getSymbolOfNode(container.parent);
const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (<InterfaceType>getDeclaredTypeOfSymbol(symbol)).thisType;
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true);
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*includeOuterFunctions*/ true);
}
if (isInJavaScriptFile(node)) {
@@ -9991,7 +10028,7 @@ namespace ts {
return propType;
}
}
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true);
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false);
}
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean {

View File

@@ -1537,6 +1537,13 @@ namespace ts {
id?: number; // Node id used by flow type cache in checker
}
// FlowStart represents the start of a control flow. For a function expression or arrow
// function, the container property references the function (which in turn has a flowNode
// property for the containing control flow).
export interface FlowStart extends FlowNode {
container?: FunctionExpression | ArrowFunction;
}
// FlowLabel represents a junction with multiple possible preceding control flows.
export interface FlowLabel extends FlowNode {
antecedents: FlowNode[];