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
82 changes: 80 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16306,6 +16306,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return type;
}

function getTupleElementTypes(type: Type, seenTypes = new Set<Type>()): readonly Type[] {
// Guard against circular references using a Set
if (seenTypes.has(type)) {
return emptyArray;
}

// Add the current type to the Set to track it
seenTypes.add(type);

if (isTupleType(type)) {
const target = type.target;
if (target?.resolvedTypeArguments) {
return target.resolvedTypeArguments;
}

if (type.objectFlags & ObjectFlags.Tuple) {
const tupleTarget = type as unknown as TupleType;
if (tupleTarget.elementFlags) {
return getTypeArguments(type) || emptyArray;
}
}
}
return emptyArray;
}

function getTypeArguments(type: TypeReference): readonly Type[] {
if (!type.resolvedTypeArguments) {
if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedTypeArguments)) {
Expand Down Expand Up @@ -18154,13 +18179,52 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return reduceLeft(types, (n, t) => t.flags & TypeFlags.Union ? n * (t as UnionType).types.length : t.flags & TypeFlags.Never ? 0 : n, 1);
}

function checkCrossProductUnion(types: readonly Type[]) {
function containsDeeplyNestedType(types: readonly Type[], maxDepth: number, currentDepth: number = 0): boolean {
if (currentDepth >= maxDepth) return true;

for (const type of types) {
// Check if the type is a union type
if (isUnionType(type)) {
if (containsDeeplyNestedType(type.types, maxDepth, currentDepth + 1)) {
return true;
}
}

// Check if the type is a tuple type
if (isTupleType(type)) {
const tupleElementTypes = getTupleElementTypes(type);
if (containsDeeplyNestedType(tupleElementTypes, maxDepth, currentDepth + 1)) {
return true;
}
}
}

return false;
}

function checkCrossProductUnion(types: readonly Type[]): boolean {
const size = getCrossProductUnionSize(types);

// Check if the cross-product size exceeds the threshold
if (size >= 100000) {
tracing?.instant(tracing.Phase.CheckTypes, "checkCrossProductUnion_DepthLimit", { typeIds: types.map(t => t.id), size });
tracing?.instant(tracing.Phase.CheckTypes, "checkCrossProductUnion_DepthLimit", {
typeIds: types.map(t => t.id),
size,
});
error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
return false;
}

// Check for excessive nesting within types
const maxDepth = 3;
if (containsDeeplyNestedType(types, maxDepth)) {
tracing?.instant(tracing.Phase.CheckTypes, "checkCrossProductUnion_TooNested", {
typeIds: types.map(t => t.id),
});
error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
return false;
}

return true;
}

Expand Down Expand Up @@ -25039,6 +25103,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return !!(getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target.objectFlags & ObjectFlags.Tuple);
}

/**
* Determines if the given type is a union type.
*
* A union type is a type formed by combining multiple types
* using the `|` operator (e.g., `string | number`). This function
* checks if the provided type has the `TypeFlags.Union` flag set.
*
* @param type - The `Type` instance to check.
* @returns `true` if the type is a union type, otherwise `false`.
*/
function isUnionType(type: Type): type is UnionType {
return (type.flags & TypeFlags.Union) !== 0;
}

function isGenericTupleType(type: Type): type is TupleTypeReference {
return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic);
}
Expand Down
59 changes: 59 additions & 0 deletions tests/baselines/reference/tupleComplexity.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
tupleComplexity.ts(43,7): error TS2322: Type '[string, number]' is not assignable to type '[string, number, boolean]'.
Source has 2 element(s) but target requires 3.
tupleComplexity.ts(44,36): error TS2322: Type 'number' is not assignable to type 'string'.
tupleComplexity.ts(44,40): error TS2322: Type 'boolean' is not assignable to type 'string'.


==== tupleComplexity.ts (3 errors) ====
// Tests for TS2590: "Expression produces a union type that is too complex to represent"

