mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge pull request #33510 from microsoft/controlFlowLoopCaching
Enable caching for control flow paths that precede loops
This commit is contained in:
+47
-42
@@ -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);
|
||||
@@ -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[][] = [];
|
||||
@@ -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!) ||
|
||||
@@ -16990,6 +16984,7 @@ namespace ts {
|
||||
if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
|
||||
return declaredType;
|
||||
}
|
||||
flowInvocationCount++;
|
||||
const sharedFlowStart = sharedFlowCount;
|
||||
const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
|
||||
sharedFlowCount = sharedFlowStart;
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user