diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 2476709b501..93417493fa7 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -104,14 +104,15 @@ namespace ts { let seenThisKeyword: boolean; // state used by reachability checks - let hasExplicitReturn: boolean; let currentFlow: FlowNode; let currentBreakTarget: FlowLabel; let currentContinueTarget: FlowLabel; + let currentReturnTarget: FlowLabel; let currentTrueTarget: FlowLabel; let currentFalseTarget: FlowLabel; let preSwitchCaseFlow: FlowNode; let activeLabels: ActiveLabel[]; + let hasExplicitReturn: boolean; // state used for emit helpers let hasClassExtends: boolean; @@ -156,13 +157,14 @@ namespace ts { blockScopeContainer = undefined; lastContainer = undefined; seenThisKeyword = false; - hasExplicitReturn = false; currentFlow = undefined; currentBreakTarget = undefined; currentContinueTarget = undefined; + currentReturnTarget = undefined; currentTrueTarget = undefined; currentFalseTarget = undefined; activeLabels = undefined; + hasExplicitReturn = false; hasClassExtends = false; hasAsyncFunctions = false; hasDecorators = false; @@ -443,20 +445,20 @@ namespace ts { blockScopeContainer.locals = undefined; } - let savedHasExplicitReturn: boolean; - let savedCurrentFlow: FlowNode; - let savedBreakTarget: FlowLabel; - let savedContinueTarget: FlowLabel; - let savedActiveLabels: ActiveLabel[]; + let saveCurrentFlow: FlowNode; + let saveBreakTarget: FlowLabel; + let saveContinueTarget: FlowLabel; + let saveReturnTarget: FlowLabel; + let saveActiveLabels: ActiveLabel[]; + let saveHasExplicitReturn: boolean; + let isIIFE: boolean; const kind = node.kind; let flags = node.flags; - // reset all reachability check related flags on node (for incremental scenarios) - flags &= ~NodeFlags.ReachabilityCheckFlags; - - // reset all emit helper flags on node (for incremental scenarios) - flags &= ~NodeFlags.EmitHelperFlags; + // Reset all reachability check related flags on node (for incremental scenarios) + // Reset all emit helper flags on node (for incremental scenarios) + flags &= ~NodeFlags.ReachabilityAndEmitFlags; if (kind === SyntaxKind.InterfaceDeclaration) { seenThisKeyword = false; @@ -464,23 +466,30 @@ namespace ts { const saveState = kind === SyntaxKind.SourceFile || kind === SyntaxKind.ModuleBlock || isFunctionLikeKind(kind); if (saveState) { - savedHasExplicitReturn = hasExplicitReturn; - savedCurrentFlow = currentFlow; - savedBreakTarget = currentBreakTarget; - savedContinueTarget = currentContinueTarget; - savedActiveLabels = activeLabels; - - hasExplicitReturn = false; - currentFlow = { flags: FlowFlags.Start }; - if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { - (currentFlow).container = node; + saveCurrentFlow = currentFlow; + saveBreakTarget = currentBreakTarget; + saveContinueTarget = currentContinueTarget; + saveReturnTarget = currentReturnTarget; + saveActiveLabels = activeLabels; + saveHasExplicitReturn = hasExplicitReturn; + isIIFE = (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) && !!getImmediatelyInvokedFunctionExpression(node); + if (isIIFE) { + currentReturnTarget = createBranchLabel(); + } + else { + currentFlow = { flags: FlowFlags.Start }; + if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { + (currentFlow).container = node; + } + currentReturnTarget = undefined; } currentBreakTarget = undefined; currentContinueTarget = undefined; activeLabels = undefined; + hasExplicitReturn = false; } - if (isInJavaScriptFile(node) && node.jsDocComment) { + if (node.flags & NodeFlags.JavaScriptFile && node.jsDocComment) { bind(node.jsDocComment); } @@ -518,11 +527,18 @@ namespace ts { node.flags = flags; if (saveState) { - hasExplicitReturn = savedHasExplicitReturn; - currentFlow = savedCurrentFlow; - currentBreakTarget = savedBreakTarget; - currentContinueTarget = savedContinueTarget; - activeLabels = savedActiveLabels; + if (isIIFE) { + addAntecedent(currentReturnTarget, currentFlow); + currentFlow = finishFlowLabel(currentReturnTarget); + } + else { + currentFlow = saveCurrentFlow; + } + currentBreakTarget = saveBreakTarget; + currentContinueTarget = saveContinueTarget; + currentReturnTarget = saveReturnTarget; + activeLabels = saveActiveLabels; + hasExplicitReturn = saveHasExplicitReturn; } container = saveContainer; @@ -854,6 +870,9 @@ namespace ts { bind(node.expression); if (node.kind === SyntaxKind.ReturnStatement) { hasExplicitReturn = true; + if (currentReturnTarget) { + addAntecedent(currentReturnTarget, currentFlow); + } } currentFlow = unreachableFlow; } @@ -1105,7 +1124,6 @@ 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). @@ -1114,7 +1132,12 @@ namespace ts { expr = (expr).expression; } if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { - node.flowNode = currentFlow; + forEach(node.typeArguments, bind); + forEach(node.arguments, bind); + bind(node.expression); + } + else { + forEachChild(node, bind); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 58aaae9f72b..037ab78413a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7695,20 +7695,11 @@ namespace ts { getTypeAtFlowLoopLabel(flow); } else if (flow.flags & FlowFlags.Start) { + // Check if we should continue with the control flow of the containing function. const container = (flow).container; - if (container) { - // If container is an IIFE continue with the control flow associated with the - // call expression node. - const 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; - } + if (container && includeOuterFunctions) { + flow = container.flowNode; + continue; } // At the top of the flow we have the initial type. type = initialType; @@ -8652,23 +8643,25 @@ namespace ts { function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type { const func = parameter.parent; if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { - const iife = getImmediatelyInvokedFunctionExpression(func); - if (iife) { - const indexOfParameter = indexOf(func.parameters, parameter); - if (iife.arguments && indexOfParameter < iife.arguments.length) { - if (parameter.dotDotDotToken) { - const restTypes: Type[] = []; - for (let i = indexOfParameter; i < iife.arguments.length; i++) { - restTypes.push(getTypeOfExpression(iife.arguments[i])); + if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) { + const iife = getImmediatelyInvokedFunctionExpression(func); + if (iife) { + const indexOfParameter = indexOf(func.parameters, parameter); + if (iife.arguments && indexOfParameter < iife.arguments.length) { + if (parameter.dotDotDotToken) { + const restTypes: Type[] = []; + for (let i = indexOfParameter; i < iife.arguments.length; i++) { + restTypes.push(getTypeOfExpression(iife.arguments[i])); + } + return createArrayType(getUnionType(restTypes)); } - return createArrayType(getUnionType(restTypes)); + const links = getNodeLinks(iife); + const cached = links.resolvedSignature; + links.resolvedSignature = anySignature; + const type = checkExpression(iife.arguments[indexOfParameter]); + links.resolvedSignature = cached; + return type; } - const links = getNodeLinks(iife); - const cached = links.resolvedSignature; - links.resolvedSignature = anySignature; - const type = checkExpression(iife.arguments[indexOfParameter]); - links.resolvedSignature = cached; - return type; } } const contextualSignature = getContextualSignature(func); @@ -8691,20 +8684,6 @@ namespace ts { return undefined; } - function getImmediatelyInvokedFunctionExpression(func: FunctionExpression | MethodDeclaration) { - if (isFunctionExpressionOrArrowFunction(func)) { - let prev: Node = func; - let parent: Node = func.parent; - while (parent.kind === SyntaxKind.ParenthesizedExpression) { - prev = parent; - parent = parent.parent; - } - if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) { - return parent as CallExpression; - } - } - } - // In a variable, parameter or property declaration with a type annotation, // the contextual type of an initializer expression is the type of the variable, parameter or property. // Otherwise, in a parameter declaration of a contextually typed function expression, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 762d710d504..4e55956fa7c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -416,6 +416,7 @@ namespace ts { ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn, EmitHelperFlags = HasClassExtends | HasDecorators | HasParamDecorators | HasAsyncFunctions, + ReachabilityAndEmitFlags = ReachabilityCheckFlags | EmitHelperFlags, // Parsing context flags ContextFlags = DisallowInContext | YieldContext | DecoratorContext | AwaitContext | JavaScriptFile, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7969389d788..500fa722dda 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -987,6 +987,20 @@ namespace ts { } } + export function getImmediatelyInvokedFunctionExpression(func: Node): CallExpression { + if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) { + let prev = func; + let parent = func.parent; + while (parent.kind === SyntaxKind.ParenthesizedExpression) { + prev = parent; + parent = parent.parent; + } + if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) { + return parent as CallExpression; + } + } + } + /** * Determines whether a node is a property or element access expression for super. */