// --- Tuple Unions ---

// Simple union - Should work
type TupleUnion1 = [string, number] | [boolean, string];
const valid1: TupleUnion1 = ["hello", 42]; // ✅ Should pass
const valid2: TupleUnion1 = [true, "world"]; // ✅ Should pass

// Extended union - Should trigger TS2590
type TupleUnion2 = [string, number] | [boolean, string];
type ComplexTuple = [...TupleUnion2, string]; // ❌ Should trigger TS2590
const invalid: ComplexTuple = ["hello", 42, "world"]; // Should fail with TS2590

// --- Tuple Concatenations ---

// Manageable concatenation - Should work
type ConcatTuple<T extends any[], U extends any[]> = [...T, ...U];
type Result1 = ConcatTuple<[string, number], [boolean]>; // ✅ Should infer [string, number, boolean]
const concat1: Result1 = ["hello", 42, true]; // ✅ Should pass

// Excessively large concatenation - Should trigger TS2590
type LargeConcat = ConcatTuple<[...Array<100>], [...Array<100>]>;
// ❌ Should trigger TS2590 for excessive complexity

// --- Mapped Types on Tuples ---

// Simple mapping - Should work
type Stringify<T extends any[]> = { [K in keyof T]: string };
type MappedTuple1 = Stringify<[number, boolean]>; // ✅ Should infer [string, string]
const map1: MappedTuple1 = ["42", "true"]; // ✅ Should pass

// --- Nested Tuples ---

// Deeply nested tuple - Should trigger TS2590
type DeepTuple = [string, [boolean | number, [boolean | number, [boolean | number]]]];
type Nested = [...DeepTuple, string]; // ❌ Should trigger TS2590
const deep: Nested = ["root", [true, [42, [false]]], "leaf"]; // Should fail with TS2590

// --- Invalid Cases ---

// Expected type mismatches (non-TS2590 failures)
const invalidConcat1: Result1 = ["hello", 42]; // ❌ Error: Missing boolean
~~~~~~~~~~~~~~
!!! error TS2322: Type '[string, number]' is not assignable to type '[string, number, boolean]'.
!!! error TS2322: Source has 2 element(s) but target requires 3.
const invalidMap1: MappedTuple1 = [42, true]; // ❌ Error: Expected strings
~~
!!! error TS2322: Type 'number' is not assignable to type 'string'.
~~~~
!!! error TS2322: Type 'boolean' is not assignable to type 'string'.

61 changes: 61 additions & 0 deletions tests/baselines/reference/tupleComplexity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//// [tests/cases/compiler/tupleComplexity.ts] ////

//// [tupleComplexity.ts]
// Tests for TS2590: "Expression produces a union type that is too complex to represent"

// --- Tuple Unions ---

// Simple union - Should work
type TupleUnion1 = [string, number] | [boolean, string];
const valid1: TupleUnion1 = ["hello", 42]; // ✅ Should pass
const valid2: TupleUnion1 = [true, "world"]; // ✅ Should pass

// Extended union - Should trigger TS2590
type TupleUnion2 = [string, number] | [boolean, string];
type ComplexTuple = [...TupleUnion2, string]; // ❌ Should trigger TS2590
const invalid: ComplexTuple = ["hello", 42, "world"]; // Should fail with TS2590

// --- Tuple Concatenations ---

// Manageable concatenation - Should work
type ConcatTuple<T extends any[], U extends any[]> = [...T, ...U];
type Result1 = ConcatTuple<[string, number], [boolean]>; // ✅ Should infer [string, number, boolean]
const concat1: Result1 = ["hello", 42, true]; // ✅ Should pass

// Excessively large concatenation - Should trigger TS2590
type LargeConcat = ConcatTuple<[...Array<100>], [...Array<100>]>;
// ❌ Should trigger TS2590 for excessive complexity

// --- Mapped Types on Tuples ---

