Skip to content

Commit c26e920

Browse files
committed
Merge pull request microsoft#8480 from Microsoft/controlFlowLoopLogic
Improve control flow loop analysis logic
2 parents 673fa41 + 1749839 commit c26e920

1 file changed

Lines changed: 64 additions & 45 deletions

File tree

src/compiler/checker.ts

Lines changed: 64 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)