Skip to content

Commit d8f2702

Browse files
authored
Cache control flow results across invocations (#31003)
* Modify flow loop cache key to include all inputs * Add test case, cache similarly to loop cache, reuse loop cache key (now corrected) * Use simpler singleton key and type cache for FlowAssignment nodes
1 parent 39e9a2b commit d8f2702

6 files changed

+28169
-8
lines changed

src/compiler/checker.ts

+35-8
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,8 @@ namespace ts {
559559
const symbolLinks: SymbolLinks[] = [];
560560
const nodeLinks: NodeLinks[] = [];
561561
const flowLoopCaches: Map<Type>[] = [];
562+
const flowAssignmentKeys: string[] = [];
563+
const flowAssignmentTypes: FlowType[] = [];
562564
const flowLoopNodes: FlowNode[] = [];
563565
const flowLoopKeys: string[] = [];
564566
const flowLoopTypes: Type[][] = [];
@@ -15666,21 +15668,21 @@ namespace ts {
1566615668
// The result is undefined if the reference isn't a dotted name. We prefix nodes
1566715669
// occurring in an apparent type position with '@' because the control flow type
1566815670
// of such nodes may be based on the apparent type instead of the declared type.
15669-
function getFlowCacheKey(node: Node): string | undefined {
15671+
function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined {
1567015672
switch (node.kind) {
1567115673
case SyntaxKind.Identifier:
1567215674
const symbol = getResolvedSymbol(<Identifier>node);
15673-
return symbol !== unknownSymbol ? (isConstraintPosition(node) ? "@" : "") + getSymbolId(symbol) : undefined;
15675+
return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${isConstraintPosition(node) ? "@" : ""}${getSymbolId(symbol)}` : undefined;
1567415676
case SyntaxKind.ThisKeyword:
1567515677
return "0";
1567615678
case SyntaxKind.NonNullExpression:
1567715679
case SyntaxKind.ParenthesizedExpression:
15678-
return getFlowCacheKey((<NonNullExpression | ParenthesizedExpression>node).expression);
15680+
return getFlowCacheKey((<NonNullExpression | ParenthesizedExpression>node).expression, declaredType, initialType, flowContainer);
1567915681
case SyntaxKind.PropertyAccessExpression:
1568015682
case SyntaxKind.ElementAccessExpression:
1568115683
const propName = getAccessedPropertyName(<AccessExpression>node);
1568215684
if (propName !== undefined) {
15683-
const key = getFlowCacheKey((<AccessExpression>node).expression);
15685+
const key = getFlowCacheKey((<AccessExpression>node).expression, declaredType, initialType, flowContainer);
1568415686
return key && key + "." + propName;
1568515687
}
1568615688
}
@@ -16345,6 +16347,7 @@ namespace ts {
1634516347

1634616348
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) {
1634716349
let key: string | undefined;
16350+
let keySet = false;
1634816351
let flowDepth = 0;
1634916352
if (flowAnalysisDisabled) {
1635016353
return errorType;
@@ -16365,6 +16368,14 @@ namespace ts {
1636516368
}
1636616369
return resultType;
1636716370

16371+
function getOrSetCacheKey() {
16372+
if (keySet) {
16373+
return key;
16374+
}
16375+
keySet = true;
16376+
return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer);
16377+
}
16378+
1636816379
function getTypeAtFlowNode(flow: FlowNode): FlowType {
1636916380
if (flowDepth === 2000) {
1637016381
// We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error
@@ -16376,6 +16387,15 @@ namespace ts {
1637616387
flowDepth++;
1637716388
while (true) {
1637816389
const flags = flow.flags;
16390+
if (flags & FlowFlags.Cached) {
16391+
const key = getOrSetCacheKey();
16392+
if (key) {
16393+
const id = getFlowNodeId(flow);
16394+
if (flowAssignmentKeys[id] === key) {
16395+
return flowAssignmentTypes[id];
16396+
}
16397+
}
16398+
}
1637916399
if (flags & FlowFlags.Shared) {
1638016400
// We cache results of flow type resolution for shared nodes that were previously visited in
1638116401
// the same getFlowTypeOfReference invocation. A node is considered shared when it is the
@@ -16406,6 +16426,15 @@ namespace ts {
1640616426
flow = (<FlowAssignment>flow).antecedent;
1640716427
continue;
1640816428
}
16429+
else if (flowLoopCount === flowLoopStart) { // Only cache assignments when not within loop analysis
16430+
const key = getOrSetCacheKey();
16431+
if (key && !isIncomplete(type)) {
16432+
flow.flags |= FlowFlags.Cached;
16433+
const id = getFlowNodeId(flow);
16434+
flowAssignmentKeys[id] = key;
16435+
flowAssignmentTypes[id] = type;
16436+
}
16437+
}
1640916438
}
1641016439
else if (flags & FlowFlags.Condition) {
1641116440
type = getTypeAtFlowCondition(<FlowCondition>flow);
@@ -16618,12 +16647,10 @@ namespace ts {
1661816647
// this flow loop junction, return the cached type.
1661916648
const id = getFlowNodeId(flow);
1662016649
const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap<Type>());
16650+
const key = getOrSetCacheKey();
1662116651
if (!key) {
16622-
key = getFlowCacheKey(reference);
1662316652
// No cache key is generated when binding patterns are in unnarrowable situations
16624-
if (!key) {
16625-
return declaredType;
16626-
}
16653+
return declaredType;
1662716654
}
1662816655
const cached = cache.get(key);
1662916656
if (cached) {

src/compiler/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2546,6 +2546,8 @@ namespace ts {
25462546
Shared = 1 << 10, // Referenced as antecedent more than once
25472547
PreFinally = 1 << 11, // Injected edge that links pre-finally label and pre-try flow
25482548
AfterFinally = 1 << 12, // Injected edge that links post-finally flow with the rest of the graph
2549+
/** @internal */
2550+
Cached = 1 << 13, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant
25492551
Label = BranchLabel | LoopLabel,
25502552
Condition = TrueCondition | FalseCondition
25512553
}

0 commit comments

Comments
 (0)