Skip to content

Enable caching for control flow paths that precede loops #33510

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

Merged
merged 3 commits into from
Sep 20, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 47 additions & 42 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,7 @@ namespace ts {
let flowLoopCount = 0;
let sharedFlowCount = 0;
let flowAnalysisDisabled = false;
let flowInvocationCount = 0;

const emptyStringType = getLiteralType("");
const zeroType = getLiteralType(0);
Expand All @@ -834,8 +835,7 @@ namespace ts {
const symbolLinks: SymbolLinks[] = [];
const nodeLinks: NodeLinks[] = [];
const flowLoopCaches: Map<Type>[] = [];
const flowAssignmentKeys: string[] = [];
const flowAssignmentTypes: FlowType[] = [];
const flowAssignmentTypes: Type[] = [];
const flowLoopNodes: FlowNode[] = [];
const flowLoopKeys: string[] = [];
const flowLoopTypes: Type[][] = [];
Expand Down Expand Up @@ -16698,12 +16698,6 @@ namespace ts {
getInitialTypeOfBindingElement(node);
}

function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression, reference: Node) {
return getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
getInitialType(<VariableDeclaration | BindingElement>node) :
getAssignedType(node), reference);
}

function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) {
return node.kind === SyntaxKind.VariableDeclaration && (<VariableDeclaration>node).initializer &&
isEmptyArrayLiteral((<VariableDeclaration>node).initializer!) ||
Expand Down Expand Up @@ -16990,6 +16984,7 @@ namespace ts {
if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
return declaredType;
}
flowInvocationCount++;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing ever decrements this - that intentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's just a monotonically increasing count that allows us to detect if the function was (recursively) invoked.

const sharedFlowStart = sharedFlowCount;
const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
sharedFlowCount = sharedFlowStart;
Expand Down Expand Up @@ -17022,15 +17017,6 @@ 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 @@ -17061,15 +17047,6 @@ 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 @@ -17122,6 +17099,27 @@ namespace ts {
}
}

function getInitialOrAssignedType(flow: FlowAssignment) {
const node = flow.node;
if (flow.flags & FlowFlags.Cached) {
const cached = flowAssignmentTypes[getNodeId(node)];
if (cached) {
return cached;
}
}
const startInvocationCount = flowInvocationCount;
const type = getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
getInitialType(<VariableDeclaration | BindingElement>node) :
getAssignedType(node), reference);
// We cache the assigned type when getFlowTypeOfReference was recursively invoked in the
// resolution of the assigned type and we're not within loop analysis.
if (flowInvocationCount !== startInvocationCount && flowLoopCount === flowLoopStart) {
flow.flags |= FlowFlags.Cached;
flowAssignmentTypes[getNodeId(node)] = type;
}
return type;
}

function getTypeAtFlowAssignment(flow: FlowAssignment) {
const node = flow.node;
// Assignments only narrow the computed type if the declared type is a union type. Thus, we
Expand All @@ -17135,11 +17133,11 @@ namespace ts {
if (isEmptyArrayAssignment(node)) {
return getEvolvingArrayType(neverType);
}
const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(node, reference));
const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(flow));
return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType;
}
if (declaredType.flags & TypeFlags.Union) {
return getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(node, reference));
return getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(flow));
}
return declaredType;
}
Expand Down Expand Up @@ -17309,24 +17307,31 @@ namespace ts {
const antecedentTypes: Type[] = [];
let subtypeReduction = false;
let firstAntecedentType: FlowType | undefined;
flowLoopNodes[flowLoopCount] = flow;
flowLoopKeys[flowLoopCount] = key;
flowLoopTypes[flowLoopCount] = antecedentTypes;
for (const antecedent of flow.antecedents!) {
flowLoopCount++;
const flowType = getTypeAtFlowNode(antecedent);
flowLoopCount--;
let flowType;
if (!firstAntecedentType) {
firstAntecedentType = flowType;
// The first antecedent of a loop junction is always the non-looping control
// flow path that leads to the top.
flowType = firstAntecedentType = getTypeAtFlowNode(antecedent);
}
const type = getTypeFromFlowType(flowType);
// If we see a value appear in the cache it is a sign that control flow analysis
// was restarted and completed by checkExpressionCached. We can simply pick up
// the resulting type and bail out.
const cached = cache.get(key);
if (cached) {
return cached;
else {
// All but the first antecedent are the looping control flow paths that lead
// back to the loop junction. We track these on the flow loop stack.
flowLoopNodes[flowLoopCount] = flow;
flowLoopKeys[flowLoopCount] = key;
flowLoopTypes[flowLoopCount] = antecedentTypes;
flowLoopCount++;
flowType = getTypeAtFlowNode(antecedent);
flowLoopCount--;
// If we see a value appear in the cache it is a sign that control flow analysis
// was restarted and completed by checkExpressionCached. We can simply pick up
// the resulting type and bail out.
const cached = cache.get(key);
if (cached) {
return cached;
}
}
const type = getTypeFromFlowType(flowType);
pushIfUnique(antecedentTypes, type);
// If an antecedent type is not a subset of the declared type, we need to perform
// subtype reduction. This happens when a "foreign" type is injected into the control
Expand Down