@@ -17806,7 +17806,7 @@ namespace ts {
17806
17806
if (source.flags & TypeFlags.Singleton) return true;
17807
17807
}
17808
17808
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 ));
17810
17810
if (related !== undefined) {
17811
17811
return !!(related & RelationComparisonResult.Succeeded);
17812
17812
}
@@ -18706,7 +18706,8 @@ namespace ts {
18706
18706
if (overflow) {
18707
18707
return Ternary.False;
18708
18708
}
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);
18710
18711
const entry = relation.get(id);
18711
18712
if (entry !== undefined) {
18712
18713
if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) {
@@ -18733,16 +18734,13 @@ namespace ts {
18733
18734
targetStack = [];
18734
18735
}
18735
18736
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;
18743
18741
for (let i = 0; i < maybeCount; i++) {
18744
18742
// 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]) {
18746
18744
return Ternary.Maybe;
18747
18745
}
18748
18746
}
@@ -20297,47 +20295,55 @@ namespace ts {
20297
20295
return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => !!(t.flags & TypeFlags.TypeParameter) || isTypeReferenceWithGenericArguments(t));
20298
20296
}
20299
20297
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;
20312
20326
}
20313
- result += "=" + index;
20314
- }
20315
- else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) {
20316
- result += "<" + getTypeReferenceId(t as TypeReference, typeParameters, depth + 1) + ">";
20317
- }
20318
- else {
20319
20327
result += "-" + t.id;
20320
20328
}
20329
+ return result;
20321
20330
}
20322
- return result;
20323
20331
}
20324
20332
20325
20333
/**
20326
20334
* To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters.
20327
20335
* For other cases, the types ids are used.
20328
20336
*/
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 ) {
20330
20338
if (relation === identityRelation && source.id > target.id) {
20331
20339
const temp = source;
20332
20340
source = target;
20333
20341
target = temp;
20334
20342
}
20335
20343
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}`;
20341
20347
}
20342
20348
20343
20349
// Invoke the callback for each underlying property symbol of the given symbol and return the first
@@ -20391,27 +20397,34 @@ namespace ts {
20391
20397
}
20392
20398
20393
20399
// 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,
20395
20401
// 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
20397
20403
// 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
20402
20408
// `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 {
20406
20412
if (depth >= maxDepth) {
20407
20413
const identity = getRecursionIdentity(type);
20408
20414
let count = 0;
20415
+ let lastTypeId = 0;
20409
20416
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
+ }
20414
20426
}
20427
+ lastTypeId = t.id;
20415
20428
}
20416
20429
}
20417
20430
}
0 commit comments