From 3b5c72c4bc033f5bf4d38931b2eed0bf0bf7e5e9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 26 May 2016 15:30:31 -0700 Subject: [PATCH] Include outer function expressions in control flow analysis --- src/compiler/binder.ts | 24 ++++++++++++++++- src/compiler/checker.ts | 57 +++++++++++++++++++++++++++++++++-------- src/compiler/types.ts | 7 +++++ 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a7cb0d12758..2476709b501 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -472,6 +472,9 @@ namespace ts { hasExplicitReturn = false; currentFlow = { flags: FlowFlags.Start }; + if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { + (currentFlow).container = node; + } currentBreakTarget = undefined; currentContinueTarget = undefined; activeLabels = undefined; @@ -589,6 +592,9 @@ namespace ts { case SyntaxKind.VariableDeclaration: bindVariableDeclarationFlow(node); break; + case SyntaxKind.CallExpression: + bindCallExpressionFlow(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 = (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(node); const bindingName = (node).name ? (node).name.text : "__function"; return bindAnonymousDeclaration(node, SymbolFlags.Function, bindingName); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5a7c6fa29e5..c14a7e26add 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7648,7 +7648,7 @@ namespace ts { getInitialTypeOfBindingElement(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(flow) : getTypeAtFlowLoopLabel(flow); } - else if (flow.flags & FlowFlags.Unreachable) { + else if (flow.flags & FlowFlags.Start) { + let container = (flow).container; + if (container) { + // If container is an IIFE continue with the control flow associated with the + // call expression node. + let iife = getImmediatelyInvokedFunctionExpression(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(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(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) : (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 { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7613c489920..762d710d504 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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[];