// Simple mapping - Should work
type Stringify<T extends any[]> = { [K in keyof T]: string };
type MappedTuple1 = Stringify<[number, boolean]>; // ✅ Should infer [string, string]
const map1: MappedTuple1 = ["42", "true"]; // ✅ Should pass

// --- Nested Tuples ---

// Deeply nested tuple - Should trigger TS2590
type DeepTuple = [string, [boolean | number, [boolean | number, [boolean | number]]]];
type Nested = [...DeepTuple, string]; // ❌ Should trigger TS2590
const deep: Nested = ["root", [true, [42, [false]]], "leaf"]; // Should fail with TS2590

// --- Invalid Cases ---

// Expected type mismatches (non-TS2590 failures)
const invalidConcat1: Result1 = ["hello", 42]; // ❌ Error: Missing boolean
const invalidMap1: MappedTuple1 = [42, true]; // ❌ Error: Expected strings


//// [tupleComplexity.js]
// Tests for TS2590: "Expression produces a union type that is too complex to represent"
var valid1 = ["hello", 42]; // ✅ Should pass
var valid2 = [true, "world"]; // ✅ Should pass
var invalid = ["hello", 42, "world"]; // Should fail with TS2590
var concat1 = ["hello", 42, true]; // ✅ Should pass
var map1 = ["42", "true"]; // ✅ Should pass
var deep = ["root", [true, [42, [false]]], "leaf"]; // Should fail with TS2590
// --- Invalid Cases ---
// Expected type mismatches (non-TS2590 failures)
var invalidConcat1 = ["hello", 42]; // ❌ Error: Missing boolean
var invalidMap1 = [42, true]; // ❌ Error: Expected strings
100 changes: 100 additions & 0 deletions tests/baselines/reference/tupleComplexity.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//// [tests/cases/compiler/tupleComplexity.ts] ////

=== tupleComplexity.ts ===
// Tests for TS2590: "Expression produces a union type that is too complex to represent"

// --- Tuple Unions ---

// Simple union - Should work
type TupleUnion1 = [string, number] | [boolean, string];
>TupleUnion1 : Symbol(TupleUnion1, Decl(tupleComplexity.ts, 0, 0))

const valid1: TupleUnion1 = ["hello", 42]; // ✅ Should pass
>valid1 : Symbol(valid1, Decl(tupleComplexity.ts, 6, 5))
>TupleUnion1 : Symbol(TupleUnion1, Decl(tupleComplexity.ts, 0, 0))

const valid2: TupleUnion1 = [true, "world"]; // ✅ Should pass
>valid2 : Symbol(valid2, Decl(tupleComplexity.ts, 7, 5))
>TupleUnion1 : Symbol(TupleUnion1, Decl(tupleComplexity.ts, 0, 0))

// Extended union - Should trigger TS2590
type TupleUnion2 = [string, number] | [boolean, string];
>TupleUnion2 : Symbol(TupleUnion2, Decl(tupleComplexity.ts, 7, 44))

type ComplexTuple = [...TupleUnion2, string]; // ❌ Should trigger TS2590
>ComplexTuple : Symbol(ComplexTuple, Decl(tupleComplexity.ts, 10, 56))
>TupleUnion2 : Symbol(TupleUnion2, Decl(tupleComplexity.ts, 7, 44))

const invalid: ComplexTuple = ["hello", 42, "world"]; // Should fail with TS2590
>invalid : Symbol(invalid, Decl(tupleComplexity.ts, 12, 5))
>ComplexTuple : Symbol(ComplexTuple, Decl(tupleComplexity.ts, 10, 56))

// --- Tuple Concatenations ---

// Manageable concatenation - Should work
type ConcatTuple<T extends any[], U extends any[]> = [...T, ...U];
>ConcatTuple : Symbol(ConcatTuple, Decl(tupleComplexity.ts, 12, 53))
>T : Symbol(T, Decl(tupleComplexity.ts, 17, 17))
>U : Symbol(U, Decl(tupleComplexity.ts, 17, 33))
>T : Symbol(T, Decl(tupleComplexity.ts, 17, 17))
>U : Symbol(U, Decl(tupleComplexity.ts, 17, 33))

