From 1749839330ab76c9d23d3c96ffdee3200a085a04 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 5 May 2016 11:11:51 -0700 Subject: [PATCH] Improve control flow loop analysis logic --- src/compiler/checker.ts | 121 +++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b03aa99e7d9..bc37e2524f8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -181,8 +181,8 @@ namespace ts { let deferredNodes: Node[]; - let flowStackStart = 0; - let flowStackCount = 0; + let flowLoopStart = 0; + let flowLoopCount = 0; let visitedFlowCount = 0; const tupleTypes: Map = {}; @@ -197,9 +197,10 @@ namespace ts { const mergedSymbols: Symbol[] = []; const symbolLinks: SymbolLinks[] = []; const nodeLinks: NodeLinks[] = []; - const flowTypeCaches: Map[] = []; - const flowStackNodes: FlowNode[] = []; - const flowStackCacheKeys: string[] = []; + const flowLoopCaches: Map[] = []; + const flowLoopNodes: FlowNode[] = []; + const flowLoopKeys: string[] = []; + const flowLoopTypes: Type[][] = []; const visitedFlowNodes: FlowNode[] = []; const visitedFlowTypes: Type[] = []; const potentialThisCollisions: Node[] = []; @@ -7381,12 +7382,12 @@ namespace ts { return false; } - function getFlowTypeCache(flow: FlowNode): Map { + function getFlowNodeId(flow: FlowNode): number { if (!flow.id) { flow.id = nextFlowId; nextFlowId++; } - return flowTypeCaches[flow.id] || (flowTypeCaches[flow.id] = {}); + return flow.id; } function typeMaybeAssignableTo(source: Type, target: Type) { @@ -7617,7 +7618,9 @@ namespace ts { flow = (flow).antecedents[0]; continue; } - type = getTypeAtFlowLabel(flow); + type = flow.flags & FlowFlags.BranchLabel ? + getTypeAtFlowBranchLabel(flow) : + getTypeAtFlowLoopLabel(flow); } else if (flow.flags & FlowFlags.Unreachable) { // Unreachable code errors are reported in the binding phase. Here we @@ -7669,45 +7672,13 @@ namespace ts { } function getTypeAtFlowCondition(flow: FlowCondition) { - const type = getTypeAtFlowNode(flow.antecedent); - return type && narrowType(type, flow.expression, (flow.flags & FlowFlags.TrueCondition) !== 0); + return narrowType(getTypeAtFlowNode(flow.antecedent), flow.expression, (flow.flags & FlowFlags.TrueCondition) !== 0); } - function getTypeAtFlowNodeCached(flow: FlowNode) { - const cache = getFlowTypeCache(flow); - if (!key) { - key = getFlowCacheKey(reference); - } - const cached = cache[key]; - if (cached) { - return cached; - } - // Return undefined if we're already processing the given node. - for (let i = flowStackStart; i < flowStackCount; i++) { - if (flowStackNodes[i] === flow && flowStackCacheKeys[i] === key) { - return undefined; - } - } - // Record node and key on stack of nodes being processed. - flowStackNodes[flowStackCount] = flow; - flowStackCacheKeys[flowStackCount] = key; - flowStackCount++; - const type = getTypeAtFlowNode(flow); - flowStackCount--; - // Record the result only if the cache is still empty. If checkExpressionCached was called - // during processing it is possible we've already recorded a result. - return cache[key] || type && (cache[key] = type); - } - - function getTypeAtFlowLabel(flow: FlowLabel) { + function getTypeAtFlowBranchLabel(flow: FlowLabel) { const antecedentTypes: Type[] = []; for (const antecedent of flow.antecedents) { - const type = flow.flags & FlowFlags.LoopLabel ? - getTypeAtFlowNodeCached(antecedent) : - getTypeAtFlowNode(antecedent); - if (!type) { - break; - } + const type = getTypeAtFlowNode(antecedent); // If the type at a particular antecedent path is the declared type and the // reference is known to always be assigned (i.e. when declared and initial types // are the same), there is no reason to process more antecedents since the only @@ -7719,9 +7690,57 @@ namespace ts { antecedentTypes.push(type); } } - return antecedentTypes.length === 0 ? undefined : - antecedentTypes.length === 1 ? antecedentTypes[0] : - getUnionType(antecedentTypes); + return antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes); + } + + function getTypeAtFlowLoopLabel(flow: FlowLabel) { + // If we have previously computed the control flow type for the reference at + // this flow loop junction, return the cached type. + const id = getFlowNodeId(flow); + const cache = flowLoopCaches[id] || (flowLoopCaches[id] = {}); + if (!key) { + key = getFlowCacheKey(reference); + } + if (cache[key]) { + return cache[key]; + } + // If this flow loop junction and reference are already being processed, return + // the union of the types computed for each branch so far. We should never see + // an empty array here because the first antecedent of a loop junction is always + // the non-looping control flow path that leads to the top. + for (let i = flowLoopStart; i < flowLoopCount; i++) { + if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key) { + return getUnionType(flowLoopTypes[i]); + } + } + // Add the flow loop junction and reference to the in-process stack and analyze + // each antecedent code path. + const antecedentTypes: Type[] = []; + flowLoopNodes[flowLoopCount] = flow; + flowLoopKeys[flowLoopCount] = key; + flowLoopTypes[flowLoopCount] = antecedentTypes; + for (const antecedent of flow.antecedents) { + flowLoopCount++; + const type = getTypeAtFlowNode(antecedent); + flowLoopCount--; + // If we see a value appear in the cache it is a sign that control flow analysis + // was restarted and completed by checkExpressionCached. We can simply pick up + // the resulting type and bail out. + if (cache[key]) { + return cache[key]; + } + // If the type at a particular antecedent path is the declared type and the + // reference is known to always be assigned (i.e. when declared and initial types + // are the same), there is no reason to process more antecedents since the only + // possible outcome is subtypes that will be removed in the final union type anyway. + if (type === declaredType && declaredType === initialType) { + return cache[key] = type; + } + if (!contains(antecedentTypes, type)) { + antecedentTypes.push(type); + } + } + return cache[key] = antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes); } function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { @@ -11223,7 +11242,7 @@ namespace ts { // If signature resolution originated in control flow type analysis (for example to compute the // assigned type in a flow assignment) we don't cache the result as it may be based on temporary // types from the control flow analysis. - links.resolvedSignature = flowStackStart === flowStackCount ? result : cached; + links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached; return result; } @@ -12380,12 +12399,12 @@ namespace ts { const links = getNodeLinks(node); if (!links.resolvedType) { // When computing a type that we're going to cache, we need to ignore any ongoing control flow - // analysis because variables may have transient types in indeterminable states. Moving flowStackStart + // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart // to the top of the stack ensures all transient types are computed from a known point. - const saveFlowStackStart = flowStackStart; - flowStackStart = flowStackCount; + const saveFlowLoopStart = flowLoopStart; + flowLoopStart = flowLoopCount; links.resolvedType = checkExpression(node, contextualMapper); - flowStackStart = saveFlowStackStart; + flowLoopStart = saveFlowLoopStart; } return links.resolvedType; }