Skip to content

Commit a0e77ae

Browse files
committed
Reduce effective maximum constraint depth for instances of the same conditional
1 parent 520f7e8 commit a0e77ae

7 files changed

+147
-6
lines changed

src/compiler/checker.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ namespace ts {
6767
let enumCount = 0;
6868
let instantiationDepth = 0;
6969
let constraintDepth = 0;
70+
const perInstanceDepth = createMap<number>();
7071
let currentNode: Node | undefined;
7172

7273
const emptySymbols = createSymbolTable();
@@ -7815,7 +7816,7 @@ namespace ts {
78157816
if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) {
78167817
return circularConstraintType;
78177818
}
7818-
if (constraintDepth >= 50) {
7819+
if (checkMaximumConstraintDepthExceeded(t)) {
78197820
// We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a
78207821
// very high likelihood we're dealing with an infinite generic type that perpetually generates
78217822
// new type identities as we descend into it. We stop the recursion here and mark this type
@@ -7824,9 +7825,9 @@ namespace ts {
78247825
nonTerminating = true;
78257826
return t.immediateBaseConstraint = noConstraintType;
78267827
}
7827-
constraintDepth++;
7828+
incrementConstraintDepth(t);
78287829
let result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false));
7829-
constraintDepth--;
7830+
decrementConstraintDepth(t);
78307831
if (!popTypeResolution()) {
78317832
if (t.flags & TypeFlags.TypeParameter) {
78327833
const errorNode = getConstraintDeclaration(<TypeParameter>t);
@@ -7847,6 +7848,28 @@ namespace ts {
78477848
return t.immediateBaseConstraint;
78487849
}
78497850

7851+
function checkMaximumConstraintDepthExceeded(t: Type) {
7852+
return constraintDepth >= 50 || (t.flags & TypeFlags.Conditional && (perInstanceDepth.get("" + getTypeId(getNodeLinks((t as ConditionalType).root.node).resolvedType!)) || 0) >= 10);
7853+
}
7854+
7855+
function incrementConstraintDepth(t: Type) {
7856+
constraintDepth++;
7857+
if (t.flags & TypeFlags.Conditional) {
7858+
const base = getNodeLinks((t as ConditionalType).root.node).resolvedType!;
7859+
const id = "" + getTypeId(base);
7860+
perInstanceDepth.set(id, (perInstanceDepth.get(id) || 0) + 1);
7861+
}
7862+
}
7863+
7864+
function decrementConstraintDepth(t: Type) {
7865+
constraintDepth--;
7866+
if (t.flags & TypeFlags.Conditional) {
7867+
const base = getNodeLinks((t as ConditionalType).root.node).resolvedType!;
7868+
const id = "" + getTypeId(base);
7869+
perInstanceDepth.set(id, perInstanceDepth.get(id)! - 1);
7870+
}
7871+
}
7872+
78507873
function getBaseConstraint(t: Type): Type | undefined {
78517874
const c = getImmediateBaseConstraint(t);
78527875
return c !== noConstraintType && c !== circularConstraintType ? c : undefined;
@@ -7883,9 +7906,9 @@ namespace ts {
78837906
}
78847907
if (t.flags & TypeFlags.Conditional) {
78857908
const constraint = getConstraintFromConditionalType(<ConditionalType>t);
7886-
constraintDepth++; // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward)
7909+
incrementConstraintDepth(t); // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward)
78877910
const result = constraint && getBaseConstraint(constraint);
7888-
constraintDepth--;
7911+
decrementConstraintDepth(t);
78897912
return result;
78907913
}
78917914
if (t.flags & TypeFlags.Substitution) {

tests/baselines/reference/conditionalPrependNoHang.errors.txt

Lines changed: 18 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//// [conditionalPrependNoHang.ts]
2+
export type Prepend<Elm, T extends unknown[]> =
3+
T extends unknown ?
4+
((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 :
5+
never :
6+
never;
7+
export type ExactExtract<T, U> = T extends U ? U extends T ? T : never : never;
8+
9+
type Conv<T, U = T> =
10+
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];
11+
12+
//// [conditionalPrependNoHang.js]
13+
"use strict";
14+
exports.__esModule = true;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
=== tests/cases/compiler/conditionalPrependNoHang.ts ===
2+
export type Prepend<Elm, T extends unknown[]> =
3+
>Prepend : Symbol(Prepend, Decl(conditionalPrependNoHang.ts, 0, 0))
4+
>Elm : Symbol(Elm, Decl(conditionalPrependNoHang.ts, 0, 20))
5+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 0, 24))
6+
7+
T extends unknown ?
8+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 0, 24))
9+
10+
((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 :
11+
>arg : Symbol(arg, Decl(conditionalPrependNoHang.ts, 2, 4))
12+
>Elm : Symbol(Elm, Decl(conditionalPrependNoHang.ts, 0, 20))
13+
>rest : Symbol(rest, Decl(conditionalPrependNoHang.ts, 2, 13))
14+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 0, 24))
15+
>args : Symbol(args, Decl(conditionalPrependNoHang.ts, 2, 45))
16+
>T2 : Symbol(T2, Decl(conditionalPrependNoHang.ts, 2, 59))
17+
>T2 : Symbol(T2, Decl(conditionalPrependNoHang.ts, 2, 59))
18+
19+
never :
20+
never;
21+
export type ExactExtract<T, U> = T extends U ? U extends T ? T : never : never;
22+
>ExactExtract : Symbol(ExactExtract, Decl(conditionalPrependNoHang.ts, 4, 8))
23+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 5, 25))
24+
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 5, 27))
25+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 5, 25))
26+
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 5, 27))
27+
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 5, 27))
28+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 5, 25))
29+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 5, 25))
30+
31+
type Conv<T, U = T> =
32+
>Conv : Symbol(Conv, Decl(conditionalPrependNoHang.ts, 5, 79))
33+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))
34+
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 7, 12))
35+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))
36+
37+
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];
38+
>0 : Symbol(0, Decl(conditionalPrependNoHang.ts, 8, 3))
39+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))
40+
>1 : Symbol(1, Decl(conditionalPrependNoHang.ts, 8, 11))
41+
>Prepend : Symbol(Prepend, Decl(conditionalPrependNoHang.ts, 0, 0))
42+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))
43+
>Conv : Symbol(Conv, Decl(conditionalPrependNoHang.ts, 5, 79))
44+
>ExactExtract : Symbol(ExactExtract, Decl(conditionalPrependNoHang.ts, 4, 8))
45+
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 7, 12))
46+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))
47+
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 7, 12))
48+
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))
49+

