@@ -17806,7 +17806,7 @@ namespace ts {
1780617806 if (source.flags & TypeFlags.Singleton) return true;
1780717807 }
1780817808 if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
17809- const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation));
17809+ const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation, /*ignoreConstraints*/ false ));
1781017810 if (related !== undefined) {
1781117811 return !!(related & RelationComparisonResult.Succeeded);
1781217812 }
@@ -18706,7 +18706,8 @@ namespace ts {
1870618706 if (overflow) {
1870718707 return Ternary.False;
1870818708 }
18709- const id = getRelationKey(source, target, intersectionState | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0), relation);
18709+ const keyIntersectionState = intersectionState | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0);
18710+ const id = getRelationKey(source, target, keyIntersectionState, relation, /*ingnoreConstraints*/ false);
1871018711 const entry = relation.get(id);
1871118712 if (entry !== undefined) {
1871218713 if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) {
@@ -18733,16 +18734,13 @@ namespace ts {
1873318734 targetStack = [];
1873418735 }
1873518736 else {
18736- // generate a key where all type parameter id positions are replaced with unconstrained type parameter ids
18737- // this isn't perfect - nested type references passed as type arguments will muck up the indexes and thus
18738- // prevent finding matches- but it should hit up the common cases
18739- const broadestEquivalentId = id.split(",").map(i => i.replace(/-\d+/g, (_match, offset: number) => {
18740- const index = length(id.slice(0, offset).match(/[-=]/g) || undefined);
18741- return `=${index}`;
18742- })).join(",");
18737+ // A key that starts with "*" is an indication that we have type references that reference constrained
18738+ // type parameters. For such keys we also check against the key we would have gotten if all type parameters
18739+ // were unconstrained.
18740+ const broadestEquivalentId = id.startsWith("*") ? getRelationKey(source, target, keyIntersectionState, relation, /*ignoreConstraints*/ true) : undefined;
1874318741 for (let i = 0; i < maybeCount; i++) {
1874418742 // If source and target are already being compared, consider them related with assumptions
18745- if (id === maybeKeys[i] || broadestEquivalentId === maybeKeys[i]) {
18743+ if (id === maybeKeys[i] || broadestEquivalentId && broadestEquivalentId === maybeKeys[i]) {
1874618744 return Ternary.Maybe;
1874718745 }
1874818746 }
@@ -20297,47 +20295,55 @@ namespace ts {
2029720295 return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => !!(t.flags & TypeFlags.TypeParameter) || isTypeReferenceWithGenericArguments(t));
2029820296 }
2029920297
20300- /**
20301- * getTypeReferenceId(A<T, number, U>) returns "111=0-12=1"
20302- * where A.id=111 and number.id=12
20303- */
20304- function getTypeReferenceId(type: TypeReference, typeParameters: Type[], depth = 0) {
20305- let result = "" + type.target.id;
20306- for (const t of getTypeArguments(type)) {
20307- if (isUnconstrainedTypeParameter(t)) {
20308- let index = typeParameters.indexOf(t);
20309- if (index < 0) {
20310- index = typeParameters.length;
20311- typeParameters.push(t);
20298+ function getGenericTypeReferenceRelationKey(source: TypeReference, target: TypeReference, postFix: string, ignoreConstraints: boolean) {
20299+ const typeParameters: Type[] = [];
20300+ let constraintMarker = "";
20301+ const sourceId = getTypeReferenceId(source, 0);
20302+ const targetId = getTypeReferenceId(target, 0);
20303+ return `${constraintMarker}${sourceId},${targetId}${postFix}`;
20304+ // getTypeReferenceId(A<T, number, U>) returns "111=0-12=1"
20305+ // where A.id=111 and number.id=12
20306+ function getTypeReferenceId(type: TypeReference, depth = 0) {
20307+ let result = "" + type.target.id;
20308+ for (const t of getTypeArguments(type)) {
20309+ if (t.flags & TypeFlags.TypeParameter) {
20310+ if (ignoreConstraints || isUnconstrainedTypeParameter(t)) {
20311+ let index = typeParameters.indexOf(t);
20312+ if (index < 0) {
20313+ index = typeParameters.length;
20314+ typeParameters.push(t);
20315+ }
20316+ result += "=" + index;
20317+ continue;
20318+ }
20319+ // We mark type references that reference constrained type parameters such that we know to obtain
20320+ // and look for a "broadest equivalent key" in the cache.
20321+ constraintMarker = "*";
20322+ }
20323+ else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) {
20324+ result += "<" + getTypeReferenceId(t as TypeReference, depth + 1) + ">";
20325+ continue;
2031220326 }
20313- result += "=" + index;
20314- }
20315- else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) {
20316- result += "<" + getTypeReferenceId(t as TypeReference, typeParameters, depth + 1) + ">";
20317- }
20318- else {
2031920327 result += "-" + t.id;
2032020328 }
20329+ return result;
2032120330 }
20322- return result;
2032320331 }
2032420332
2032520333 /**
2032620334 * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters.
2032720335 * For other cases, the types ids are used.
2032820336 */
20329- function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap<string, RelationComparisonResult>) {
20337+ function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap<string, RelationComparisonResult>, ignoreConstraints: boolean ) {
2033020338 if (relation === identityRelation && source.id > target.id) {
2033120339 const temp = source;
2033220340 source = target;
2033320341 target = temp;
2033420342 }
2033520343 const postFix = intersectionState ? ":" + intersectionState : "";
20336- if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) {
20337- const typeParameters: Type[] = [];
20338- return getTypeReferenceId(source as TypeReference, typeParameters) + "," + getTypeReferenceId(target as TypeReference, typeParameters) + postFix;
20339- }
20340- return source.id + "," + target.id + postFix;
20344+ return isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target) ?
20345+ getGenericTypeReferenceRelationKey(source as TypeReference, target as TypeReference, postFix, ignoreConstraints) :
20346+ `${source.id},${target.id}${postFix}`;
2034120347 }
2034220348
2034320349 // Invoke the callback for each underlying property symbol of the given symbol and return the first
@@ -20391,27 +20397,34 @@ namespace ts {
2039120397 }
2039220398
2039320399 // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons
20394- // for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible,
20400+ // for maxDepth or more occurrences or instantiations of the type have been recorded on the given stack. It is possible,
2039520401 // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely
20396- // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least 5
20402+ // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth
2039720403 // levels, but unequal at some level beyond that.
20398- // In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially
20399- // the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives
20400- // for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`).
20401- // It also detects when a recursive type reference has expanded 5 or more times, eg, if the true branch of
20404+ // In addition, this will also detect when an indexed access has been chained off of maxDepth more times (which is
20405+ // essentially the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding
20406+ // false positives for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`).
20407+ // It also detects when a recursive type reference has expanded maxDepth or more times, e.g. if the true branch of
2040220408 // `type A<T> = null extends T ? [A<NonNullable<T>>] : [T]`
20403- // has expanded into `[A<NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>>>>>>]`
20404- // in such cases we need to terminate the expansion, and we do so here.
20405- function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 5 ): boolean {
20409+ // has expanded into `[A<NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>>>>>>]`. In such cases we need
20410+ // to terminate the expansion, and we do so here.
20411+ function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3 ): boolean {
2040620412 if (depth >= maxDepth) {
2040720413 const identity = getRecursionIdentity(type);
2040820414 let count = 0;
20415+ let lastTypeId = 0;
2040920416 for (let i = 0; i < depth; i++) {
20410- if (getRecursionIdentity(stack[i]) === identity) {
20411- count++;
20412- if (count >= maxDepth) {
20413- return true;
20417+ const t = stack[i];
20418+ if (getRecursionIdentity(t) === identity) {
20419+ // We only count occurrences with a higher type id than the previous occurrence, since higher
20420+ // type ids are an indicator of newer instantiations caused by recursion.
20421+ if (t.id >= lastTypeId) {
20422+ count++;
20423+ if (count >= maxDepth) {
20424+ return true;
20425+ }
2041420426 }
20427+ lastTypeId = t.id;
2041520428 }
2041620429 }
2041720430 }
0 commit comments