Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce effective maximum constraint depth for instances of the same conditional #31880

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ namespace ts {
let enumCount = 0;
let instantiationDepth = 0;
let constraintDepth = 0;
const perInstanceDepth = createMap<number>();
let currentNode: Node | undefined;

const emptySymbols = createSymbolTable();
Expand Down Expand Up @@ -7815,7 +7816,7 @@ namespace ts {
if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) {
return circularConstraintType;
}
if (constraintDepth >= 50) {
if (checkMaximumConstraintDepthExceeded(t)) {
// We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a
// very high likelihood we're dealing with an infinite generic type that perpetually generates
// new type identities as we descend into it. We stop the recursion here and mark this type
Expand All @@ -7824,9 +7825,9 @@ namespace ts {
nonTerminating = true;
return t.immediateBaseConstraint = noConstraintType;
}
constraintDepth++;
incrementConstraintDepth(t);
let result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false));
constraintDepth--;
decrementConstraintDepth(t);
if (!popTypeResolution()) {
if (t.flags & TypeFlags.TypeParameter) {
const errorNode = getConstraintDeclaration(<TypeParameter>t);
Expand All @@ -7847,6 +7848,28 @@ namespace ts {
return t.immediateBaseConstraint;
}

function checkMaximumConstraintDepthExceeded(t: Type) {
return constraintDepth >= 50 || (t.flags & TypeFlags.Conditional && (perInstanceDepth.get("" + getTypeId(getNodeLinks((t as ConditionalType).root.node).resolvedType!)) || 0) >= 10);
}

function incrementConstraintDepth(t: Type) {
constraintDepth++;
if (t.flags & TypeFlags.Conditional) {
const base = getNodeLinks((t as ConditionalType).root.node).resolvedType!;
const id = "" + getTypeId(base);
perInstanceDepth.set(id, (perInstanceDepth.get(id) || 0) + 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why all the extra indirections here? Couldn't you just store the counter as a property in the resolved type?

}
}

function decrementConstraintDepth(t: Type) {
constraintDepth--;
if (t.flags & TypeFlags.Conditional) {
const base = getNodeLinks((t as ConditionalType).root.node).resolvedType!;
const id = "" + getTypeId(base);
perInstanceDepth.set(id, perInstanceDepth.get(id)! - 1);
}
}

function getBaseConstraint(t: Type): Type | undefined {
const c = getImmediateBaseConstraint(t);
return c !== noConstraintType && c !== circularConstraintType ? c : undefined;
Expand Down Expand Up @@ -7883,9 +7906,9 @@ namespace ts {
}
if (t.flags & TypeFlags.Conditional) {
const constraint = getConstraintFromConditionalType(<ConditionalType>t);
constraintDepth++; // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward)
incrementConstraintDepth(t); // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward)
const result = constraint && getBaseConstraint(constraint);
constraintDepth--;
decrementConstraintDepth(t);
return result;
}
if (t.flags & TypeFlags.Substitution) {
Expand Down
18 changes: 18 additions & 0 deletions tests/baselines/reference/conditionalPrependNoHang.errors.txt

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions tests/baselines/reference/conditionalPrependNoHang.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//// [conditionalPrependNoHang.ts]
export type Prepend<Elm, T extends unknown[]> =
T extends unknown ?
((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 :
never :
never;
export type ExactExtract<T, U> = T extends U ? U extends T ? T : never : never;

type Conv<T, U = T> =
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];

//// [conditionalPrependNoHang.js]
"use strict";
exports.__esModule = true;
49 changes: 49 additions & 0 deletions tests/baselines/reference/conditionalPrependNoHang.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
=== tests/cases/compiler/conditionalPrependNoHang.ts ===
export type Prepend<Elm, T extends unknown[]> =
>Prepend : Symbol(Prepend, Decl(conditionalPrependNoHang.ts, 0, 0))
>Elm : Symbol(Elm, Decl(conditionalPrependNoHang.ts, 0, 20))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 0, 24))

T extends unknown ?
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 0, 24))

((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 :
>arg : Symbol(arg, Decl(conditionalPrependNoHang.ts, 2, 4))
>Elm : Symbol(Elm, Decl(conditionalPrependNoHang.ts, 0, 20))
>rest : Symbol(rest, Decl(conditionalPrependNoHang.ts, 2, 13))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 0, 24))
>args : Symbol(args, Decl(conditionalPrependNoHang.ts, 2, 45))
>T2 : Symbol(T2, Decl(conditionalPrependNoHang.ts, 2, 59))
>T2 : Symbol(T2, Decl(conditionalPrependNoHang.ts, 2, 59))

never :
never;
export type ExactExtract<T, U> = T extends U ? U extends T ? T : never : never;
>ExactExtract : Symbol(ExactExtract, Decl(conditionalPrependNoHang.ts, 4, 8))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 5, 25))
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 5, 27))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 5, 25))
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 5, 27))
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 5, 27))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 5, 25))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 5, 25))

type Conv<T, U = T> =
>Conv : Symbol(Conv, Decl(conditionalPrependNoHang.ts, 5, 79))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 7, 12))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))

{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];
>0 : Symbol(0, Decl(conditionalPrependNoHang.ts, 8, 3))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))
>1 : Symbol(1, Decl(conditionalPrependNoHang.ts, 8, 11))
>Prepend : Symbol(Prepend, Decl(conditionalPrependNoHang.ts, 0, 0))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))
>Conv : Symbol(Conv, Decl(conditionalPrependNoHang.ts, 5, 79))
>ExactExtract : Symbol(ExactExtract, Decl(conditionalPrependNoHang.ts, 4, 8))
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 7, 12))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))
>U : Symbol(U, Decl(conditionalPrependNoHang.ts, 7, 12))
>T : Symbol(T, Decl(conditionalPrependNoHang.ts, 7, 10))

22 changes: 22 additions & 0 deletions tests/baselines/reference/conditionalPrependNoHang.types

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion tests/baselines/reference/infiniteConstraints.errors.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
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; }>'.
tests/cases/compiler/infiniteConstraints.ts(3,37): error TS2589: Type instantiation is excessively deep and possibly infinite.
tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
tests/cases/compiler/infiniteConstraints.ts(31,43): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.


==== tests/cases/compiler/infiniteConstraints.ts (4 errors) ====
==== tests/cases/compiler/infiniteConstraints.ts (6 errors) ====
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint

type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2536: Type '"val"' cannot be used to index type 'Extract<B[Exclude<keyof B, K>], { val: string; }>'.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
type T2<B extends { [K in keyof B]: B[Exclude<keyof B, K>]["val"] }> = B;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
Expand Down
9 changes: 9 additions & 0 deletions tests/cases/compiler/conditionalPrependNoHang.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type Prepend<Elm, T extends unknown[]> =
T extends unknown ?
((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 :
never :
never;
export type ExactExtract<T, U> = T extends U ? U extends T ? T : never : never;

type Conv<T, U = T> =
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];