Skip to content
Open
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
19 changes: 19 additions & 0 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -29492,6 +29492,14 @@ func (c *Checker) getConstraintDeclaration(t *Type) *ast.Node {
return nil
}

// Limits on the size of a template literal type produced by getTemplateLiteralType. Recursive instantiations
// such as `Recur<any, `${S}_${S}`>` double the text (or the number of placeholders) on every iteration and
// exhaust memory long before the tail recursion limit in getConditionalType is reached (see #63271).
const (
maxTemplateLiteralTypeLength = 50_000_000
maxTemplateLiteralTypeSpans = 100_000
)

func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type {
unionIndex := core.FindIndex(types, func(t *Type) bool {
return t.flags&(TypeFlagsNever|TypeFlagsUnion) != 0
Expand All @@ -29511,6 +29519,8 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type {
var newTexts []string
var sb strings.Builder
sb.WriteString(texts[0])
textLength := 0 // combined length of the segments already moved into newTexts
tooLarge := false
var addSpans func([]string, []*Type) bool
addSpans = func(texts []string, types []*Type) bool {
for i, t := range types {
Expand All @@ -29527,15 +29537,24 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type {
case c.isGenericIndexType(t) || c.isPatternLiteralPlaceholderType(t):
newTypes = append(newTypes, t)
newTexts = append(newTexts, stringutil.CombineSurrogatePairs(sb.String()))
textLength += sb.Len()
sb.Reset()
sb.WriteString(texts[i+1])
default:
return false
}
if textLength+sb.Len() > maxTemplateLiteralTypeLength || len(newTypes) > maxTemplateLiteralTypeSpans {
tooLarge = true
return false
}
}
return true
}
if !addSpans(texts, types) {
if tooLarge {
c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite)
return c.errorType
}
return c.stringType
}
if len(newTypes) == 0 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
templateLiteralTypeExcessiveLength.ts(15,11): error TS2589: Type instantiation is excessively deep and possibly infinite.
templateLiteralTypeExcessiveLength.ts(20,11): error TS2589: Type instantiation is excessively deep and possibly infinite.
templateLiteralTypeExcessiveLength.ts(34,11): error TS2589: Type instantiation is excessively deep and possibly infinite.


==== templateLiteralTypeExcessiveLength.ts (3 errors) ====
// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and
// keeps tail-recursing forever. The checker must report an error instead of building an unbounded
// template literal type.

type Dec<N extends number> =
N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 :
N extends 2 ? any :
N extends 1 ? 0 : 0;

type Recur<N extends number, S extends string> =
N extends 0 ? S : Recur<Dec<N>, `${S}_${S}`>;

// The string literal doubles on every iteration.
type Explode = {
[P in Recur<5, "a"> as `${P}_key`]: any;
~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
};

// The number of placeholders doubles on every iteration.
type ExplodeSpans = {
[P in Recur<5, string> as `${P}_key`]: any;
~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
};

// Every text segment stays small, but the number of segments doubles on every iteration,
// so the combined text length grows without bound.
type A16 = "aaaaaaaaaaaaaaaa";
type A64 = `${A16}${A16}${A16}${A16}`;
type A256 = `${A64}${A64}${A64}${A64}`;
type A1024 = `${A256}${A256}${A256}${A256}`;

type RecurSegments<N extends number, S extends string> =
N extends 0 ? S : RecurSegments<Dec<N>, `${S}${string}${S}`>;

type ExplodeSegments = {
[P in RecurSegments<5, A1024> as `${P}_key`]: any;
~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
};

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//// [tests/cases/compiler/templateLiteralTypeExcessiveLength.ts] ////

//// [templateLiteralTypeExcessiveLength.ts]
// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and
// keeps tail-recursing forever. The checker must report an error instead of building an unbounded
// template literal type.

type Dec<N extends number> =
N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 :
N extends 2 ? any :
N extends 1 ? 0 : 0;

type Recur<N extends number, S extends string> =
N extends 0 ? S : Recur<Dec<N>, `${S}_${S}`>;

// The string literal doubles on every iteration.
type Explode = {
[P in Recur<5, "a"> as `${P}_key`]: any;
};

// The number of placeholders doubles on every iteration.
type ExplodeSpans = {
[P in Recur<5, string> as `${P}_key`]: any;
};

// Every text segment stays small, but the number of segments doubles on every iteration,
// so the combined text length grows without bound.
type A16 = "aaaaaaaaaaaaaaaa";
type A64 = `${A16}${A16}${A16}${A16}`;
type A256 = `${A64}${A64}${A64}${A64}`;
type A1024 = `${A256}${A256}${A256}${A256}`;

type RecurSegments<N extends number, S extends string> =
N extends 0 ? S : RecurSegments<Dec<N>, `${S}${string}${S}`>;

type ExplodeSegments = {
[P in RecurSegments<5, A1024> as `${P}_key`]: any;
};


//// [templateLiteralTypeExcessiveLength.js]
"use strict";
// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and
// keeps tail-recursing forever. The checker must report an error instead of building an unbounded
// template literal type.
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//// [tests/cases/compiler/templateLiteralTypeExcessiveLength.ts] ////

=== templateLiteralTypeExcessiveLength.ts ===
// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and
// keeps tail-recursing forever. The checker must report an error instead of building an unbounded
// template literal type.

type Dec<N extends number> =
>Dec : Symbol(Dec, Decl(templateLiteralTypeExcessiveLength.ts, 0, 0))
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9))

N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 :
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9))
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9))
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9))

