Skip to content

Commit 156d219

Browse files
ahejlsbergandrewbranch
authored andcommitted
Cherry-pick PR microsoft#46599 into release-4.5
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
1 parent 49b1acc commit 156d219

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
@@ -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
}

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)