@@ -181,8 +181,8 @@ namespace ts {
181181
182182 let deferredNodes: Node[];
183183
184- let flowStackStart = 0;
185- let flowStackCount = 0;
184+ let flowLoopStart = 0;
185+ let flowLoopCount = 0;
186186 let visitedFlowCount = 0;
187187
188188 const tupleTypes: Map<TupleType> = {};
@@ -197,9 +197,10 @@ namespace ts {
197197 const mergedSymbols: Symbol[] = [];
198198 const symbolLinks: SymbolLinks[] = [];
199199 const nodeLinks: NodeLinks[] = [];
200- const flowTypeCaches: Map<Type>[] = [];
201- const flowStackNodes: FlowNode[] = [];
202- const flowStackCacheKeys: string[] = [];
200+ const flowLoopCaches: Map<Type>[] = [];
201+ const flowLoopNodes: FlowNode[] = [];
202+ const flowLoopKeys: string[] = [];
203+ const flowLoopTypes: Type[][] = [];
203204 const visitedFlowNodes: FlowNode[] = [];
204205 const visitedFlowTypes: Type[] = [];
205206 const potentialThisCollisions: Node[] = [];
@@ -7381,12 +7382,12 @@ namespace ts {
73817382 return false;
73827383 }
73837384
7384- function getFlowTypeCache (flow: FlowNode): Map<Type> {
7385+ function getFlowNodeId (flow: FlowNode): number {
73857386 if (!flow.id) {
73867387 flow.id = nextFlowId;
73877388 nextFlowId++;
73887389 }
7389- return flowTypeCaches[ flow.id] || (flowTypeCaches[flow.id] = {}) ;
7390+ return flow.id;
73907391 }
73917392
73927393 function typeMaybeAssignableTo(source: Type, target: Type) {
@@ -7617,7 +7618,9 @@ namespace ts {
76177618 flow = (<FlowLabel>flow).antecedents[0];
76187619 continue;
76197620 }
7620- type = getTypeAtFlowLabel(<FlowLabel>flow);
7621+ type = flow.flags & FlowFlags.BranchLabel ?
7622+ getTypeAtFlowBranchLabel(<FlowLabel>flow) :
7623+ getTypeAtFlowLoopLabel(<FlowLabel>flow);
76217624 }
76227625 else if (flow.flags & FlowFlags.Unreachable) {
76237626 // Unreachable code errors are reported in the binding phase. Here we
@@ -7669,59 +7672,75 @@ namespace ts {
76697672 }
76707673
76717674 function getTypeAtFlowCondition(flow: FlowCondition) {
7672- const type = getTypeAtFlowNode(flow.antecedent);
7673- return type && narrowType(type, flow.expression, (flow.flags & FlowFlags.TrueCondition) !== 0);
7675+ return narrowType(getTypeAtFlowNode(flow.antecedent), flow.expression, (flow.flags & FlowFlags.TrueCondition) !== 0);
76747676 }
76757677
7676- function getTypeAtFlowNodeCached(flow: FlowNode) {
7677- const cache = getFlowTypeCache(flow);
7678+ function getTypeAtFlowBranchLabel(flow: FlowLabel) {
7679+ const antecedentTypes: Type[] = [];
7680+ for (const antecedent of flow.antecedents) {
7681+ const type = getTypeAtFlowNode(antecedent);
7682+ // If the type at a particular antecedent path is the declared type and the
7683+ // reference is known to always be assigned (i.e. when declared and initial types
7684+ // are the same), there is no reason to process more antecedents since the only
7685+ // possible outcome is subtypes that will be removed in the final union type anyway.
7686+ if (type === declaredType && declaredType === initialType) {
7687+ return type;
7688+ }
7689+ if (!contains(antecedentTypes, type)) {
7690+ antecedentTypes.push(type);
7691+ }
7692+ }
7693+ return antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes);
7694+ }
7695+
7696+ function getTypeAtFlowLoopLabel(flow: FlowLabel) {
7697+ // If we have previously computed the control flow type for the reference at
7698+ // this flow loop junction, return the cached type.
7699+ const id = getFlowNodeId(flow);
7700+ const cache = flowLoopCaches[id] || (flowLoopCaches[id] = {});
76787701 if (!key) {
76797702 key = getFlowCacheKey(reference);
76807703 }
7681- const cached = cache[key];
7682- if (cached) {
7683- return cached;
7704+ if (cache[key]) {
7705+ return cache[key];
76847706 }
7685- // Return undefined if we're already processing the given node.
7686- for (let i = flowStackStart; i < flowStackCount; i++) {
7687- if (flowStackNodes[i] === flow && flowStackCacheKeys[i] === key) {
7688- return undefined;
7707+ // If this flow loop junction and reference are already being processed, return
7708+ // the union of the types computed for each branch so far. We should never see
7709+ // an empty array here because the first antecedent of a loop junction is always
7710+ // the non-looping control flow path that leads to the top.
7711+ for (let i = flowLoopStart; i < flowLoopCount; i++) {
7712+ if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key) {
7713+ return getUnionType(flowLoopTypes[i]);
76897714 }
76907715 }
7691- // Record node and key on stack of nodes being processed.
7692- flowStackNodes[flowStackCount] = flow;
7693- flowStackCacheKeys[flowStackCount] = key;
7694- flowStackCount++;
7695- const type = getTypeAtFlowNode(flow);
7696- flowStackCount--;
7697- // Record the result only if the cache is still empty. If checkExpressionCached was called
7698- // during processing it is possible we've already recorded a result.
7699- return cache[key] || type && (cache[key] = type);
7700- }
7701-
7702- function getTypeAtFlowLabel(flow: FlowLabel) {
7716+ // Add the flow loop junction and reference to the in-process stack and analyze
7717+ // each antecedent code path.
77037718 const antecedentTypes: Type[] = [];
7719+ flowLoopNodes[flowLoopCount] = flow;
7720+ flowLoopKeys[flowLoopCount] = key;
7721+ flowLoopTypes[flowLoopCount] = antecedentTypes;
77047722 for (const antecedent of flow.antecedents) {
7705- const type = flow.flags & FlowFlags.LoopLabel ?
7706- getTypeAtFlowNodeCached(antecedent) :
7707- getTypeAtFlowNode(antecedent);
7708- if (!type) {
7709- break;
7723+ flowLoopCount++;
7724+ const type = getTypeAtFlowNode(antecedent);
7725+ flowLoopCount--;
7726+ // If we see a value appear in the cache it is a sign that control flow analysis
7727+ // was restarted and completed by checkExpressionCached. We can simply pick up
7728+ // the resulting type and bail out.
7729+ if (cache[key]) {
7730+ return cache[key];
77107731 }
77117732 // If the type at a particular antecedent path is the declared type and the
77127733 // reference is known to always be assigned (i.e. when declared and initial types
77137734 // are the same), there is no reason to process more antecedents since the only
77147735 // possible outcome is subtypes that will be removed in the final union type anyway.
77157736 if (type === declaredType && declaredType === initialType) {
7716- return type;
7737+ return cache[key] = type;
77177738 }
77187739 if (!contains(antecedentTypes, type)) {
77197740 antecedentTypes.push(type);
77207741 }
77217742 }
7722- return antecedentTypes.length === 0 ? undefined :
7723- antecedentTypes.length === 1 ? antecedentTypes[0] :
7724- getUnionType(antecedentTypes);
7743+ return cache[key] = antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes);
77257744 }
77267745
77277746 function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
@@ -11223,7 +11242,7 @@ namespace ts {
1122311242 // If signature resolution originated in control flow type analysis (for example to compute the
1122411243 // assigned type in a flow assignment) we don't cache the result as it may be based on temporary
1122511244 // types from the control flow analysis.
11226- links.resolvedSignature = flowStackStart === flowStackCount ? result : cached;
11245+ links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached;
1122711246 return result;
1122811247 }
1122911248
@@ -12380,12 +12399,12 @@ namespace ts {
1238012399 const links = getNodeLinks(node);
1238112400 if (!links.resolvedType) {
1238212401 // When computing a type that we're going to cache, we need to ignore any ongoing control flow
12383- // analysis because variables may have transient types in indeterminable states. Moving flowStackStart
12402+ // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart
1238412403 // to the top of the stack ensures all transient types are computed from a known point.
12385- const saveFlowStackStart = flowStackStart ;
12386- flowStackStart = flowStackCount ;
12404+ const saveFlowLoopStart = flowLoopStart ;
12405+ flowLoopStart = flowLoopCount ;
1238712406 links.resolvedType = checkExpression(node, contextualMapper);
12388- flowStackStart = saveFlowStackStart ;
12407+ flowLoopStart = saveFlowLoopStart ;
1238912408 }
1239012409 return links.resolvedType;
1239112410 }
0 commit comments