tests/baselines/reference/conditionalPrependNoHang.types

Lines changed: 22 additions & 0 deletions
Large diffs are not rendered by default.

tests/baselines/reference/infiniteConstraints.errors.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1+
tests/cases/compiler/infiniteConstraints.ts(3,37): error TS2536: Type '"val"' cannot be used to index type 'Extract<B[Exclude<keyof B, K>], { val: string; }>'.
2+
tests/cases/compiler/infiniteConstraints.ts(3,37): error TS2589: Type instantiation is excessively deep and possibly infinite.
13
tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
24
tests/cases/compiler/infiniteConstraints.ts(31,43): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
35
tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
46
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.
57

68

7-
==== tests/cases/compiler/infiniteConstraints.ts (4 errors) ====
9+
==== tests/cases/compiler/infiniteConstraints.ts (6 errors) ====
810
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
911

1012
type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
13+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14+
!!! error TS2536: Type '"val"' cannot be used to index type 'Extract<B[Exclude<keyof B, K>], { val: string; }>'.
15+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16+
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
1117
type T2<B extends { [K in keyof B]: B[Exclude<keyof B, K>]["val"] }> = B;
1218
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1319
!!! error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export type Prepend<Elm, T extends unknown[]> =
2+
T extends unknown ?
3+
((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 :
4+
never :
5+
never;
6+
export type ExactExtract<T, U> = T extends U ? U extends T ? T : never : never;
7+
8+
type Conv<T, U = T> =
9+
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];

0 commit comments

Comments
 (0)