Skip to content

Commit

Permalink
Reduce effective maximum constraint depth for instances of the same c…
Browse files Browse the repository at this point in the history
…onditional
  • Loading branch information
weswigham committed Jun 12, 2019
1 parent 520f7e8 commit a0e77ae
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 6 deletions.
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);
}
}

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];

0 comments on commit a0e77ae

Please sign in to comment.