Skip to content
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
109 changes: 64 additions & 45 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ namespace ts {

let deferredNodes: Node[];

let flowStackStart = 0;
let flowStackCount = 0;
let flowLoopStart = 0;
let flowLoopCount = 0;
let visitedFlowCount = 0;

const tupleTypes: Map<TupleType> = {};
Expand All @@ -197,9 +197,10 @@ namespace ts {
const mergedSymbols: Symbol[] = [];
const symbolLinks: SymbolLinks[] = [];
const nodeLinks: NodeLinks[] = [];
const flowTypeCaches: Map<Type>[] = [];
const flowStackNodes: FlowNode[] = [];
const flowStackCacheKeys: string[] = [];
const flowLoopCaches: Map<Type>[] = [];
const flowLoopNodes: FlowNode[] = [];
const flowLoopKeys: string[] = [];
const flowLoopTypes: Type[][] = [];
const visitedFlowNodes: FlowNode[] = [];
const visitedFlowTypes: Type[] = [];
const potentialThisCollisions: Node[] = [];
Expand Down Expand Up @@ -7381,12 +7382,12 @@ namespace ts {
return false;
}

function getFlowTypeCache(flow: FlowNode): Map<Type> {
function getFlowNodeId(flow: FlowNode): number {
if (!flow.id) {
flow.id = nextFlowId;
nextFlowId++;
}
return flowTypeCaches[flow.id] || (flowTypeCaches[flow.id] = {});
return flow.id;
}

function typeMaybeAssignableTo(source: Type, target: Type) {
Expand Down Expand Up @@ -7617,7 +7618,9 @@ namespace ts {
flow = (<FlowLabel>flow).antecedents[0];
continue;
}
type = getTypeAtFlowLabel(<FlowLabel>flow);
type = flow.flags & FlowFlags.BranchLabel ?
getTypeAtFlowBranchLabel(<FlowLabel>flow) :
getTypeAtFlowLoopLabel(<FlowLabel>flow);
}
else if (flow.flags & FlowFlags.Unreachable) {
// Unreachable code errors are reported in the binding phase. Here we
Expand Down Expand Up @@ -7669,59 +7672,75 @@ namespace ts {
}

function getTypeAtFlowCondition(flow: FlowCondition) {
const type = getTypeAtFlowNode(flow.antecedent);
return type && narrowType(type, flow.expression, (flow.flags & FlowFlags.TrueCondition) !== 0);
return narrowType(getTypeAtFlowNode(flow.antecedent), flow.expression, (flow.flags & FlowFlags.TrueCondition) !== 0);
}

function getTypeAtFlowNodeCached(flow: FlowNode) {
const cache = getFlowTypeCache(flow);
function getTypeAtFlowBranchLabel(flow: FlowLabel) {
const antecedentTypes: Type[] = [];
for (const antecedent of flow.antecedents) {
const type = getTypeAtFlowNode(antecedent);
// If the type at a particular antecedent path is the declared type and the
// reference is known to always be assigned (i.e. when declared and initial types
// are the same), there is no reason to process more antecedents since the only
// possible outcome is subtypes that will be removed in the final union type anyway.
if (type === declaredType && declaredType === initialType) {
return type;
}
if (!contains(antecedentTypes, type)) {
antecedentTypes.push(type);
}
}
return antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes);
}

function getTypeAtFlowLoopLabel(flow: FlowLabel) {
// If we have previously computed the control flow type for the reference at
// this flow loop junction, return the cached type.
const id = getFlowNodeId(flow);
const cache = flowLoopCaches[id] || (flowLoopCaches[id] = {});
if (!key) {
key = getFlowCacheKey(reference);
}
const cached = cache[key];
if (cached) {
return cached;
if (cache[key]) {
return cache[key];
}
// Return undefined if we're already processing the given node.
for (let i = flowStackStart; i < flowStackCount; i++) {
if (flowStackNodes[i] === flow && flowStackCacheKeys[i] === key) {
return undefined;
// If this flow loop junction and reference are already being processed, return
// the union of the types computed for each branch so far. We should never see
// an empty array here because the first antecedent of a loop junction is always
// the non-looping control flow path that leads to the top.
for (let i = flowLoopStart; i < flowLoopCount; i++) {
if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key) {
return getUnionType(flowLoopTypes[i]);
}
}
// Record node and key on stack of nodes being processed.
flowStackNodes[flowStackCount] = flow;
flowStackCacheKeys[flowStackCount] = key;
flowStackCount++;
const type = getTypeAtFlowNode(flow);
flowStackCount--;
// Record the result only if the cache is still empty. If checkExpressionCached was called
// during processing it is possible we've already recorded a result.
return cache[key] || type && (cache[key] = type);
}

function getTypeAtFlowLabel(flow: FlowLabel) {
// Add the flow loop junction and reference to the in-process stack and analyze
// each antecedent code path.
const antecedentTypes: Type[] = [];
flowLoopNodes[flowLoopCount] = flow;
flowLoopKeys[flowLoopCount] = key;
flowLoopTypes[flowLoopCount] = antecedentTypes;
for (const antecedent of flow.antecedents) {
const type = flow.flags & FlowFlags.LoopLabel ?
getTypeAtFlowNodeCached(antecedent) :
getTypeAtFlowNode(antecedent);
if (!type) {
break;
flowLoopCount++;
const type = 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.
if (cache[key]) {
return cache[key];
}
// If the type at a particular antecedent path is the declared type and the
// reference is known to always be assigned (i.e. when declared and initial types
// are the same), there is no reason to process more antecedents since the only
// possible outcome is subtypes that will be removed in the final union type anyway.
if (type === declaredType && declaredType === initialType) {
return type;
return cache[key] = type;
}
if (!contains(antecedentTypes, type)) {
antecedentTypes.push(type);
}
}
return antecedentTypes.length === 0 ? undefined :
antecedentTypes.length === 1 ? antecedentTypes[0] :
getUnionType(antecedentTypes);
return cache[key] = antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes);
}

function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
Expand Down Expand Up @@ -11223,7 +11242,7 @@ namespace ts {
// If signature resolution originated in control flow type analysis (for example to compute the
// assigned type in a flow assignment) we don't cache the result as it may be based on temporary
// types from the control flow analysis.
links.resolvedSignature = flowStackStart === flowStackCount ? result : cached;
links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached;
return result;
}

Expand Down Expand Up @@ -12380,12 +12399,12 @@ namespace ts {
const links = getNodeLinks(node);
if (!links.resolvedType) {
// When computing a type that we're going to cache, we need to ignore any ongoing control flow
// analysis because variables may have transient types in indeterminable states. Moving flowStackStart
// analysis because variables may have transient types in indeterminable states. Moving flowLoopStart
// to the top of the stack ensures all transient types are computed from a known point.
const saveFlowStackStart = flowStackStart;
flowStackStart = flowStackCount;
const saveFlowLoopStart = flowLoopStart;
flowLoopStart = flowLoopCount;
links.resolvedType = checkExpression(node, contextualMapper);
flowStackStart = saveFlowStackStart;
flowLoopStart = saveFlowLoopStart;
}
return links.resolvedType;
}
Expand Down