Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache control flow results across invocations #31003

Merged
merged 6 commits into from May 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 35 additions & 8 deletions src/compiler/checker.ts
Expand Up @@ -559,6 +559,8 @@ namespace ts {
const symbolLinks: SymbolLinks[] = [];
const nodeLinks: NodeLinks[] = [];
const flowLoopCaches: Map<Type>[] = [];
const flowAssignmentKeys: string[] = [];
const flowAssignmentTypes: FlowType[] = [];
const flowLoopNodes: FlowNode[] = [];
const flowLoopKeys: string[] = [];
const flowLoopTypes: Type[][] = [];
Expand Down Expand Up @@ -15666,21 +15668,21 @@ namespace ts {
// The result is undefined if the reference isn't a dotted name. We prefix nodes
// occurring in an apparent type position with '@' because the control flow type
// of such nodes may be based on the apparent type instead of the declared type.
function getFlowCacheKey(node: Node): string | undefined {
function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined {
switch (node.kind) {
case SyntaxKind.Identifier:
const symbol = getResolvedSymbol(<Identifier>node);
return symbol !== unknownSymbol ? (isConstraintPosition(node) ? "@" : "") + getSymbolId(symbol) : undefined;
return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${isConstraintPosition(node) ? "@" : ""}${getSymbolId(symbol)}` : undefined;
case SyntaxKind.ThisKeyword:
return "0";
case SyntaxKind.NonNullExpression:
case SyntaxKind.ParenthesizedExpression:
return getFlowCacheKey((<NonNullExpression | ParenthesizedExpression>node).expression);
return getFlowCacheKey((<NonNullExpression | ParenthesizedExpression>node).expression, declaredType, initialType, flowContainer);
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
const propName = getAccessedPropertyName(<AccessExpression>node);
if (propName !== undefined) {
const key = getFlowCacheKey((<AccessExpression>node).expression);
const key = getFlowCacheKey((<AccessExpression>node).expression, declaredType, initialType, flowContainer);
return key && key + "." + propName;
}
}
Expand Down Expand Up @@ -16345,6 +16347,7 @@ namespace ts {

function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) {
let key: string | undefined;
let keySet = false;
let flowDepth = 0;
if (flowAnalysisDisabled) {
return errorType;
Expand All @@ -16365,6 +16368,14 @@ namespace ts {
}
return resultType;

function getOrSetCacheKey() {
if (keySet) {
return key;
}
keySet = true;
return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer);
}

function getTypeAtFlowNode(flow: FlowNode): FlowType {
if (flowDepth === 2000) {
// We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error
Expand All @@ -16376,6 +16387,15 @@ namespace ts {
flowDepth++;
while (true) {
const flags = flow.flags;
if (flags & FlowFlags.Cached) {
const key = getOrSetCacheKey();
if (key) {
const id = getFlowNodeId(flow);
if (flowAssignmentKeys[id] === key) {
return flowAssignmentTypes[id];
}
}
}
if (flags & FlowFlags.Shared) {
// We cache results of flow type resolution for shared nodes that were previously visited in
// the same getFlowTypeOfReference invocation. A node is considered shared when it is the
Expand Down Expand Up @@ -16406,6 +16426,15 @@ namespace ts {
flow = (<FlowAssignment>flow).antecedent;
continue;
}
else if (flowLoopCount === flowLoopStart) { // Only cache assignments when not within loop analysis
const key = getOrSetCacheKey();
if (key && !isIncomplete(type)) {
flow.flags |= FlowFlags.Cached;
const id = getFlowNodeId(flow);
flowAssignmentKeys[id] = key;
flowAssignmentTypes[id] = type;
}
}
}
else if (flags & FlowFlags.Condition) {
type = getTypeAtFlowCondition(<FlowCondition>flow);
Expand Down Expand Up @@ -16618,12 +16647,10 @@ namespace ts {
// this flow loop junction, return the cached type.
const id = getFlowNodeId(flow);
const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap<Type>());
const key = getOrSetCacheKey();
if (!key) {
key = getFlowCacheKey(reference);
// No cache key is generated when binding patterns are in unnarrowable situations
if (!key) {
return declaredType;
}
return declaredType;
}
const cached = cache.get(key);
if (cached) {
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Expand Up @@ -2546,6 +2546,8 @@ namespace ts {
Shared = 1 << 10, // Referenced as antecedent more than once
PreFinally = 1 << 11, // Injected edge that links pre-finally label and pre-try flow
AfterFinally = 1 << 12, // Injected edge that links post-finally flow with the rest of the graph
/** @internal */
Cached = 1 << 13, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant
Label = BranchLabel | LoopLabel,
Condition = TrueCondition | FalseCondition
}
Expand Down