N extends 2 ? any :
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9))

N extends 1 ? 0 : 0;
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 4, 9))

type Recur<N extends number, S extends string> =
>Recur : Symbol(Recur, Decl(templateLiteralTypeExcessiveLength.ts, 7, 24))
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 9, 11))
>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 9, 28))

N extends 0 ? S : Recur<Dec<N>, `${S}_${S}`>;
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 9, 11))
>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 9, 28))
>Recur : Symbol(Recur, Decl(templateLiteralTypeExcessiveLength.ts, 7, 24))
>Dec : Symbol(Dec, Decl(templateLiteralTypeExcessiveLength.ts, 0, 0))
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 9, 11))
>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 9, 28))
>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 9, 28))

// The string literal doubles on every iteration.
type Explode = {
>Explode : Symbol(Explode, Decl(templateLiteralTypeExcessiveLength.ts, 10, 49))

[P in Recur<5, "a"> as `${P}_key`]: any;
>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 14, 5))
>Recur : Symbol(Recur, Decl(templateLiteralTypeExcessiveLength.ts, 7, 24))
>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 14, 5))

};

// The number of placeholders doubles on every iteration.
type ExplodeSpans = {
>ExplodeSpans : Symbol(ExplodeSpans, Decl(templateLiteralTypeExcessiveLength.ts, 15, 2))

[P in Recur<5, string> as `${P}_key`]: any;
>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 19, 5))
>Recur : Symbol(Recur, Decl(templateLiteralTypeExcessiveLength.ts, 7, 24))
>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 19, 5))

};

// Every text segment stays small, but the number of segments doubles on every iteration,
// so the combined text length grows without bound.
type A16 = "aaaaaaaaaaaaaaaa";
>A16 : Symbol(A16, Decl(templateLiteralTypeExcessiveLength.ts, 20, 2))

type A64 = `${A16}${A16}${A16}${A16}`;
>A64 : Symbol(A64, Decl(templateLiteralTypeExcessiveLength.ts, 24, 30))
>A16 : Symbol(A16, Decl(templateLiteralTypeExcessiveLength.ts, 20, 2))
>A16 : Symbol(A16, Decl(templateLiteralTypeExcessiveLength.ts, 20, 2))
>A16 : Symbol(A16, Decl(templateLiteralTypeExcessiveLength.ts, 20, 2))
>A16 : Symbol(A16, Decl(templateLiteralTypeExcessiveLength.ts, 20, 2))

type A256 = `${A64}${A64}${A64}${A64}`;
>A256 : Symbol(A256, Decl(templateLiteralTypeExcessiveLength.ts, 25, 38))
>A64 : Symbol(A64, Decl(templateLiteralTypeExcessiveLength.ts, 24, 30))
>A64 : Symbol(A64, Decl(templateLiteralTypeExcessiveLength.ts, 24, 30))
>A64 : Symbol(A64, Decl(templateLiteralTypeExcessiveLength.ts, 24, 30))
>A64 : Symbol(A64, Decl(templateLiteralTypeExcessiveLength.ts, 24, 30))

type A1024 = `${A256}${A256}${A256}${A256}`;
>A1024 : Symbol(A1024, Decl(templateLiteralTypeExcessiveLength.ts, 26, 39))
>A256 : Symbol(A256, Decl(templateLiteralTypeExcessiveLength.ts, 25, 38))
>A256 : Symbol(A256, Decl(templateLiteralTypeExcessiveLength.ts, 25, 38))
>A256 : Symbol(A256, Decl(templateLiteralTypeExcessiveLength.ts, 25, 38))
>A256 : Symbol(A256, Decl(templateLiteralTypeExcessiveLength.ts, 25, 38))

