Skip to content

Commit f33e25e

Browse files
committed
Track source and target relationship stack depth seperately, only increase on change in value
1 parent 387b6dc commit f33e25e

7 files changed

+280
-12
lines changed

src/compiler/checker.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16507,7 +16507,8 @@ namespace ts {
1650716507
let sourceStack: Type[];
1650816508
let targetStack: Type[];
1650916509
let maybeCount = 0;
16510-
let depth = 0;
16510+
let sourceDepth = 0;
16511+
let targetDepth = 0;
1651116512
let expandingFlags = ExpandingFlags.None;
1651216513
let overflow = false;
1651316514
let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid
@@ -16522,7 +16523,7 @@ namespace ts {
1652216523
reportIncompatibleStack();
1652316524
}
1652416525
if (overflow) {
16525-
tracing.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth });
16526+
tracing.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth });
1652616527
const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target));
1652716528
if (errorOutputContainer) {
1652816529
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
@@ -17320,20 +17321,27 @@ namespace ts {
1732017321
return Ternary.Maybe;
1732117322
}
1732217323
}
17323-
if (depth === 100) {
17324+
if (sourceDepth === 100 || targetDepth === 100) {
1732417325
overflow = true;
1732517326
return Ternary.False;
1732617327
}
1732717328
}
17329+
const constantSource = sourceStack[sourceDepth] === source;
17330+
const constantTarget = targetStack[targetDepth] === target;
1732817331
const maybeStart = maybeCount;
1732917332
maybeKeys[maybeCount] = id;
1733017333
maybeCount++;
17331-
sourceStack[depth] = source;
17332-
targetStack[depth] = target;
17333-
depth++;
17334+
if (!constantSource) {
17335+
sourceStack[sourceDepth] = source;
17336+
sourceDepth++;
17337+
}
17338+
if (!constantTarget) {
17339+
targetStack[targetDepth] = target;
17340+
targetDepth++;
17341+
}
1733417342
const saveExpandingFlags = expandingFlags;
17335-
if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= ExpandingFlags.Source;
17336-
if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= ExpandingFlags.Target;
17343+
if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source;
17344+
if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target;
1733717345
let originalHandler: typeof outofbandVarianceMarkerHandler;
1733817346
let propagatingVarianceFlags: RelationComparisonResult = 0;
1733917347
if (outofbandVarianceMarkerHandler) {
@@ -17350,7 +17358,8 @@ namespace ts {
1735017358
sourceIdStack: sourceStack.map(t => t.id),
1735117359
targetId: target.id,
1735217360
targetIdStack: targetStack.map(t => t.id),
17353-
depth,
17361+
depth: sourceDepth,
17362+
targetDepth
1735417363
});
1735517364
}
1735617365

