Skip to content

Commit 6c11cf4

Browse files
committed
Revise recursion tracking in type inference
1 parent 7c4d923 commit 6c11cf4

File tree

1 file changed

+41
-23
lines changed

1 file changed

+41
-23
lines changed

src/compiler/checker.ts

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18201,15 +18201,24 @@ namespace ts {
1820118201
return false;
1820218202
}
1820318203

18204-
function getRecursionIdentity(type: Type) {
18204+
// Types with constituents that could circularly reference the type have a recursion identity. The recursion
18205+
// identity is some object that is common to instantiations of the type with the same origin.
18206+
function getRecursionIdentity(type: Type): object | undefined {
1820518207
if (type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) {
18206-
if (type.symbol) {
18207-
// We track all object types that have an associated symbol (representing the origin of the type)
18208+
if (getObjectFlags(type) && ObjectFlags.Reference && (type as TypeReference).node) {
18209+
// Deferred type references are tracked through their associated AST node. This gives us finer
18210+
// granularity than using their associated target because each manifest type reference has a
18211+
// unique AST node.
18212+
return (type as TypeReference).node;
18213+
}
18214+
if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
18215+
// We track all object types that have an associated symbol (representing the origin of the type), but
18216+
// exclude the static side of classes from this check since it shares its symbol with the instance side.
1820818217
return type.symbol;
1820918218
}
18210-
if (getObjectFlags(type) && ObjectFlags.Reference && (type as TypeReference).node || isTupleType(type)) {
18211-
// Deferred type references and tuple types are tracked through their target type
18212-
return (type as TypeReference).target;
18219+
if (isTupleType(type)) {
18220+
// Tuple types are tracked through their target type
18221+
return type.target;
1821318222
}
1821418223
}
1821518224
if (type.flags & TypeFlags.IndexedAccess) {
@@ -19283,7 +19292,9 @@ namespace ts {
1928319292
let inferencePriority = InferencePriority.MaxValue;
1928419293
let allowComplexConstraintInference = true;
1928519294
let visited: ESMap<string, number>;
19286-
let targetStack: Type[];
19295+
let sourceStack: object[];
19296+
let targetStack: object[];
19297+
let expandingFlags = ExpandingFlags.None;
1928719298
inferFromTypes(originalSource, originalTarget);
1928819299

1928919300
function inferFromTypes(source: Type, target: Type): void {
@@ -19406,7 +19417,7 @@ namespace ts {
1940619417
// Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine
1940719418
const simplified = getSimplifiedType(target, /*writing*/ false);
1940819419
if (simplified !== target) {
19409-
invokeWithDepthLimit(source, simplified, inferFromTypes);
19420+
invokeOnce(source, simplified, inferFromTypes);
1941019421
}
1941119422
else if (target.flags & TypeFlags.IndexedAccess) {
1941219423
const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false);
@@ -19415,7 +19426,7 @@ namespace ts {
1941519426
if (indexType.flags & TypeFlags.Instantiable) {
1941619427
const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false);
1941719428
if (simplified && simplified !== target) {
19418-
invokeWithDepthLimit(source, simplified, inferFromTypes);
19429+
invokeOnce(source, simplified, inferFromTypes);
1941919430
}
1942019431
}
1942119432
}
@@ -19443,7 +19454,7 @@ namespace ts {
1944319454
inferFromTypes((<IndexedAccessType>source).indexType, (<IndexedAccessType>target).indexType);
1944419455
}
1944519456
else if (target.flags & TypeFlags.Conditional) {
19446-
invokeWithDepthLimit(<ConditionalType>source, <ConditionalType>target, inferToConditionalType);
19457+
invokeOnce(source, target, inferToConditionalType);
1944719458
}
1944819459
else if (target.flags & TypeFlags.UnionOrIntersection) {
1944919460
inferToMultipleTypes(source, (<UnionOrIntersectionType>target).types, target.flags);
@@ -19476,7 +19487,7 @@ namespace ts {
1947619487
source = apparentSource;
1947719488
}
1947819489
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) {
19479-
invokeWithDepthLimit(source, target, inferFromObjectTypes);
19490+
invokeOnce(source, target, inferFromObjectTypes);
1948019491
}
1948119492
}
1948219493
if (source.flags & TypeFlags.Simplifiable) {
@@ -19494,7 +19505,7 @@ namespace ts {
1949419505
priority = savePriority;
1949519506
}
1949619507

19497-
function invokeWithDepthLimit(source: Type, target: Type, action: (source: Type, target: Type) => void) {
19508+
function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) {
1949819509
const key = source.id + "," + target.id;
1949919510
const status = visited && visited.get(key);
1950019511
if (status !== undefined) {
@@ -19504,17 +19515,24 @@ namespace ts {
1950419515
(visited || (visited = new Map<string, number>())).set(key, InferencePriority.Circularity);
1950519516
const saveInferencePriority = inferencePriority;
1950619517
inferencePriority = InferencePriority.MaxValue;
19507-
// It is possible for recursion to originate in generative types that create infinitely deep instantiations,
19508-
// with unique identities, for example 'type RecArray<T> = T | Array<RecArray<T>>'. We explore up to five
19509-
// nested instantiations of such types using the same isDeeplyNestedType check as recursiveTypeRelatedTo.
19510-
(targetStack || (targetStack = [])).push(target);
19511-
if (!isDeeplyNestedType(target, targetStack, targetStack.length)) {
19518+
// We stop inferring and report a circularity if we encounter duplicate recursion identities on both
19519+
// the source side and the target side.
19520+
const saveExpandingFlags = expandingFlags;
19521+
const sourceIdentity = getRecursionIdentity(source);
19522+
const targetIdentity = getRecursionIdentity(target);
19523+
if (sourceIdentity && contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source;
19524+
if (targetIdentity && contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target;
19525+
if (expandingFlags !== ExpandingFlags.Both) {
19526+
if (sourceIdentity) (sourceStack || (sourceStack = [])).push(sourceIdentity);
19527+
if (targetIdentity) (targetStack || (targetStack = [])).push(targetIdentity);
1951219528
action(source, target);
19529+
if (targetIdentity) targetStack.pop();
19530+
if (sourceIdentity) sourceStack.pop();
1951319531
}
1951419532
else {
1951519533
inferencePriority = InferencePriority.Circularity;
1951619534
}
19517-
targetStack.pop();
19535+
expandingFlags = saveExpandingFlags;
1951819536
visited.set(key, inferencePriority);
1951919537
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
1952019538
}
@@ -19710,12 +19728,12 @@ namespace ts {
1971019728
return false;
1971119729
}
1971219730

19713-
function inferToConditionalType(source: ConditionalType, target: ConditionalType) {
19731+
function inferToConditionalType(source: Type, target: ConditionalType) {
1971419732
if (source.flags & TypeFlags.Conditional) {
19715-
inferFromTypes(source.checkType, target.checkType);
19716-
inferFromTypes(source.extendsType, target.extendsType);
19717-
inferFromTypes(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target));
19718-
inferFromTypes(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target));
19733+
inferFromTypes((<ConditionalType>source).checkType, target.checkType);
19734+
inferFromTypes((<ConditionalType>source).extendsType, target.extendsType);
19735+
inferFromTypes(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(target));
19736+
inferFromTypes(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(target));
1971919737
}
1972019738
else {
1972119739
const savePriority = priority;

0 commit comments

Comments
 (0)