@@ -17804,7 +17804,7 @@ namespace ts {
1780417804 if (source.flags & TypeFlags.Singleton) return true;
1780517805 }
1780617806 if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
17807- const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation));
17807+ const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation, /*ignoreConstraints*/ false ));
1780817808 if (related !== undefined) {
1780917809 return !!(related & RelationComparisonResult.Succeeded);
1781017810 }
@@ -18704,7 +18704,8 @@ namespace ts {
1870418704 if (overflow) {
1870518705 return Ternary.False;
1870618706 }
18707- const id = getRelationKey(source, target, intersectionState | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0), relation);
18707+ const keyIntersectionState = intersectionState | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0);
18708+ const id = getRelationKey(source, target, keyIntersectionState, relation, /*ingnoreConstraints*/ false);
1870818709 const entry = relation.get(id);
1870918710 if (entry !== undefined) {
1871018711 if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) {
@@ -18731,16 +18732,13 @@ namespace ts {
1873118732 targetStack = [];
1873218733 }
1873318734 else {
18734- // generate a key where all type parameter id positions are replaced with unconstrained type parameter ids
18735- // this isn't perfect - nested type references passed as type arguments will muck up the indexes and thus
18736- // prevent finding matches- but it should hit up the common cases
18737- const broadestEquivalentId = id.split(",").map(i => i.replace(/-\d+/g, (_match, offset: number) => {
18738- const index = length(id.slice(0, offset).match(/[-=]/g) || undefined);
18739- return `=${index}`;
18740- })).join(",");
18735+ // A key that starts with "*" is an indication that we have type references that reference constrained
18736+ // type parameters. For such keys we also check against the key we would have gotten if all type parameters
18737+ // were unconstrained.
18738+ const broadestEquivalentId = id.startsWith("*") ? getRelationKey(source, target, keyIntersectionState, relation, /*ignoreConstraints*/ true) : undefined;
1874118739 for (let i = 0; i < maybeCount; i++) {
1874218740 // If source and target are already being compared, consider them related with assumptions
18743- if (id === maybeKeys[i] || broadestEquivalentId === maybeKeys[i]) {
18741+ if (id === maybeKeys[i] || broadestEquivalentId && broadestEquivalentId === maybeKeys[i]) {
1874418742 return Ternary.Maybe;
1874518743 }
1874618744 }
@@ -20295,47 +20293,55 @@ namespace ts {
2029520293 return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => !!(t.flags & TypeFlags.TypeParameter) || isTypeReferenceWithGenericArguments(t));
2029620294 }
2029720295
20298- /**
20299- * getTypeReferenceId(A<T, number, U>) returns "111=0-12=1"
20300- * where A.id=111 and number.id=12
20301- */
20302- function getTypeReferenceId(type: TypeReference, typeParameters: Type[], depth = 0) {
20303- let result = "" + type.target.id;
20304- for (const t of getTypeArguments(type)) {
20305- if (isUnconstrainedTypeParameter(t)) {
20306- let index = typeParameters.indexOf(t);
20307- if (index < 0) {
20308- index = typeParameters.length;
20309- typeParameters.push(t);
20296+ function getGenericTypeReferenceRelationKey(source: TypeReference, target: TypeReference, postFix: string, ignoreConstraints: boolean) {
20297+ const typeParameters: Type[] = [];
20298+ let constraintMarker = "";
20299+ const sourceId = getTypeReferenceId(source, 0);
20300+ const targetId = getTypeReferenceId(target, 0);
20301+ return `${constraintMarker}${sourceId},${targetId}${postFix}`;
20302+ // getTypeReferenceId(A<T, number, U>) returns "111=0-12=1"
20303+ // where A.id=111 and number.id=12
20304+ function getTypeReferenceId(type: TypeReference, depth = 0) {
20305+ let result = "" + type.target.id;
20306+ for (const t of getTypeArguments(type)) {
20307+ if (t.flags & TypeFlags.TypeParameter) {
20308+ if (ignoreConstraints || isUnconstrainedTypeParameter(t)) {
20309+ let index = typeParameters.indexOf(t);
20310+ if (index < 0) {
20311+ index = typeParameters.length;
20312+ typeParameters.push(t);
20313+ }
20314+ result += "=" + index;
20315+ continue;
20316+ }
20317+ // We mark type references that reference constrained type parameters such that we know to obtain
20318+ // and look for a "broadest equivalent key" in the cache.
20319+ constraintMarker = "*";
20320+ }
20321+ else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) {
20322+ result += "<" + getTypeReferenceId(t as TypeReference, depth + 1) + ">";
20323+ continue;
2031020324 }
20311- result += "=" + index;
20312- }
20313- else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) {
20314- result += "<" + getTypeReferenceId(t as TypeReference, typeParameters, depth + 1) + ">";
20315- }
20316- else {
2031720325 result += "-" + t.id;
2031820326 }
20327+ return result;
2031920328 }
20320- return result;
2032120329 }
2032220330
2032320331 /**
2032420332 * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters.
2032520333 * For other cases, the types ids are used.
2032620334 */
20327- function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap<string, RelationComparisonResult>) {
20335+ function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap<string, RelationComparisonResult>, ignoreConstraints: boolean ) {
2032820336 if (relation === identityRelation && source.id > target.id) {
2032920337 const temp = source;
2033020338 source = target;
2033120339 target = temp;
2033220340 }
2033320341 const postFix = intersectionState ? ":" + intersectionState : "";
20334- if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) {
20335- const typeParameters: Type[] = [];
20336- return getTypeReferenceId(source as TypeReference, typeParameters) + "," + getTypeReferenceId(target as TypeReference, typeParameters) + postFix;
20337- }
20338- return source.id + "," + target.id + postFix;
20342+ return isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target) ?
20343+ getGenericTypeReferenceRelationKey(source as TypeReference, target as TypeReference, postFix, ignoreConstraints) :
20344+ `${source.id},${target.id}${postFix}`;
2033920345 }
2034020346
2034120347 // Invoke the callback for each underlying property symbol of the given symbol and return the first
@@ -20389,27 +20395,34 @@ namespace ts {
2038920395 }
2039020396
2039120397 // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons
20392- // for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible,
20398+ // for maxDepth or more occurrences or instantiations of the type have been recorded on the given stack. It is possible,
2039320399 // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely
20394- // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least 5
20400+ // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth
2039520401 // levels, but unequal at some level beyond that.
20396- // In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially
20397- // the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives
20398- // for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`).
20399- // It also detects when a recursive type reference has expanded 5 or more times, eg, if the true branch of
20402+ // In addition, this will also detect when an indexed access has been chained off of maxDepth more times (which is
20403+ // essentially the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding
20404+ // false positives for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`).
20405+ // It also detects when a recursive type reference has expanded maxDepth or more times, e.g. if the true branch of
2040020406 // `type A<T> = null extends T ? [A<NonNullable<T>>] : [T]`
20401- // has expanded into `[A<NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>>>>>>]`
20402- // in such cases we need to terminate the expansion, and we do so here.
20403- function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 5 ): boolean {
20407+ // has expanded into `[A<NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>>>>>>]`. In such cases we need
20408+ // to terminate the expansion, and we do so here.
20409+ function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3 ): boolean {
2040420410 if (depth >= maxDepth) {
2040520411 const identity = getRecursionIdentity(type);
2040620412 let count = 0;
20413+ let lastTypeId = 0;
2040720414 for (let i = 0; i < depth; i++) {
20408- if (getRecursionIdentity(stack[i]) === identity) {
20409- count++;
20410- if (count >= maxDepth) {
20411- return true;
20415+ const t = stack[i];
20416+ if (getRecursionIdentity(t) === identity) {
20417+ // We only count occurrences with a higher type id than the previous occurrence, since higher
20418+ // type ids are an indicator of newer instantiations caused by recursion.
20419+ if (t.id >= lastTypeId) {
20420+ count++;
20421+ if (count >= maxDepth) {
20422+ return true;
20423+ }
2041220424 }
20425+ lastTypeId = t.id;
2041320426 }
2041420427 }
2041520428 }
0 commit comments