Skip to content

Commit 10e3f11

Browse files
authored
Merge pull request microsoft#33510 from microsoft/controlFlowLoopCaching
Enable caching for control flow paths that precede loops
2 parents ee6f00d + 007b4b1 commit 10e3f11

File tree

1 file changed

+47
-42
lines changed

1 file changed

+47
-42
lines changed

src/compiler/checker.ts

+47-42
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,7 @@ namespace ts {
819819
let flowLoopCount = 0;
820820
let sharedFlowCount = 0;
821821
let flowAnalysisDisabled = false;
822+
let flowInvocationCount = 0;
822823

823824
const emptyStringType = getLiteralType("");
824825
const zeroType = getLiteralType(0);
@@ -834,8 +835,7 @@ namespace ts {
834835
const symbolLinks: SymbolLinks[] = [];
835836
const nodeLinks: NodeLinks[] = [];
836837
const flowLoopCaches: Map<Type>[] = [];
837-
const flowAssignmentKeys: string[] = [];
838-
const flowAssignmentTypes: FlowType[] = [];
838+
const flowAssignmentTypes: Type[] = [];
839839
const flowLoopNodes: FlowNode[] = [];
840840
const flowLoopKeys: string[] = [];
841841
const flowLoopTypes: Type[][] = [];
@@ -16698,12 +16698,6 @@ namespace ts {
1669816698
getInitialTypeOfBindingElement(node);
1669916699
}
1670016700

16701-
function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression, reference: Node) {
16702-
return getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
16703-
getInitialType(<VariableDeclaration | BindingElement>node) :
16704-
getAssignedType(node), reference);
16705-
}
16706-
1670716701
function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) {
1670816702
return node.kind === SyntaxKind.VariableDeclaration && (<VariableDeclaration>node).initializer &&
1670916703
isEmptyArrayLiteral((<VariableDeclaration>node).initializer!) ||
@@ -16990,6 +16984,7 @@ namespace ts {
1699016984
if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
1699116985
return declaredType;
1699216986
}
16987+
flowInvocationCount++;
1699316988
const sharedFlowStart = sharedFlowCount;
1699416989
const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
1699516990
sharedFlowCount = sharedFlowStart;
@@ -17022,15 +17017,6 @@ namespace ts {
1702217017
flowDepth++;
1702317018
while (true) {
1702417019
const flags = flow.flags;
17025-
if (flags & FlowFlags.Cached) {
17026-
const key = getOrSetCacheKey();
17027-
if (key) {
17028-
const id = getFlowNodeId(flow);
17029-
if (flowAssignmentKeys[id] === key) {
17030-
return flowAssignmentTypes[id];
17031-
}
17032-
}
17033-
}
1703417020
if (flags & FlowFlags.Shared) {
1703517021
// We cache results of flow type resolution for shared nodes that were previously visited in
1703617022
// the same getFlowTypeOfReference invocation. A node is considered shared when it is the
@@ -17061,15 +17047,6 @@ namespace ts {
1706117047
flow = (<FlowAssignment>flow).antecedent;
1706217048
continue;
1706317049
}
17064-
else if (flowLoopCount === flowLoopStart) { // Only cache assignments when not within loop analysis
17065-
const key = getOrSetCacheKey();
17066-
if (key && !isIncomplete(type)) {
17067-
flow.flags |= FlowFlags.Cached;
17068-
const id = getFlowNodeId(flow);
17069-
flowAssignmentKeys[id] = key;
17070-
flowAssignmentTypes[id] = type;
17071-
}
17072-
}
1707317050
}
1707417051
else if (flags & FlowFlags.Condition) {
1707517052
type = getTypeAtFlowCondition(<FlowCondition>flow);
@@ -17122,6 +17099,27 @@ namespace ts {
1712217099
}
1712317100
}
1712417101

17102+
function getInitialOrAssignedType(flow: FlowAssignment) {
17103+
const node = flow.node;
17104+
if (flow.flags & FlowFlags.Cached) {
17105+
const cached = flowAssignmentTypes[getNodeId(node)];
17106+
if (cached) {
17107+
return cached;
17108+
}
17109+
}
17110+
const startInvocationCount = flowInvocationCount;
17111+
const type = getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
17112+
getInitialType(<VariableDeclaration | BindingElement>node) :
17113+
getAssignedType(node), reference);
17114+
// We cache the assigned type when getFlowTypeOfReference was recursively invoked in the
17115+
// resolution of the assigned type and we're not within loop analysis.
17116+
if (flowInvocationCount !== startInvocationCount && flowLoopCount === flowLoopStart) {
17117+
flow.flags |= FlowFlags.Cached;
17118+
flowAssignmentTypes[getNodeId(node)] = type;
17119+
}
17120+
return type;
17121+
}
17122+
1712517123
function getTypeAtFlowAssignment(flow: FlowAssignment) {
1712617124
const node = flow.node;
1712717125
// Assignments only narrow the computed type if the declared type is a union type. Thus, we
@@ -17135,11 +17133,11 @@ namespace ts {
1713517133
if (isEmptyArrayAssignment(node)) {
1713617134
return getEvolvingArrayType(neverType);
1713717135
}
17138-
const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(node, reference));
17136+
const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(flow));
1713917137
return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType;
1714017138
}
1714117139
if (declaredType.flags & TypeFlags.Union) {
17142-
return getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(node, reference));
17140+
return getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(flow));
1714317141
}
1714417142
return declaredType;
1714517143
}
@@ -17309,24 +17307,31 @@ namespace ts {
1730917307
const antecedentTypes: Type[] = [];
1731017308
let subtypeReduction = false;
1731117309
let firstAntecedentType: FlowType | undefined;
17312-
flowLoopNodes[flowLoopCount] = flow;
17313-
flowLoopKeys[flowLoopCount] = key;
17314-
flowLoopTypes[flowLoopCount] = antecedentTypes;
1731517310
for (const antecedent of flow.antecedents!) {
17316-
flowLoopCount++;
17317-
const flowType = getTypeAtFlowNode(antecedent);
17318-
flowLoopCount--;
17311+
let flowType;
1731917312
if (!firstAntecedentType) {
17320-
firstAntecedentType = flowType;
17313+
// The first antecedent of a loop junction is always the non-looping control
17314+
// flow path that leads to the top.
17315+
flowType = firstAntecedentType = getTypeAtFlowNode(antecedent);
1732117316
}
17322-
const type = getTypeFromFlowType(flowType);
17323-
// If we see a value appear in the cache it is a sign that control flow analysis
17324-
// was restarted and completed by checkExpressionCached. We can simply pick up
17325-
// the resulting type and bail out.
17326-
const cached = cache.get(key);
17327-
if (cached) {
17328-
return cached;
17317+
else {
17318+
// All but the first antecedent are the looping control flow paths that lead
17319+
// back to the loop junction. We track these on the flow loop stack.
17320+
flowLoopNodes[flowLoopCount] = flow;
17321+
flowLoopKeys[flowLoopCount] = key;
17322+
flowLoopTypes[flowLoopCount] = antecedentTypes;
17323+
flowLoopCount++;
17324+
flowType = getTypeAtFlowNode(antecedent);
17325+
flowLoopCount--;
17326+
// If we see a value appear in the cache it is a sign that control flow analysis
17327+
// was restarted and completed by checkExpressionCached. We can simply pick up
17328+
// the resulting type and bail out.
17329+
const cached = cache.get(key);
17330+
if (cached) {
17331+
return cached;
17332+
}
1732917333
}
17334+
const type = getTypeFromFlowType(flowType);
1733017335
pushIfUnique(antecedentTypes, type);
1733117336
// If an antecedent type is not a subset of the declared type, we need to perform
1733217337
// subtype reduction. This happens when a "foreign" type is injected into the control

0 commit comments

Comments
 (0)