Skip to content

Commit 767cf7d

Browse files
committed
Merge remote-tracking branch 'upstream/master' into fix41977
2 parents 376a03b + 0d8a868 commit 767cf7d

File tree

10 files changed

+158
-27
lines changed

10 files changed

+158
-27
lines changed

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/compiler/checker.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ namespace ts {
337337
let totalInstantiationCount = 0;
338338
let instantiationCount = 0;
339339
let instantiationDepth = 0;
340-
let constraintDepth = 0;
341340
let currentNode: Node | undefined;
342341

343342
const typeCatalog: Type[] = []; // NB: id is index + 1
@@ -11073,7 +11072,6 @@ namespace ts {
1107311072
if (type.resolvedBaseConstraint) {
1107411073
return type.resolvedBaseConstraint;
1107511074
}
11076-
let nonTerminating = false;
1107711075
const stack: Type[] = [];
1107811076
return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type);
1107911077

@@ -11082,22 +11080,16 @@ namespace ts {
1108211080
if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) {
1108311081
return circularConstraintType;
1108411082
}
11085-
if (constraintDepth >= 50) {
11086-
// We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a
11087-
// very high likelihood we're dealing with an infinite generic type that perpetually generates
11088-
// new type identities as we descend into it. We stop the recursion here and mark this type
11089-
// and the outer types as having circular constraints.
11090-
tracing.instant(tracing.Phase.CheckTypes, "getImmediateBaseConstraint_DepthLimit", { typeId: t.id, originalTypeId: type.id, depth: constraintDepth });
11091-
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
11092-
nonTerminating = true;
11093-
return t.immediateBaseConstraint = noConstraintType;
11094-
}
1109511083
let result;
11096-
if (!isDeeplyNestedType(t, stack, stack.length)) {
11084+
// We always explore at least 10 levels of nested constraints. Thereafter, we continue to explore
11085+
// up to 50 levels of nested constraints provided there are no "deeply nested" types on the stack
11086+
// (i.e. no types for which five instantiations have been recorded on the stack). If we reach 50
11087+
// levels of nesting, we are presumably exploring a repeating pattern with a long cycle that hasn't
11088+
// yet triggered the deeply nested limiter. We have no test cases that actually get to 50 levels of
11089+
// nesting, so it is effectively just a safety stop.
11090+
if (stack.length < 10 || stack.length < 50 && !isDeeplyNestedType(t, stack, stack.length)) {
1109711091
stack.push(t);
11098-
constraintDepth++;
1109911092
result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false));
11100-
constraintDepth--;
1110111093
stack.pop();
1110211094
}
1110311095
if (!popTypeResolution()) {
@@ -11112,9 +11104,6 @@ namespace ts {
1111211104
}
1111311105
result = circularConstraintType;
1111411106
}
11115-
if (nonTerminating) {
11116-
result = circularConstraintType;
11117-
}
1111811107
t.immediateBaseConstraint = result || noConstraintType;
1111911108
}
1112011109
return t.immediateBaseConstraint;
@@ -11165,10 +11154,7 @@ namespace ts {
1116511154
}
1116611155
if (t.flags & TypeFlags.Conditional) {
1116711156
const constraint = getConstraintFromConditionalType(<ConditionalType>t);
11168-
constraintDepth++; // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward)
11169-
const result = constraint && getBaseConstraint(constraint);
11170-
constraintDepth--;
11171-
return result;
11157+
return constraint && getBaseConstraint(constraint);
1117211158
}
1117311159
if (t.flags & TypeFlags.Substitution) {
1117411160
return getBaseConstraint((<SubstitutionType>t).substitute);

src/compiler/factory/nodeTests.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,8 +787,12 @@ namespace ts {
787787

788788
export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag {
789789
return node.kind === SyntaxKind.JSDocDeprecatedTag;
790+
}
790791

792+
export function isJSDocSeeTag(node: Node): node is JSDocSeeTag {
793+
return node.kind === SyntaxKind.JSDocSeeTag;
791794
}
795+
792796
export function isJSDocEnumTag(node: Node): node is JSDocEnumTag {
793797
return node.kind === SyntaxKind.JSDocEnumTag;
794798
}

src/services/utilities.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,7 +1391,23 @@ namespace ts {
13911391
return isInsideJsxElementTraversal(getTokenAtPosition(sourceFile, position));
13921392
}
13931393

1394-
export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind, sourceFile: SourceFile) {
1394+
export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind.OpenBraceToken | SyntaxKind.OpenParenToken | SyntaxKind.OpenBracketToken, sourceFile: SourceFile) {
1395+
const closeTokenText = tokenToString(token.kind)!;
1396+
const matchingTokenText = tokenToString(matchingTokenKind)!;
1397+
const tokenFullStart = token.getFullStart();
1398+
// Text-scan based fast path - can be bamboozled by comments and other trivia, but often provides
1399+
// a good, fast approximation without too much extra work in the cases where it fails.
1400+
const bestGuessIndex = sourceFile.text.lastIndexOf(matchingTokenText, tokenFullStart);
1401+
if (bestGuessIndex === -1) {
1402+
return undefined; // if the token text doesn't appear in the file, there can't be a match - super fast bail
1403+
}
1404+
// we can only use the textual result directly if we didn't have to count any close tokens within the range
1405+
if (sourceFile.text.lastIndexOf(closeTokenText, tokenFullStart - 1) < bestGuessIndex) {
1406+
const nodeAtGuess = findPrecedingToken(bestGuessIndex + 1, sourceFile);
1407+
if (nodeAtGuess && nodeAtGuess.kind === matchingTokenKind) {
1408+
return nodeAtGuess;
1409+
}
1410+
}
13951411
const tokenKind = token.kind;
13961412
let remainingMatchingTokens = 0;
13971413
while (true) {
@@ -1447,7 +1463,14 @@ namespace ts {
14471463
}
14481464

14491465
// Get info for an expression like `f <` that may be the start of type arguments.
1450-
export function getPossibleTypeArgumentsInfo(tokenIn: Node, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined {
1466+
export function getPossibleTypeArgumentsInfo(tokenIn: Node | undefined, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined {
1467+
// This is a rare case, but one that saves on a _lot_ of work if true - if the source file has _no_ `<` character,
1468+
// then there obviously can't be any type arguments - no expensive brace-matching backwards scanning required
1469+
1470+
if (sourceFile.text.lastIndexOf("<", tokenIn ? tokenIn.pos : sourceFile.text.length) === -1) {
1471+
return undefined;
1472+
}
1473+
14511474
let token: Node | undefined = tokenIn;
14521475
// This function determines if the node could be type argument position
14531476
// Since during editing, when type argument list is not complete,

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4507,6 +4507,7 @@ declare namespace ts {
45074507
function isJSDocProtectedTag(node: Node): node is JSDocProtectedTag;
45084508
function isJSDocReadonlyTag(node: Node): node is JSDocReadonlyTag;
45094509
function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag;
4510+
function isJSDocSeeTag(node: Node): node is JSDocSeeTag;
45104511
function isJSDocEnumTag(node: Node): node is JSDocEnumTag;
45114512
function isJSDocParameterTag(node: Node): node is JSDocParameterTag;
45124513
function isJSDocReturnTag(node: Node): node is JSDocReturnTag;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4507,6 +4507,7 @@ declare namespace ts {
45074507
function isJSDocProtectedTag(node: Node): node is JSDocProtectedTag;
45084508
function isJSDocReadonlyTag(node: Node): node is JSDocReadonlyTag;
45094509
function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag;
4510+
function isJSDocSeeTag(node: Node): node is JSDocSeeTag;
45104511
function isJSDocEnumTag(node: Node): node is JSDocEnumTag;
45114512
function isJSDocParameterTag(node: Node): node is JSDocParameterTag;
45124513
function isJSDocReturnTag(node: Node): node is JSDocReturnTag;
Lines changed: 35 additions & 0 deletions
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+
}
Lines changed: 43 additions & 0 deletions
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+
Lines changed: 24 additions & 0 deletions
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+
Lines changed: 14 additions & 0 deletions
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)