Skip to content

Commit bec8071

Browse files
authored
Simplify constraint depth limiter logic (microsoft#41972)
* Explore at least 10 levels of constraints before checking for deeply nested types * Simplify constraint depth limiter logic * Add regression test * Accept new baselines
1 parent b869c9c commit bec8071

File tree

5 files changed

+124
-22
lines changed

5 files changed

+124
-22
lines changed

src/compiler/checker.ts

+8-22
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,6 @@ namespace ts {
336336
let totalInstantiationCount = 0;
337337
let instantiationCount = 0;
338338
let instantiationDepth = 0;
339-
let constraintDepth = 0;
340339
let currentNode: Node | undefined;
341340

342341
const typeCatalog: Type[] = []; // NB: id is index + 1
@@ -11033,7 +11032,6 @@ namespace ts {
1103311032
if (type.resolvedBaseConstraint) {
1103411033
return type.resolvedBaseConstraint;
1103511034
}
11036-
let nonTerminating = false;
1103711035
const stack: Type[] = [];
1103811036
return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type);
1103911037

@@ -11042,22 +11040,16 @@ namespace ts {
1104211040
if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) {
1104311041
return circularConstraintType;
1104411042
}
11045-
if (constraintDepth >= 50) {
11046-
// We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a
11047-
// very high likelihood we're dealing with an infinite generic type that perpetually generates
11048-
// new type identities as we descend into it. We stop the recursion here and mark this type
11049-
// and the outer types as having circular constraints.
11050-
tracing.instant(tracing.Phase.CheckTypes, "getImmediateBaseConstraint_DepthLimit", { typeId: t.id, originalTypeId: type.id, depth: constraintDepth });
11051-
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
11052-
nonTerminating = true;
11053-
return t.immediateBaseConstraint = noConstraintType;
11054-
}
1105511043
let result;
11056-
if (!isDeeplyNestedType(t, stack, stack.length)) {
11044+
// We always explore at least 10 levels of nested constraints. Thereafter, we continue to explore
11045+
// up to 50 levels of nested constraints provided there are no "deeply nested" types on the stack
11046+
// (i.e. no types for which five instantiations have been recorded on the stack). If we reach 50
11047+
// levels of nesting, we are presumably exploring a repeating pattern with a long cycle that hasn't
11048+
// yet triggered the deeply nested limiter. We have no test cases that actually get to 50 levels of
11049+
// nesting, so it is effectively just a safety stop.
11050+
if (stack.length < 10 || stack.length < 50 && !isDeeplyNestedType(t, stack, stack.length)) {
1105711051
stack.push(t);
11058-
constraintDepth++;
1105911052
result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false));
11060-
constraintDepth--;
1106111053
stack.pop();
1106211054
}
1106311055
if (!popTypeResolution()) {
@@ -11072,9 +11064,6 @@ namespace ts {
1107211064
}
1107311065
result = circularConstraintType;
1107411066
}
11075-
if (nonTerminating) {
11076-
result = circularConstraintType;
11077-
}
1107811067
t.immediateBaseConstraint = result || noConstraintType;
1107911068
}
1108011069
return t.immediateBaseConstraint;
@@ -11125,10 +11114,7 @@ namespace ts {
1112511114
}
1112611115
if (t.flags & TypeFlags.Conditional) {
1112711116
const constraint = getConstraintFromConditionalType(<ConditionalType>t);
11128-
constraintDepth++; // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward)
11129-
const result = constraint && getBaseConstraint(constraint);
11130-
constraintDepth--;
11131-
return result;
11117+
return constraint && getBaseConstraint(constraint);
1113211118
}
1113311119
if (t.flags & TypeFlags.Substitution) {
1113411120
return getBaseConstraint((<SubstitutionType>t).substitute);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//// [deeplyNestedConstraints.ts]
2+
// Repro from #41931
3+
4+
type Enum = Record<string, string | number>;
5+
6+
type TypeMap<E extends Enum> = { [key in E[keyof E]]: number | boolean | string | number[] };
7+
8+
class BufferPool<E extends Enum, M extends TypeMap<E>> {
9+
setArray2<K extends E[keyof E]>(_: K, array: Extract<M[K], ArrayLike<any>>) {
10+
array.length; // Requires exploration of >5 levels of constraints
11+
}
12+
}
13+
14+
15+
//// [deeplyNestedConstraints.js]
16+
"use strict";
17+
// Repro from #41931
18+
var BufferPool = /** @class */ (function () {
19+
function BufferPool() {
20+
}
21+
BufferPool.prototype.setArray2 = function (_, array) {
22+
array.length; // Requires exploration of >5 levels of constraints
23+
};
24+
return BufferPool;
25+
}());
26+
27+
28+
//// [deeplyNestedConstraints.d.ts]
29+
declare type Enum = Record<string, string | number>;
30+
declare type TypeMap<E extends Enum> = {
31+
[key in E[keyof E]]: number | boolean | string | number[];
32+
};
33+
declare class BufferPool<E extends Enum, M extends TypeMap<E>> {
34+
setArray2<K extends E[keyof E]>(_: K, array: Extract<M[K], ArrayLike<any>>): void;
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=== tests/cases/compiler/deeplyNestedConstraints.ts ===
2+
// Repro from #41931
3+
4+
type Enum = Record<string, string | number>;
5+
>Enum : Symbol(Enum, Decl(deeplyNestedConstraints.ts, 0, 0))
6+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
7+
8+
type TypeMap<E extends Enum> = { [key in E[keyof E]]: number | boolean | string | number[] };
9+
>TypeMap : Symbol(TypeMap, Decl(deeplyNestedConstraints.ts, 2, 44))
10+
>E : Symbol(E, Decl(deeplyNestedConstraints.ts, 4, 13))
11+
>Enum : Symbol(Enum, Decl(deeplyNestedConstraints.ts, 0, 0))
12+
>key : Symbol(key, Decl(deeplyNestedConstraints.ts, 4, 34))
13+
>E : Symbol(E, Decl(deeplyNestedConstraints.ts, 4, 13))
14+
>E : Symbol(E, Decl(deeplyNestedConstraints.ts, 4, 13))
15+
16+
class BufferPool<E extends Enum, M extends TypeMap<E>> {
17+
>BufferPool : Symbol(BufferPool, Decl(deeplyNestedConstraints.ts, 4, 93))
18+
>E : Symbol(E, Decl(deeplyNestedConstraints.ts, 6, 17))
19+
>Enum : Symbol(Enum, Decl(deeplyNestedConstraints.ts, 0, 0))
20+
>M : Symbol(M, Decl(deeplyNestedConstraints.ts, 6, 32))
21+
>TypeMap : Symbol(TypeMap, Decl(deeplyNestedConstraints.ts, 2, 44))
22+
>E : Symbol(E, Decl(deeplyNestedConstraints.ts, 6, 17))
23+
24+
setArray2<K extends E[keyof E]>(_: K, array: Extract<M[K], ArrayLike<any>>) {
25+
>setArray2 : Symbol(BufferPool.setArray2, Decl(deeplyNestedConstraints.ts, 6, 56))
26+
>K : Symbol(K, Decl(deeplyNestedConstraints.ts, 7, 14))
27+
>E : Symbol(E, Decl(deeplyNestedConstraints.ts, 6, 17))
28+
>E : Symbol(E, Decl(deeplyNestedConstraints.ts, 6, 17))
29+
>_ : Symbol(_, Decl(deeplyNestedConstraints.ts, 7, 36))
30+
>K : Symbol(K, Decl(deeplyNestedConstraints.ts, 7, 14))
31+
>array : Symbol(array, Decl(deeplyNestedConstraints.ts, 7, 41))
32+
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
33+
>M : Symbol(M, Decl(deeplyNestedConstraints.ts, 6, 32))
34+
>K : Symbol(K, Decl(deeplyNestedConstraints.ts, 7, 14))
35+
>ArrayLike : Symbol(ArrayLike, Decl(lib.es5.d.ts, --, --))
36+
37+
array.length; // Requires exploration of >5 levels of constraints
38+
>array.length : Symbol(length, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
39+
>array : Symbol(array, Decl(deeplyNestedConstraints.ts, 7, 41))
40+
>length : Symbol(length, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
41+
}
42+
}
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
=== tests/cases/compiler/deeplyNestedConstraints.ts ===
2+
// Repro from #41931
3+
4+
type Enum = Record<string, string | number>;
5+
>Enum : Record<string, string | number>
6+
7+
type TypeMap<E extends Enum> = { [key in E[keyof E]]: number | boolean | string | number[] };
8+
>TypeMap : TypeMap<E>
9+
10+
class BufferPool<E extends Enum, M extends TypeMap<E>> {
11+
>BufferPool : BufferPool<E, M>
12+
13+
setArray2<K extends E[keyof E]>(_: K, array: Extract<M[K], ArrayLike<any>>) {
14+
>setArray2 : <K extends E[keyof E]>(_: K, array: Extract<M[K], ArrayLike<any>>) => void
15+
>_ : K
16+
>array : Extract<M[K], ArrayLike<any>>
17+
18+
array.length; // Requires exploration of >5 levels of constraints
19+
>array.length : number
20+
>array : Extract<M[K], ArrayLike<any>>
21+
>length : number
22+
}
23+
}
24+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @strict: true
2+
// @declaration: true
3+
4+
// Repro from #41931
5+
6+
type Enum = Record<string, string | number>;
7+
8+
type TypeMap<E extends Enum> = { [key in E[keyof E]]: number | boolean | string | number[] };
9+
10+
class BufferPool<E extends Enum, M extends TypeMap<E>> {
11+
setArray2<K extends E[keyof E]>(_: K, array: Extract<M[K], ArrayLike<any>>) {
12+
array.length; // Requires exploration of >5 levels of constraints
13+
}
14+
}

0 commit comments

Comments
 (0)