Merge pull request #8480 from Microsoft/controlFlowLoopLogic

Improve control flow loop analysis logic
This commit is contained in:
Anders Hejlsberg 2016-05-05 12:47:29 -07:00
commit c26e920c11

View file

@ -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> = {};
@ -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[] = [];
@ -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) {
@ -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
@ -7669,45 +7672,13 @@ 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);
if (!key) {
key = getFlowCacheKey(reference);
}
const cached = cache[key];
if (cached) {
return cached;
}
// 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;
}
}
// 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) {
function getTypeAtFlowBranchLabel(flow: FlowLabel) {
const antecedentTypes: Type[] = [];
for (const antecedent of flow.antecedents) {
const type = flow.flags & FlowFlags.LoopLabel ?
getTypeAtFlowNodeCached(antecedent) :
getTypeAtFlowNode(antecedent);
if (!type) {
break;
}
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
@ -7719,9 +7690,57 @@ namespace ts {
antecedentTypes.push(type);
}
}
return antecedentTypes.length === 0 ? undefined :
antecedentTypes.length === 1 ? antecedentTypes[0] :
getUnionType(antecedentTypes);
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);
}
if (cache[key]) {
return cache[key];
}
// 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]);
}
}
// 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) {
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 cache[key] = type;
}
if (!contains(antecedentTypes, type)) {
antecedentTypes.push(type);
}
}
return cache[key] = antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes);
}
function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
@ -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;
}
@ -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;
}