type Result1 = ConcatTuple<[string, number], [boolean]>; // ✅ Should infer [string, number, boolean]
>Result1 : Symbol(Result1, Decl(tupleComplexity.ts, 17, 66))
>ConcatTuple : Symbol(ConcatTuple, Decl(tupleComplexity.ts, 12, 53))

const concat1: Result1 = ["hello", 42, true]; // ✅ Should pass
>concat1 : Symbol(concat1, Decl(tupleComplexity.ts, 19, 5))
>Result1 : Symbol(Result1, Decl(tupleComplexity.ts, 17, 66))

// Excessively large concatenation - Should trigger TS2590
type LargeConcat = ConcatTuple<[...Array<100>], [...Array<100>]>;
>LargeConcat : Symbol(LargeConcat, Decl(tupleComplexity.ts, 19, 45))
>ConcatTuple : Symbol(ConcatTuple, Decl(tupleComplexity.ts, 12, 53))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

// ❌ Should trigger TS2590 for excessive complexity

// --- Mapped Types on Tuples ---

// Simple mapping - Should work
type Stringify<T extends any[]> = { [K in keyof T]: string };
>Stringify : Symbol(Stringify, Decl(tupleComplexity.ts, 22, 65))
>T : Symbol(T, Decl(tupleComplexity.ts, 28, 15))
>K : Symbol(K, Decl(tupleComplexity.ts, 28, 37))
>T : Symbol(T, Decl(tupleComplexity.ts, 28, 15))

type MappedTuple1 = Stringify<[number, boolean]>; // ✅ Should infer [string, string]
>MappedTuple1 : Symbol(MappedTuple1, Decl(tupleComplexity.ts, 28, 61))
>Stringify : Symbol(Stringify, Decl(tupleComplexity.ts, 22, 65))

const map1: MappedTuple1 = ["42", "true"]; // ✅ Should pass
>map1 : Symbol(map1, Decl(tupleComplexity.ts, 30, 5))
>MappedTuple1 : Symbol(MappedTuple1, Decl(tupleComplexity.ts, 28, 61))

// --- Nested Tuples ---

// Deeply nested tuple - Should trigger TS2590
type DeepTuple = [string, [boolean | number, [boolean | number, [boolean | number]]]];
>DeepTuple : Symbol(DeepTuple, Decl(tupleComplexity.ts, 30, 42))

type Nested = [...DeepTuple, string]; // ❌ Should trigger TS2590
>Nested : Symbol(Nested, Decl(tupleComplexity.ts, 35, 86))
>DeepTuple : Symbol(DeepTuple, Decl(tupleComplexity.ts, 30, 42))

const deep: Nested = ["root", [true, [42, [false]]], "leaf"]; // Should fail with TS2590
>deep : Symbol(deep, Decl(tupleComplexity.ts, 37, 5))
>Nested : Symbol(Nested, Decl(tupleComplexity.ts, 35, 86))

// --- Invalid Cases ---

// Expected type mismatches (non-TS2590 failures)
const invalidConcat1: Result1 = ["hello", 42]; // ❌ Error: Missing boolean
>invalidConcat1 : Symbol(invalidConcat1, Decl(tupleComplexity.ts, 42, 5))
>Result1 : Symbol(Result1, Decl(tupleComplexity.ts, 17, 66))

const invalidMap1: MappedTuple1 = [42, true]; // ❌ Error: Expected strings
>invalidMap1 : Symbol(invalidMap1, Decl(tupleComplexity.ts, 43, 5))
>MappedTuple1 : Symbol(MappedTuple1, Decl(tupleComplexity.ts, 28, 61))

Loading