Track return statements in IIFE using a flow label

This commit is contained in:
Anders Hejlsberg 2016-05-28 06:43:28 -07:00
parent 1647d20d90
commit e8ecf0e615
4 changed files with 89 additions and 72 deletions

View File

@ -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) {
(<FlowStart>currentFlow).container = <FunctionExpression | ArrowFunction>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) {
(<FlowStart>currentFlow).container = <FunctionExpression | ArrowFunction>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 = (<ParenthesizedExpression>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);
}
}

View File

@ -7695,20 +7695,11 @@ namespace ts {
getTypeAtFlowLoopLabel(<FlowLabel>flow);
}
else if (flow.flags & FlowFlags.Start) {
// Check if we should continue with the control flow of the containing function.
const container = (<FlowStart>flow).container;
if (container) {
// If container is an IIFE continue with the control flow associated with the
// call expression node.
const 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;
}
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,

View File

@ -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,

View File

@ -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.
*/