Skip to content

Commit ea4e6b3

Browse files
authored
Improve control flow type caching (#35332)
* Cache getTypeOfExpression results when CFA was invoked * Add tests * Accept new baselines
1 parent 0c2c58c commit ea4e6b3

6 files changed

+1572
-26
lines changed

src/compiler/checker.ts

+39-24
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,7 @@ namespace ts {
829829
let flowInvocationCount = 0;
830830
let lastFlowNode: FlowNode | undefined;
831831
let lastFlowNodeReachable: boolean;
832+
let flowTypeCache: Type[] | undefined;
832833

833834
const emptyStringType = getLiteralType("");
834835
const zeroType = getLiteralType(0);
@@ -844,7 +845,6 @@ namespace ts {
844845
const symbolLinks: SymbolLinks[] = [];
845846
const nodeLinks: NodeLinks[] = [];
846847
const flowLoopCaches: Map<Type>[] = [];
847-
const flowAssignmentTypes: Type[] = [];
848848
const flowLoopNodes: FlowNode[] = [];
849849
const flowLoopKeys: string[] = [];
850850
const flowLoopTypes: Type[][] = [];
@@ -19173,23 +19173,9 @@ namespace ts {
1917319173

1917419174
function getInitialOrAssignedType(flow: FlowAssignment) {
1917519175
const node = flow.node;
19176-
if (flow.flags & FlowFlags.Cached) {
19177-
const cached = flowAssignmentTypes[getNodeId(node)];
19178-
if (cached) {
19179-
return cached;
19180-
}
19181-
}
19182-
const startInvocationCount = flowInvocationCount;
19183-
const type = getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
19176+
return getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
1918419177
getInitialType(<VariableDeclaration | BindingElement>node) :
1918519178
getAssignedType(node), reference);
19186-
// We cache the assigned type when getFlowTypeOfReference was recursively invoked in the
19187-
// resolution of the assigned type and we're not within loop analysis.
19188-
if (flowInvocationCount !== startInvocationCount && flowLoopCount === flowLoopStart) {
19189-
flow.flags |= FlowFlags.Cached;
19190-
flowAssignmentTypes[getNodeId(node)] = type;
19191-
}
19192-
return type;
1919319179
}
1919419180

1919519181
function getTypeAtFlowAssignment(flow: FlowAssignment) {
@@ -19469,7 +19455,10 @@ namespace ts {
1946919455
flowLoopKeys[flowLoopCount] = key;
1947019456
flowLoopTypes[flowLoopCount] = antecedentTypes;
1947119457
flowLoopCount++;
19458+
const saveFlowTypeCache = flowTypeCache;
19459+
flowTypeCache = undefined;
1947219460
flowType = getTypeAtFlowNode(antecedent);
19461+
flowTypeCache = saveFlowTypeCache;
1947319462
flowLoopCount--;
1947419463
// If we see a value appear in the cache it is a sign that control flow analysis
1947519464
// was restarted and completed by checkExpressionCached. We can simply pick up
@@ -27322,8 +27311,11 @@ namespace ts {
2732227311
// analysis because variables may have transient types in indeterminable states. Moving flowLoopStart
2732327312
// to the top of the stack ensures all transient types are computed from a known point.
2732427313
const saveFlowLoopStart = flowLoopStart;
27314+
const saveFlowTypeCache = flowTypeCache;
2732527315
flowLoopStart = flowLoopCount;
27316+
flowTypeCache = undefined;
2732627317
links.resolvedType = checkExpression(node, checkMode);
27318+
flowTypeCache = saveFlowTypeCache;
2732727319
flowLoopStart = saveFlowLoopStart;
2732827320
}
2732927321
return links.resolvedType;
@@ -27336,7 +27328,7 @@ namespace ts {
2733627328

2733727329
function checkDeclarationInitializer(declaration: HasExpressionInitializer) {
2733827330
const initializer = getEffectiveInitializer(declaration)!;
27339-
const type = getTypeOfExpression(initializer, /*cache*/ true);
27331+
const type = getQuickTypeOfExpression(initializer) || checkExpressionCached(initializer);
2734027332
const padded = isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
2734127333
isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ?
2734227334
padTupleType(type, declaration.name) : type;
@@ -27590,10 +27582,32 @@ namespace ts {
2759027582
/**
2759127583
* Returns the type of an expression. Unlike checkExpression, this function is simply concerned
2759227584
* with computing the type and may not fully check all contained sub-expressions for errors.
27593-
* A cache argument of true indicates that if the function performs a full type check, it is ok
27594-
* to cache the result.
2759527585
*/
27596-
function getTypeOfExpression(node: Expression, cache?: boolean) {
27586+
function getTypeOfExpression(node: Expression) {
27587+
// Don't bother caching types that require no flow analysis and are quick to compute.
27588+
const quickType = getQuickTypeOfExpression(node);
27589+
if (quickType) {
27590+
return quickType;
27591+
}
27592+
// If a type has been cached for the node, return it.
27593+
if (node.flags & NodeFlags.TypeCached && flowTypeCache) {
27594+
const cachedType = flowTypeCache[getNodeId(node)];
27595+
if (cachedType) {
27596+
return cachedType;
27597+
}
27598+
}
27599+
const startInvocationCount = flowInvocationCount;
27600+
const type = checkExpression(node);
27601+
// If control flow analysis was required to determine the type, it is worth caching.
27602+
if (flowInvocationCount !== startInvocationCount) {
27603+
const cache = flowTypeCache || (flowTypeCache = []);
27604+
cache[getNodeId(node)] = type;
27605+
node.flags |= NodeFlags.TypeCached;
27606+
}
27607+
return type;
27608+
}
27609+
27610+
function getQuickTypeOfExpression(node: Expression) {
2759727611
const expr = skipParentheses(node);
2759827612
// Optimize for the common case of a call to a function with a single non-generic call
2759927613
// signature where we can just fetch the return type without checking the arguments.
@@ -27607,10 +27621,11 @@ namespace ts {
2760727621
else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) {
2760827622
return getTypeFromTypeNode((<TypeAssertion>expr).type);
2760927623
}
27610-
// Otherwise simply call checkExpression. Ideally, the entire family of checkXXX functions
27611-
// should have a parameter that indicates whether full error checking is required such that
27612-
// we can perform the optimizations locally.
27613-
return cache ? checkExpressionCached(node) : checkExpression(node);
27624+
else if (node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral ||
27625+
node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword) {
27626+
return checkExpression(node);
27627+
}
27628+
return undefined;
2761427629
}
2761527630

2761627631
/**

src/compiler/types.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,7 @@ namespace ts {
567567
/* @internal */ Ambient = 1 << 23, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier.
568568
/* @internal */ InWithStatement = 1 << 24, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`)
569569
JsonFile = 1 << 25, // If node was parsed in a Json
570+
/* @internal */ TypeCached = 1 << 26, // If a type was cached for node at any point
570571

571572
BlockScoped = Let | Const,
572573

@@ -2699,8 +2700,6 @@ namespace ts {
26992700
Shared = 1 << 11, // Referenced as antecedent more than once
27002701
PreFinally = 1 << 12, // Injected edge that links pre-finally label and pre-try flow
27012702
AfterFinally = 1 << 13, // Injected edge that links post-finally flow with the rest of the graph
2702-
/** @internal */
2703-
Cached = 1 << 14, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant
27042703

27052704
Label = BranchLabel | LoopLabel,
27062705
Condition = TrueCondition | FalseCondition,

0 commit comments

Comments
 (0)