type RecurSegments<N extends number, S extends string> =
>RecurSegments : Symbol(RecurSegments, Decl(templateLiteralTypeExcessiveLength.ts, 27, 44))
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 29, 19))
>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 29, 36))

N extends 0 ? S : RecurSegments<Dec<N>, `${S}${string}${S}`>;
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 29, 19))
>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 29, 36))
>RecurSegments : Symbol(RecurSegments, Decl(templateLiteralTypeExcessiveLength.ts, 27, 44))
>Dec : Symbol(Dec, Decl(templateLiteralTypeExcessiveLength.ts, 0, 0))
>N : Symbol(N, Decl(templateLiteralTypeExcessiveLength.ts, 29, 19))
>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 29, 36))
>S : Symbol(S, Decl(templateLiteralTypeExcessiveLength.ts, 29, 36))

type ExplodeSegments = {
>ExplodeSegments : Symbol(ExplodeSegments, Decl(templateLiteralTypeExcessiveLength.ts, 30, 65))

[P in RecurSegments<5, A1024> as `${P}_key`]: any;
>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 33, 5))
>RecurSegments : Symbol(RecurSegments, Decl(templateLiteralTypeExcessiveLength.ts, 27, 44))
>A1024 : Symbol(A1024, Decl(templateLiteralTypeExcessiveLength.ts, 26, 39))
>P : Symbol(P, Decl(templateLiteralTypeExcessiveLength.ts, 33, 5))

};

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//// [tests/cases/compiler/templateLiteralTypeExcessiveLength.ts] ////

=== templateLiteralTypeExcessiveLength.ts ===
// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and
// keeps tail-recursing forever. The checker must report an error instead of building an unbounded
// template literal type.

type Dec<N extends number> =
>Dec : Dec<N>

N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 :
N extends 2 ? any :
N extends 1 ? 0 : 0;

type Recur<N extends number, S extends string> =
>Recur : Recur<N, S>

N extends 0 ? S : Recur<Dec<N>, `${S}_${S}`>;

// The string literal doubles on every iteration.
type Explode = {
>Explode : Explode

[P in Recur<5, "a"> as `${P}_key`]: any;
};

// The number of placeholders doubles on every iteration.
type ExplodeSpans = {
>ExplodeSpans : ExplodeSpans

[P in Recur<5, string> as `${P}_key`]: any;
};

// Every text segment stays small, but the number of segments doubles on every iteration,
// so the combined text length grows without bound.
type A16 = "aaaaaaaaaaaaaaaa";
>A16 : "aaaaaaaaaaaaaaaa"

type A64 = `${A16}${A16}${A16}${A16}`;
>A64 : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

type A256 = `${A64}${A64}${A64}${A64}`;
>A256 : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

type A1024 = `${A256}${A256}${A256}${A256}`;
>A1024 : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

type RecurSegments<N extends number, S extends string> =
>RecurSegments : RecurSegments<N, S>

N extends 0 ? S : RecurSegments<Dec<N>, `${S}${string}${S}`>;

type ExplodeSegments = {
>ExplodeSegments : ExplodeSegments

[P in RecurSegments<5, A1024> as `${P}_key`]: any;
};

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// @strict: true

// Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and
// keeps tail-recursing forever. The checker must report an error instead of building an unbounded
// template literal type.

type Dec<N extends number> =
N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 :
N extends 2 ? any :
N extends 1 ? 0 : 0;

type Recur<N extends number, S extends string> =
N extends 0 ? S : Recur<Dec<N>, `${S}_${S}`>;

// The string literal doubles on every iteration.
type Explode = {
[P in Recur<5, "a"> as `${P}_key`]: any;
};

// The number of placeholders doubles on every iteration.
type ExplodeSpans = {
[P in Recur<5, string> as `${P}_key`]: any;
};

// Every text segment stays small, but the number of segments doubles on every iteration,
// so the combined text length grows without bound.
type A16 = "aaaaaaaaaaaaaaaa";
type A64 = `${A16}${A16}${A16}${A16}`;
type A256 = `${A64}${A64}${A64}${A64}`;
type A1024 = `${A256}${A256}${A256}${A256}`;

type RecurSegments<N extends number, S extends string> =
N extends 0 ? S : RecurSegments<Dec<N>, `${S}${string}${S}`>;

type ExplodeSegments = {
[P in RecurSegments<5, A1024> as `${P}_key`]: any;
};