@@ -17359,9 +17368,14 @@ namespace ts {
1735917368
outofbandVarianceMarkerHandler = originalHandler;
1736017369
}
1736117370
expandingFlags = saveExpandingFlags;
17362-
depth--;
17371+
if (!constantSource) {
17372+
sourceDepth--;
17373+
}
17374+
if (!constantTarget) {
17375+
targetDepth--;
17376+
}
1736317377
if (result) {
17364-
if (result === Ternary.True || depth === 0) {
17378+
if (result === Ternary.True || (sourceDepth === 0 && targetDepth === 0)) {
1736517379
if (result === Ternary.True || result === Ternary.Maybe) {
1736617380
// If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe
1736717381
// results as having succeeded once we reach depth 0, but never record Ternary.Unknown results.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts(13,5): error TS2322: Type 'ReturnType<T[M]>' is not assignable to type 'A'.
2+
Type 'unknown' is not assignable to type 'A'.
3+
Type 'ReturnType<T[keyof T]>' is not assignable to type 'A'.
4+
Type 'unknown' is not assignable to type 'A'.
5+
Type 'ReturnType<T[string | number | symbol]>' is not assignable to type 'A'.
6+
Type 'unknown' is not assignable to type 'A'.
7+
Type 'ReturnType<T[string]> | ReturnType<T[number]> | ReturnType<T[symbol]>' is not assignable to type 'A'.
8+
Type 'ReturnType<T[number]>' is not assignable to type 'A'.
9+
Type 'unknown' is not assignable to type 'A'.
10+
Type 'ReturnType<FunctionsObj<T>[number]>' is not assignable to type 'A'.
11+
Type 'unknown' is not assignable to type 'A'.
12+
Property 'x' is missing in type '{}' but required in type 'A'.
13+
14+
15+
==== tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts (1 errors) ====
16+
interface A { x: number }
17+
18+
declare function isA(a: unknown): a is A;
19+
20+
type FunctionsObj<T> = {
21+
[K in keyof T]: () => unknown
22+
}
23+
24+
function g<
25+
T extends FunctionsObj<T>,
26+
M extends keyof T
27+
>(a2: ReturnType<T[M]>, x: A) {
28+
x = a2;
29+
~
30+
!!! error TS2322: Type 'ReturnType<T[M]>' is not assignable to type 'A'.
31+
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
32+
!!! error TS2322: Type 'ReturnType<T[keyof T]>' is not assignable to type 'A'.
33+
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
34+
!!! error TS2322: Type 'ReturnType<T[string | number | symbol]>' is not assignable to type 'A'.
35+
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
36+
!!! error TS2322: Type 'ReturnType<T[string]> | ReturnType<T[number]> | ReturnType<T[symbol]>' is not assignable to type 'A'.
37+
!!! error TS2322: Type 'ReturnType<T[number]>' is not assignable to type 'A'.
38+
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
39+
!!! error TS2322: Type 'ReturnType<FunctionsObj<T>[number]>' is not assignable to type 'A'.
40+
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
41+
!!! error TS2322: Property 'x' is missing in type '{}' but required in type 'A'.
42+
!!! related TS2728 tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts:1:15: 'x' is declared here.
43+
}
44+
45+
// Original CFA report of the above issue
46+
47+
function g2<
48+
T extends FunctionsObj<T>,
49+
M extends keyof T
50+
>(a2: ReturnType<T[M]>) {
51+
if (isA(a2)) {
52+
// a2 is not narrowed
53+
a2.x // error, but should be ok
54+
}
55+
}
56+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//// [genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts]
2+
interface A { x: number }
3+
4+
declare function isA(a: unknown): a is A;
5+
6+
type FunctionsObj<T> = {
7+
[K in keyof T]: () => unknown
8+
}
9+
10+
function g<
11+
T extends FunctionsObj<T>,
12+
M extends keyof T
13+
>(a2: ReturnType<T[M]>, x: A) {
14+
x = a2;
15+
}
16+
17+
// Original CFA report of the above issue
18+
19+
function g2<
20+
T extends FunctionsObj<T>,
21+
M extends keyof T
22+
>(a2: ReturnType<T[M]>) {
23+
if (isA(a2)) {
24+
// a2 is not narrowed
25+
a2.x // error, but should be ok
26+
}
27+
}
28+
29+
30+
//// [genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.js]
31+
function g(a2, x) {
32+
x = a2;
33+
}
34+
// Original CFA report of the above issue
35+
function g2(a2) {
36+
if (isA(a2)) {
37+
// a2 is not narrowed
38+
a2.x; // error, but should be ok
39+
}
40+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
=== tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts ===
2+
interface A { x: number }
3+
>A : Symbol(A, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 0))
4+
>x : Symbol(A.x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 13))
5+
6+
declare function isA(a: unknown): a is A;
7+
>isA : Symbol(isA, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 25))
8+
>a : Symbol(a, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 2, 21))
9+
>a : Symbol(a, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 2, 21))
10+
>A : Symbol(A, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 0))
11+
12+
type FunctionsObj<T> = {
13+
>FunctionsObj : Symbol(FunctionsObj, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 2, 41))
14+
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 4, 18))
15+
16+
[K in keyof T]: () => unknown
17+
>K : Symbol(K, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 5, 5))
18+
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 4, 18))
19+
}
20+
21+
function g<
22+
>g : Symbol(g, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 6, 1))
23+
24+
T extends FunctionsObj<T>,
25+
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 8, 11))
26+
>FunctionsObj : Symbol(FunctionsObj, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 2, 41))
27+
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 8, 11))
28+
29+
M extends keyof T
30+
>M : Symbol(M, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 9, 30))
31+
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 8, 11))
32+
33+
>(a2: ReturnType<T[M]>, x: A) {
34+
>a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 11, 2))
35+
>ReturnType : Symbol(ReturnType, Decl(lib.es5.d.ts, --, --))
36+
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 8, 11))
37+
>M : Symbol(M, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 9, 30))
38+
>x : Symbol(x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 11, 23))
39+
>A : Symbol(A, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 0))
40+
41+
x = a2;
42+
>x : Symbol(x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 11, 23))
43+
>a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 11, 2))
44+
}
45+
46+
// Original CFA report of the above issue
47+
48+
function g2<
49+
>g2 : Symbol(g2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 13, 1))
50+
51+
T extends FunctionsObj<T>,
52+
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 17, 12))
53+
>FunctionsObj : Symbol(FunctionsObj, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 2, 41))
54+
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 17, 12))
55+
56+
M extends keyof T
57+
>M : Symbol(M, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 18, 30))
58+
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 17, 12))
59+
60+
>(a2: ReturnType<T[M]>) {
61+
>a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 20, 2))
62+
>ReturnType : Symbol(ReturnType, Decl(lib.es5.d.ts, --, --))
63+
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 17, 12))
64+
>M : Symbol(M, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 18, 30))
65+
66+
if (isA(a2)) {
67+
>isA : Symbol(isA, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 25))
68+
>a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 20, 2))
69+
70+
// a2 is not narrowed
71+
a2.x // error, but should be ok
72+
>a2.x : Symbol(A.x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 13))
73+
>a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 20, 2))
74+
>x : Symbol(A.x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 13))
75+
}
76+
}
77+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
=== tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts ===
2+
interface A { x: number }
3+
>x : number
4+
5+
declare function isA(a: unknown): a is A;
6+
>isA : (a: unknown) => a is A
7+
>a : unknown
8+
9+
type FunctionsObj<T> = {
10+
>FunctionsObj : FunctionsObj<T>
11+
12+
[K in keyof T]: () => unknown
13+
}
14+
15+
function g<
16+
>g : <T extends FunctionsObj<T>, M extends keyof T>(a2: ReturnType<T[M]>, x: A) => void
17+
18+
T extends FunctionsObj<T>,
19+
M extends keyof T
20+
>(a2: ReturnType<T[M]>, x: A) {
21+
>a2 : ReturnType<T[M]>
22+
>x : A
23+
24+
x = a2;
25+
>x = a2 : ReturnType<T[M]>
26+
>x : A
27+
>a2 : ReturnType<T[M]>
28+
}
29+
30+
// Original CFA report of the above issue
31+
32+
function g2<
33+
>g2 : <T extends FunctionsObj<T>, M extends keyof T>(a2: ReturnType<T[M]>) => void
34+
35+
T extends FunctionsObj<T>,
36+
M extends keyof T
37+
>(a2: ReturnType<T[M]>) {
38+
>a2 : ReturnType<T[M]>
39+
40+
if (isA(a2)) {
41+
>isA(a2) : boolean
42+
>isA : (a: unknown) => a is A
43+
>a2 : ReturnType<T[M]>
44+
45+
// a2 is not narrowed
46+
a2.x // error, but should be ok
47+
>a2.x : number
48+
>a2 : ReturnType<T[M]> & A
49+
>x : number
50+
}
51+
}
52+

