Skip to content

Commit a9d8df2

Browse files
authored
Merge pull request #14825 from Microsoft/fixDeeplyNestedCheck
Fix deeply nested type check
2 parents 87c291e + f139a6c commit a9d8df2

File tree

6 files changed

+100
-18
lines changed

6 files changed

+100
-18
lines changed

src/compiler/checker.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8277,8 +8277,8 @@ namespace ts {
82778277
maybeStack[depth].set(id, RelationComparisonResult.Succeeded);
82788278
depth++;
82798279
const saveExpandingFlags = expandingFlags;
8280-
if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack, depth)) expandingFlags |= 1;
8281-
if (!(expandingFlags & 2) && isDeeplyNestedGeneric(target, targetStack, depth)) expandingFlags |= 2;
8280+
if (!(expandingFlags & 1) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= 1;
8281+
if (!(expandingFlags & 2) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= 2;
82828282
let result: Ternary;
82838283
if (expandingFlags === 3) {
82848284
result = Ternary.Maybe;
@@ -8698,21 +8698,23 @@ namespace ts {
86988698
return false;
86998699
}
87008700

8701-
// Return true if the given type is part of a deeply nested chain of generic instantiations. We consider this to be the case
8702-
// when structural type comparisons have been started for 10 or more instantiations of the same generic type. It is possible,
8703-
// though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely expanding.
8704-
// Effectively, we will generate a false positive when two types are structurally equal to at least 10 levels, but unequal at
8705-
// some level beyond that.
8706-
function isDeeplyNestedGeneric(type: Type, stack: Type[], depth: number): boolean {
8707-
// We track type references (created by createTypeReference) and instantiated types (created by instantiateType)
8708-
if (getObjectFlags(type) & (ObjectFlags.Reference | ObjectFlags.Instantiated) && depth >= 5) {
8701+
// Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons
8702+
// for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible,
8703+
// though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely
8704+
// expanding. Effectively, we will generate a false positive when two types are structurally equal to at least 5
8705+
// levels, but unequal at some level beyond that.
8706+
function isDeeplyNestedType(type: Type, stack: Type[], depth: number): boolean {
8707+
// We track all object types that have an associated symbol (representing the origin of the type)
8708+
if (depth >= 5 && type.flags & TypeFlags.Object) {
87098709
const symbol = type.symbol;
8710-
let count = 0;
8711-
for (let i = 0; i < depth; i++) {
8712-
const t = stack[i];
8713-
if (getObjectFlags(t) & (ObjectFlags.Reference | ObjectFlags.Instantiated) && t.symbol === symbol) {
8714-
count++;
8715-
if (count >= 5) return true;
8710+
if (symbol) {
8711+
let count = 0;
8712+
for (let i = 0; i < depth; i++) {
8713+
const t = stack[i];
8714+
if (t.flags & TypeFlags.Object && t.symbol === symbol) {
8715+
count++;
8716+
if (count >= 5) return true;
8717+
}
87168718
}
87178719
}
87188720
}
@@ -9455,7 +9457,7 @@ namespace ts {
94559457
if (isInProcess(source, target)) {
94569458
return;
94579459
}
9458-
if (isDeeplyNestedGeneric(source, sourceStack, depth) && isDeeplyNestedGeneric(target, targetStack, depth)) {
9460+
if (isDeeplyNestedType(source, sourceStack, depth) && isDeeplyNestedType(target, targetStack, depth)) {
94599461
return;
94609462
}
94619463
const key = source.id + "," + target.id;

src/compiler/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3014,7 +3014,6 @@ namespace ts {
30143014
ObjectLiteral = 1 << 7, // Originates in an object literal
30153015
EvolvingArray = 1 << 8, // Evolving array type
30163016
ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties
3017-
NonPrimitive = 1 << 10, // NonPrimitive object type
30183017
ClassOrInterface = Class | Interface
30193018
}
30203019

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//// [deeplyNestedCheck.ts]
2+
// Repro from #14794
3+
4+
interface DataSnapshot<X = {}> {
5+
child(path: string): DataSnapshot;
6+
}
7+
8+
interface Snapshot<T> extends DataSnapshot {
9+
child<U extends keyof T>(path: U): Snapshot<T[U]>;
10+
}
11+
12+
13+
//// [deeplyNestedCheck.js]
14+
// Repro from #14794
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/compiler/deeplyNestedCheck.ts ===
2+
// Repro from #14794
3+
4+
interface DataSnapshot<X = {}> {
5+
>DataSnapshot : Symbol(DataSnapshot, Decl(deeplyNestedCheck.ts, 0, 0))
6+
>X : Symbol(X, Decl(deeplyNestedCheck.ts, 2, 23))
7+
8+
child(path: string): DataSnapshot;
9+
>child : Symbol(DataSnapshot.child, Decl(deeplyNestedCheck.ts, 2, 32))
10+
>path : Symbol(path, Decl(deeplyNestedCheck.ts, 3, 8))
11+
>DataSnapshot : Symbol(DataSnapshot, Decl(deeplyNestedCheck.ts, 0, 0))
12+
}
13+
14+
interface Snapshot<T> extends DataSnapshot {
15+
>Snapshot : Symbol(Snapshot, Decl(deeplyNestedCheck.ts, 4, 1))
16+
>T : Symbol(T, Decl(deeplyNestedCheck.ts, 6, 19))
17+
>DataSnapshot : Symbol(DataSnapshot, Decl(deeplyNestedCheck.ts, 0, 0))
18+
19+
child<U extends keyof T>(path: U): Snapshot<T[U]>;
20+
>child : Symbol(Snapshot.child, Decl(deeplyNestedCheck.ts, 6, 44))
21+
>U : Symbol(U, Decl(deeplyNestedCheck.ts, 7, 8))
22+
>T : Symbol(T, Decl(deeplyNestedCheck.ts, 6, 19))
23+
>path : Symbol(path, Decl(deeplyNestedCheck.ts, 7, 27))
24+
>U : Symbol(U, Decl(deeplyNestedCheck.ts, 7, 8))
25+
>Snapshot : Symbol(Snapshot, Decl(deeplyNestedCheck.ts, 4, 1))
26+
>T : Symbol(T, Decl(deeplyNestedCheck.ts, 6, 19))
27+
>U : Symbol(U, Decl(deeplyNestedCheck.ts, 7, 8))
28+
}
29+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/compiler/deeplyNestedCheck.ts ===
2+
// Repro from #14794
3+
4+
interface DataSnapshot<X = {}> {
5+
>DataSnapshot : DataSnapshot<X>
6+
>X : X
7+
8+
child(path: string): DataSnapshot;
9+
>child : (path: string) => DataSnapshot<{}>
10+
>path : string
11+
>DataSnapshot : DataSnapshot<X>
12+
}
13+
14+
interface Snapshot<T> extends DataSnapshot {
15+
>Snapshot : Snapshot<T>
16+
>T : T
17+
>DataSnapshot : DataSnapshot<X>
18+
19+
child<U extends keyof T>(path: U): Snapshot<T[U]>;
20+
>child : <U extends keyof T>(path: U) => Snapshot<T[U]>
21+
>U : U
22+
>T : T
23+
>path : U
24+
>U : U
25+
>Snapshot : Snapshot<T>
26+
>T : T
27+
>U : U
28+
}
29+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Repro from #14794
2+
3+
interface DataSnapshot<X = {}> {
4+
child(path: string): DataSnapshot;
5+
}
6+
7+
interface Snapshot<T> extends DataSnapshot {
8+
child<U extends keyof T>(path: U): Snapshot<T[U]>;
9+
}

0 commit comments

Comments
 (0)