Skip to content

Commit ae44862

Browse files
TypeScript Botahejlsberg
TypeScript Bot
andauthored
Cherry-pick PR #46599 into release-4.5 (#46974)
Component commits: ddc106b Decrease recursion depth limit to 3 + smarter check for recursion 86185ad Accept new baselines 52e10d3 Always set last type id 5f37d89 Keep indexed access recursion depth check 9df07a8 Less expensive and corrected check for broadest equivalent keys Co-authored-by: Anders Hejlsberg <[email protected]>
1 parent c63f0cf commit ae44862

File tree

2 files changed

+67
-54
lines changed

2 files changed

+67
-54
lines changed

src/compiler/checker.ts

+61-48
Original file line numberDiff line numberDiff line change
@@ -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
}

tests/baselines/reference/invariantGenericErrorElaboration.errors.txt

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
tests/cases/compiler/invariantGenericErrorElaboration.ts(3,7): error TS2322: Type 'Num' is not assignable to type 'Runtype<any>'.
2-
The types of 'constraint.constraint.constraint' are incompatible between these types.
3-
Type 'Constraint<Constraint<Constraint<Num>>>' is not assignable to type 'Constraint<Constraint<Constraint<Runtype<any>>>>'.
4-
Type 'Constraint<Runtype<any>>' is not assignable to type 'Constraint<Num>'.
2+
The types of 'constraint.constraint' are incompatible between these types.
3+
Type 'Constraint<Constraint<Num>>' is not assignable to type 'Constraint<Constraint<Runtype<any>>>'.
4+
Type 'Runtype<any>' is not assignable to type 'Num'.
55
tests/cases/compiler/invariantGenericErrorElaboration.ts(4,19): error TS2322: Type 'Num' is not assignable to type 'Runtype<any>'.
66

77

@@ -11,9 +11,9 @@ tests/cases/compiler/invariantGenericErrorElaboration.ts(4,19): error TS2322: Ty
1111
const wat: Runtype<any> = Num;
1212
~~~
1313
!!! error TS2322: Type 'Num' is not assignable to type 'Runtype<any>'.
14-
!!! error TS2322: The types of 'constraint.constraint.constraint' are incompatible between these types.
15-
!!! error TS2322: Type 'Constraint<Constraint<Constraint<Num>>>' is not assignable to type 'Constraint<Constraint<Constraint<Runtype<any>>>>'.
16-
!!! error TS2322: Type 'Constraint<Runtype<any>>' is not assignable to type 'Constraint<Num>'.
14+
!!! error TS2322: The types of 'constraint.constraint' are incompatible between these types.
15+
!!! error TS2322: Type 'Constraint<Constraint<Num>>' is not assignable to type 'Constraint<Constraint<Runtype<any>>>'.
16+
!!! error TS2322: Type 'Runtype<any>' is not assignable to type 'Num'.
1717
const Foo = Obj({ foo: Num })
1818
~~~
1919
!!! error TS2322: Type 'Num' is not assignable to type 'Runtype<any>'.

0 commit comments

Comments
 (0)