tests/baselines/reference/infiniteConstraints.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' ca
22
tests/cases/compiler/infiniteConstraints.ts(31,43): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
33
tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
44
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.
5+
tests/cases/compiler/infiniteConstraints.ts(48,27): error TS2321: Excessive stack depth comparing types 'Conv<ExactExtract<U, T>, ExactExtract<U, T>>' and 'unknown[]'.
56

67

7-
==== tests/cases/compiler/infiniteConstraints.ts (4 errors) ====
8+
==== tests/cases/compiler/infiniteConstraints.ts (5 errors) ====
89
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
910

1011
type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
@@ -63,4 +64,6 @@ tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' c
6364

6465
type Conv<T, U = T> =
6566
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];
67+
~~~~~~~~~~~~~~~~~~~~~~~~
68+
!!! error TS2321: Excessive stack depth comparing types 'Conv<ExactExtract<U, T>, ExactExtract<U, T>>' and 'unknown[]'.
6669

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
interface A { x: number }
2+
3+
declare function isA(a: unknown): a is A;
4+
5+
type FunctionsObj<T> = {
6+
[K in keyof T]: () => unknown
7+
}
8+
9+
function g<
10+
T extends FunctionsObj<T>,
11+
M extends keyof T
12+
>(a2: ReturnType<T[M]>, x: A) {
13+
x = a2;
14+
}
15+
16+
// Original CFA report of the above issue
17+
18+
function g2<
19+
T extends FunctionsObj<T>,
20+
M extends keyof T
21+
>(a2: ReturnType<T[M]>) {
22+
if (isA(a2)) {
23+
// a2 is not narrowed
24+
a2.x // error, but should be ok
25+
}
26+
}

0 commit comments

Comments
 (0)