From 9c6b2a2767415a7c10854f984b08601b81a8be92 Mon Sep 17 00:00:00 2001 From: Ryan Myrvold <2127198+RyanMyrvold@users.noreply.github.com> Date: Sun, 24 Nov 2024 19:25:27 -0500 Subject: [PATCH 1/2] Add baselines for tupleComplexity test --- tests/baselines/reference/tupleComplexity.js | 35 +++++++ .../reference/tupleComplexity.symbols | 60 +++++++++++ .../baselines/reference/tupleComplexity.types | 99 +++++++++++++++++++ tests/cases/compiler/tupleComplexity.ts | 21 ++++ 4 files changed, 215 insertions(+) create mode 100644 tests/baselines/reference/tupleComplexity.js create mode 100644 tests/baselines/reference/tupleComplexity.symbols create mode 100644 tests/baselines/reference/tupleComplexity.types create mode 100644 tests/cases/compiler/tupleComplexity.ts diff --git a/tests/baselines/reference/tupleComplexity.js b/tests/baselines/reference/tupleComplexity.js new file mode 100644 index 0000000000000..65700b91fd432 --- /dev/null +++ b/tests/baselines/reference/tupleComplexity.js @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/tupleComplexity.ts] //// + +//// [tupleComplexity.ts] +// Tuple union with simple cases - should not produce TS2590 +type TupleUnion = [string, number] | [boolean, string]; +const example1: TupleUnion = ["hello", 42]; // Valid +const example2: TupleUnion = [true, "world"]; // Valid + +// Complex tuple concatenation - TS2590 currently triggered +type ConcatTuple = [...T, ...U]; +type Result = ConcatTuple<[number, string], [boolean]>; +// Result should be inferred as [number, string, boolean] +const concatenated: Result = [1, "foo", true]; // Valid + +// Map types on tuples +type Stringify = { [K in keyof T]: string }; +type Mapped = Stringify<[number, boolean]>; +// Should infer as [string, string] +const mapped: Mapped = ["123", "true"]; // Valid + +// Complex unions within tuples +type NestedUnion = [string, [boolean | number]]; +const nested: NestedUnion = ["test", [true]]; // Valid +const nested2: NestedUnion = ["test", [42]]; // Valid + + +//// [tupleComplexity.js] +var example1 = ["hello", 42]; // Valid +var example2 = [true, "world"]; // Valid +// Result should be inferred as [number, string, boolean] +var concatenated = [1, "foo", true]; // Valid +// Should infer as [string, string] +var mapped = ["123", "true"]; // Valid +var nested = ["test", [true]]; // Valid +var nested2 = ["test", [42]]; // Valid diff --git a/tests/baselines/reference/tupleComplexity.symbols b/tests/baselines/reference/tupleComplexity.symbols new file mode 100644 index 0000000000000..036274014c4fe --- /dev/null +++ b/tests/baselines/reference/tupleComplexity.symbols @@ -0,0 +1,60 @@ +//// [tests/cases/compiler/tupleComplexity.ts] //// + +=== tupleComplexity.ts === +// Tuple union with simple cases - should not produce TS2590 +type TupleUnion = [string, number] | [boolean, string]; +>TupleUnion : Symbol(TupleUnion, Decl(tupleComplexity.ts, 0, 0)) + +const example1: TupleUnion = ["hello", 42]; // Valid +>example1 : Symbol(example1, Decl(tupleComplexity.ts, 2, 5)) +>TupleUnion : Symbol(TupleUnion, Decl(tupleComplexity.ts, 0, 0)) + +const example2: TupleUnion = [true, "world"]; // Valid +>example2 : Symbol(example2, Decl(tupleComplexity.ts, 3, 5)) +>TupleUnion : Symbol(TupleUnion, Decl(tupleComplexity.ts, 0, 0)) + +// Complex tuple concatenation - TS2590 currently triggered +type ConcatTuple = [...T, ...U]; +>ConcatTuple : Symbol(ConcatTuple, Decl(tupleComplexity.ts, 3, 45)) +>T : Symbol(T, Decl(tupleComplexity.ts, 6, 17)) +>U : Symbol(U, Decl(tupleComplexity.ts, 6, 33)) +>T : Symbol(T, Decl(tupleComplexity.ts, 6, 17)) +>U : Symbol(U, Decl(tupleComplexity.ts, 6, 33)) + +type Result = ConcatTuple<[number, string], [boolean]>; +>Result : Symbol(Result, Decl(tupleComplexity.ts, 6, 66)) +>ConcatTuple : Symbol(ConcatTuple, Decl(tupleComplexity.ts, 3, 45)) + +// Result should be inferred as [number, string, boolean] +const concatenated: Result = [1, "foo", true]; // Valid +>concatenated : Symbol(concatenated, Decl(tupleComplexity.ts, 9, 5)) +>Result : Symbol(Result, Decl(tupleComplexity.ts, 6, 66)) + +// Map types on tuples +type Stringify = { [K in keyof T]: string }; +>Stringify : Symbol(Stringify, Decl(tupleComplexity.ts, 9, 46)) +>T : Symbol(T, Decl(tupleComplexity.ts, 12, 15)) +>K : Symbol(K, Decl(tupleComplexity.ts, 12, 37)) +>T : Symbol(T, Decl(tupleComplexity.ts, 12, 15)) + +type Mapped = Stringify<[number, boolean]>; +>Mapped : Symbol(Mapped, Decl(tupleComplexity.ts, 12, 61)) +>Stringify : Symbol(Stringify, Decl(tupleComplexity.ts, 9, 46)) + +// Should infer as [string, string] +const mapped: Mapped = ["123", "true"]; // Valid +>mapped : Symbol(mapped, Decl(tupleComplexity.ts, 15, 5)) +>Mapped : Symbol(Mapped, Decl(tupleComplexity.ts, 12, 61)) + +// Complex unions within tuples +type NestedUnion = [string, [boolean | number]]; +>NestedUnion : Symbol(NestedUnion, Decl(tupleComplexity.ts, 15, 39)) + +const nested: NestedUnion = ["test", [true]]; // Valid +>nested : Symbol(nested, Decl(tupleComplexity.ts, 19, 5)) +>NestedUnion : Symbol(NestedUnion, Decl(tupleComplexity.ts, 15, 39)) + +const nested2: NestedUnion = ["test", [42]]; // Valid +>nested2 : Symbol(nested2, Decl(tupleComplexity.ts, 20, 5)) +>NestedUnion : Symbol(NestedUnion, Decl(tupleComplexity.ts, 15, 39)) + diff --git a/tests/baselines/reference/tupleComplexity.types b/tests/baselines/reference/tupleComplexity.types new file mode 100644 index 0000000000000..18d28f5f11e6a --- /dev/null +++ b/tests/baselines/reference/tupleComplexity.types @@ -0,0 +1,99 @@ +//// [tests/cases/compiler/tupleComplexity.ts] //// + +=== tupleComplexity.ts === +// Tuple union with simple cases - should not produce TS2590 +type TupleUnion = [string, number] | [boolean, string]; +>TupleUnion : TupleUnion +> : ^^^^^^^^^^ + +const example1: TupleUnion = ["hello", 42]; // Valid +>example1 : TupleUnion +> : ^^^^^^^^^^ +>["hello", 42] : [string, number] +> : ^^^^^^^^^^^^^^^^ +>"hello" : "hello" +> : ^^^^^^^ +>42 : 42 +> : ^^ + +const example2: TupleUnion = [true, "world"]; // Valid +>example2 : TupleUnion +> : ^^^^^^^^^^ +>[true, "world"] : [true, string] +> : ^^^^^^^^^^^^^^ +>true : true +> : ^^^^ +>"world" : "world" +> : ^^^^^^^ + +// Complex tuple concatenation - TS2590 currently triggered +type ConcatTuple = [...T, ...U]; +>ConcatTuple : [...T, ...U] +> : ^^^^^^^^^^^^ + +type Result = ConcatTuple<[number, string], [boolean]>; +>Result : [number, string, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + +// Result should be inferred as [number, string, boolean] +const concatenated: Result = [1, "foo", true]; // Valid +>concatenated : [number, string, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>[1, "foo", true] : [number, string, true] +> : ^^^^^^^^^^^^^^^^^^^^^^ +>1 : 1 +> : ^ +>"foo" : "foo" +> : ^^^^^ +>true : true +> : ^^^^ + +// Map types on tuples +type Stringify = { [K in keyof T]: string }; +>Stringify : Stringify +> : ^^^^^^^^^^^^ + +type Mapped = Stringify<[number, boolean]>; +>Mapped : [string, string] +> : ^^^^^^^^^^^^^^^^ + +// Should infer as [string, string] +const mapped: Mapped = ["123", "true"]; // Valid +>mapped : [string, string] +> : ^^^^^^^^^^^^^^^^ +>["123", "true"] : [string, string] +> : ^^^^^^^^^^^^^^^^ +>"123" : "123" +> : ^^^^^ +>"true" : "true" +> : ^^^^^^ + +// Complex unions within tuples +type NestedUnion = [string, [boolean | number]]; +>NestedUnion : NestedUnion +> : ^^^^^^^^^^^ + +const nested: NestedUnion = ["test", [true]]; // Valid +>nested : NestedUnion +> : ^^^^^^^^^^^ +>["test", [true]] : [string, [true]] +> : ^^^^^^^^^^^^^^^^ +>"test" : "test" +> : ^^^^^^ +>[true] : [true] +> : ^^^^^^ +>true : true +> : ^^^^ + +const nested2: NestedUnion = ["test", [42]]; // Valid +>nested2 : NestedUnion +> : ^^^^^^^^^^^ +>["test", [42]] : [string, [number]] +> : ^^^^^^^^^^^^^^^^^^ +>"test" : "test" +> : ^^^^^^ +>[42] : [number] +> : ^^^^^^^^ +>42 : 42 +> : ^^ + diff --git a/tests/cases/compiler/tupleComplexity.ts b/tests/cases/compiler/tupleComplexity.ts new file mode 100644 index 0000000000000..7b53e3a174036 --- /dev/null +++ b/tests/cases/compiler/tupleComplexity.ts @@ -0,0 +1,21 @@ +// Tuple union with simple cases - should not produce TS2590 +type TupleUnion = [string, number] | [boolean, string]; +const example1: TupleUnion = ["hello", 42]; // Valid +const example2: TupleUnion = [true, "world"]; // Valid + +// Complex tuple concatenation - TS2590 currently triggered +type ConcatTuple = [...T, ...U]; +type Result = ConcatTuple<[number, string], [boolean]>; +// Result should be inferred as [number, string, boolean] +const concatenated: Result = [1, "foo", true]; // Valid + +// Map types on tuples +type Stringify = { [K in keyof T]: string }; +type Mapped = Stringify<[number, boolean]>; +// Should infer as [string, string] +const mapped: Mapped = ["123", "true"]; // Valid + +// Complex unions within tuples +type NestedUnion = [string, [boolean | number]]; +const nested: NestedUnion = ["test", [true]]; // Valid +const nested2: NestedUnion = ["test", [42]]; // Valid From 251c8ca4dc123e19ec6d489a81cc726c3f37d017 Mon Sep 17 00:00:00 2001 From: Ryan Myrvold <2127198+RyanMyrvold@users.noreply.github.com> Date: Mon, 25 Nov 2024 01:41:51 -0500 Subject: [PATCH 2/2] Fix getTupleElementTypes to handle tuple types correctly and add detailed logging. - Corrected the handling of tuple types in `getTupleElementTypes`: - Implemented proper checks for resolved type arguments and element flags. - Ensured accurate identification of tuple types using `isTupleType`. - Added a guard against circular references by using a `Set` to track seen types. - Enhanced debugging by adding detailed console logs: - Logs the type being analyzed. - Warns when a circular reference is detected. - Logs when a tuple type is detected and when resolved type arguments are found. - Indicates when a type is not a tuple. --- src/compiler/checker.ts | 82 +++++++- .../reference/tupleComplexity.errors.txt | 59 ++++++ tests/baselines/reference/tupleComplexity.js | 76 +++++--- .../reference/tupleComplexity.symbols | 142 +++++++++----- .../baselines/reference/tupleComplexity.types | 181 ++++++++++++------ tests/cases/compiler/tupleComplexity.ts | 57 ++++-- 6 files changed, 442 insertions(+), 155 deletions(-) create mode 100644 tests/baselines/reference/tupleComplexity.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3d044fa5bf0f4..f189189416f5c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16306,6 +16306,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } + function getTupleElementTypes(type: Type, seenTypes = new Set()): 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)) { @@ -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; } @@ -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); } diff --git a/tests/baselines/reference/tupleComplexity.errors.txt b/tests/baselines/reference/tupleComplexity.errors.txt new file mode 100644 index 0000000000000..008f2577c7992 --- /dev/null +++ b/tests/baselines/reference/tupleComplexity.errors.txt @@ -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, ...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 = { [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'. + \ No newline at end of file diff --git a/tests/baselines/reference/tupleComplexity.js b/tests/baselines/reference/tupleComplexity.js index 65700b91fd432..c75c2e0151d48 100644 --- a/tests/baselines/reference/tupleComplexity.js +++ b/tests/baselines/reference/tupleComplexity.js @@ -1,35 +1,61 @@ //// [tests/cases/compiler/tupleComplexity.ts] //// //// [tupleComplexity.ts] -// Tuple union with simple cases - should not produce TS2590 -type TupleUnion = [string, number] | [boolean, string]; -const example1: TupleUnion = ["hello", 42]; // Valid -const example2: TupleUnion = [true, "world"]; // Valid +// Tests for TS2590: "Expression produces a union type that is too complex to represent" -// Complex tuple concatenation - TS2590 currently triggered +// --- 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, ...U]; -type Result = ConcatTuple<[number, string], [boolean]>; -// Result should be inferred as [number, string, boolean] -const concatenated: Result = [1, "foo", true]; // Valid +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 -// Map types on tuples +// --- Mapped Types on Tuples --- + +// Simple mapping - Should work type Stringify = { [K in keyof T]: string }; -type Mapped = Stringify<[number, boolean]>; -// Should infer as [string, string] -const mapped: Mapped = ["123", "true"]; // Valid - -// Complex unions within tuples -type NestedUnion = [string, [boolean | number]]; -const nested: NestedUnion = ["test", [true]]; // Valid -const nested2: NestedUnion = ["test", [42]]; // Valid +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] -var example1 = ["hello", 42]; // Valid -var example2 = [true, "world"]; // Valid -// Result should be inferred as [number, string, boolean] -var concatenated = [1, "foo", true]; // Valid -// Should infer as [string, string] -var mapped = ["123", "true"]; // Valid -var nested = ["test", [true]]; // Valid -var nested2 = ["test", [42]]; // Valid +// 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 diff --git a/tests/baselines/reference/tupleComplexity.symbols b/tests/baselines/reference/tupleComplexity.symbols index 036274014c4fe..34054917e62b0 100644 --- a/tests/baselines/reference/tupleComplexity.symbols +++ b/tests/baselines/reference/tupleComplexity.symbols @@ -1,60 +1,100 @@ //// [tests/cases/compiler/tupleComplexity.ts] //// === tupleComplexity.ts === -// Tuple union with simple cases - should not produce TS2590 -type TupleUnion = [string, number] | [boolean, string]; ->TupleUnion : Symbol(TupleUnion, Decl(tupleComplexity.ts, 0, 0)) +// Tests for TS2590: "Expression produces a union type that is too complex to represent" -const example1: TupleUnion = ["hello", 42]; // Valid ->example1 : Symbol(example1, Decl(tupleComplexity.ts, 2, 5)) ->TupleUnion : Symbol(TupleUnion, Decl(tupleComplexity.ts, 0, 0)) +// --- Tuple Unions --- -const example2: TupleUnion = [true, "world"]; // Valid ->example2 : Symbol(example2, Decl(tupleComplexity.ts, 3, 5)) ->TupleUnion : Symbol(TupleUnion, Decl(tupleComplexity.ts, 0, 0)) +// Simple union - Should work +type TupleUnion1 = [string, number] | [boolean, string]; +>TupleUnion1 : Symbol(TupleUnion1, Decl(tupleComplexity.ts, 0, 0)) -// Complex tuple concatenation - TS2590 currently triggered +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, ...U]; ->ConcatTuple : Symbol(ConcatTuple, Decl(tupleComplexity.ts, 3, 45)) ->T : Symbol(T, Decl(tupleComplexity.ts, 6, 17)) ->U : Symbol(U, Decl(tupleComplexity.ts, 6, 33)) ->T : Symbol(T, Decl(tupleComplexity.ts, 6, 17)) ->U : Symbol(U, Decl(tupleComplexity.ts, 6, 33)) - -type Result = ConcatTuple<[number, string], [boolean]>; ->Result : Symbol(Result, Decl(tupleComplexity.ts, 6, 66)) ->ConcatTuple : Symbol(ConcatTuple, Decl(tupleComplexity.ts, 3, 45)) - -// Result should be inferred as [number, string, boolean] -const concatenated: Result = [1, "foo", true]; // Valid ->concatenated : Symbol(concatenated, Decl(tupleComplexity.ts, 9, 5)) ->Result : Symbol(Result, Decl(tupleComplexity.ts, 6, 66)) - -// Map types on tuples +>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 = { [K in keyof T]: string }; ->Stringify : Symbol(Stringify, Decl(tupleComplexity.ts, 9, 46)) ->T : Symbol(T, Decl(tupleComplexity.ts, 12, 15)) ->K : Symbol(K, Decl(tupleComplexity.ts, 12, 37)) ->T : Symbol(T, Decl(tupleComplexity.ts, 12, 15)) - -type Mapped = Stringify<[number, boolean]>; ->Mapped : Symbol(Mapped, Decl(tupleComplexity.ts, 12, 61)) ->Stringify : Symbol(Stringify, Decl(tupleComplexity.ts, 9, 46)) - -// Should infer as [string, string] -const mapped: Mapped = ["123", "true"]; // Valid ->mapped : Symbol(mapped, Decl(tupleComplexity.ts, 15, 5)) ->Mapped : Symbol(Mapped, Decl(tupleComplexity.ts, 12, 61)) - -// Complex unions within tuples -type NestedUnion = [string, [boolean | number]]; ->NestedUnion : Symbol(NestedUnion, Decl(tupleComplexity.ts, 15, 39)) - -const nested: NestedUnion = ["test", [true]]; // Valid ->nested : Symbol(nested, Decl(tupleComplexity.ts, 19, 5)) ->NestedUnion : Symbol(NestedUnion, Decl(tupleComplexity.ts, 15, 39)) - -const nested2: NestedUnion = ["test", [42]]; // Valid ->nested2 : Symbol(nested2, Decl(tupleComplexity.ts, 20, 5)) ->NestedUnion : Symbol(NestedUnion, Decl(tupleComplexity.ts, 15, 39)) +>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)) diff --git a/tests/baselines/reference/tupleComplexity.types b/tests/baselines/reference/tupleComplexity.types index 18d28f5f11e6a..721aa59a83b27 100644 --- a/tests/baselines/reference/tupleComplexity.types +++ b/tests/baselines/reference/tupleComplexity.types @@ -1,14 +1,18 @@ //// [tests/cases/compiler/tupleComplexity.ts] //// === tupleComplexity.ts === -// Tuple union with simple cases - should not produce TS2590 -type TupleUnion = [string, number] | [boolean, string]; ->TupleUnion : TupleUnion -> : ^^^^^^^^^^ - -const example1: TupleUnion = ["hello", 42]; // Valid ->example1 : TupleUnion -> : ^^^^^^^^^^ +// 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 : TupleUnion1 +> : ^^^^^^^^^^^ + +const valid1: TupleUnion1 = ["hello", 42]; // ✅ Should pass +>valid1 : TupleUnion1 +> : ^^^^^^^^^^^ >["hello", 42] : [string, number] > : ^^^^^^^^^^^^^^^^ >"hello" : "hello" @@ -16,9 +20,9 @@ const example1: TupleUnion = ["hello", 42]; // Valid >42 : 42 > : ^^ -const example2: TupleUnion = [true, "world"]; // Valid ->example2 : TupleUnion -> : ^^^^^^^^^^ +const valid2: TupleUnion1 = [true, "world"]; // ✅ Should pass +>valid2 : TupleUnion1 +> : ^^^^^^^^^^^ >[true, "world"] : [true, string] > : ^^^^^^^^^^^^^^ >true : true @@ -26,74 +30,131 @@ const example2: TupleUnion = [true, "world"]; // Valid >"world" : "world" > : ^^^^^^^ -// Complex tuple concatenation - TS2590 currently triggered +// Extended union - Should trigger TS2590 +type TupleUnion2 = [string, number] | [boolean, string]; +>TupleUnion2 : TupleUnion2 +> : ^^^^^^^^^^^ + +type ComplexTuple = [...TupleUnion2, string]; // ❌ Should trigger TS2590 +>ComplexTuple : [string, number, string] | [boolean, string, string] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +const invalid: ComplexTuple = ["hello", 42, "world"]; // Should fail with TS2590 +>invalid : [string, number, string] | [boolean, string, string] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>["hello", 42, "world"] : [string, number, string] +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>"hello" : "hello" +> : ^^^^^^^ +>42 : 42 +> : ^^ +>"world" : "world" +> : ^^^^^^^ + +// --- Tuple Concatenations --- + +// Manageable concatenation - Should work type ConcatTuple = [...T, ...U]; >ConcatTuple : [...T, ...U] > : ^^^^^^^^^^^^ -type Result = ConcatTuple<[number, string], [boolean]>; ->Result : [number, string, boolean] -> : ^^^^^^^^^^^^^^^^^^^^^^^^^ - -// Result should be inferred as [number, string, boolean] -const concatenated: Result = [1, "foo", true]; // Valid ->concatenated : [number, string, boolean] -> : ^^^^^^^^^^^^^^^^^^^^^^^^^ ->[1, "foo", true] : [number, string, true] -> : ^^^^^^^^^^^^^^^^^^^^^^ ->1 : 1 -> : ^ ->"foo" : "foo" -> : ^^^^^ +type Result1 = ConcatTuple<[string, number], [boolean]>; // ✅ Should infer [string, number, boolean] +>Result1 : [string, number, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + +const concat1: Result1 = ["hello", 42, true]; // ✅ Should pass +>concat1 : [string, number, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>["hello", 42, true] : [string, number, true] +> : ^^^^^^^^^^^^^^^^^^^^^^ +>"hello" : "hello" +> : ^^^^^^^ +>42 : 42 +> : ^^ >true : true > : ^^^^ -// Map types on tuples +// Excessively large concatenation - Should trigger TS2590 +type LargeConcat = ConcatTuple<[...Array<100>], [...Array<100>]>; +>LargeConcat : 100[] +> : ^^^^^ + +// ❌ Should trigger TS2590 for excessive complexity + +// --- Mapped Types on Tuples --- + +// Simple mapping - Should work type Stringify = { [K in keyof T]: string }; >Stringify : Stringify > : ^^^^^^^^^^^^ -type Mapped = Stringify<[number, boolean]>; ->Mapped : [string, string] -> : ^^^^^^^^^^^^^^^^ - -// Should infer as [string, string] -const mapped: Mapped = ["123", "true"]; // Valid ->mapped : [string, string] -> : ^^^^^^^^^^^^^^^^ ->["123", "true"] : [string, string] -> : ^^^^^^^^^^^^^^^^ ->"123" : "123" -> : ^^^^^ +type MappedTuple1 = Stringify<[number, boolean]>; // ✅ Should infer [string, string] +>MappedTuple1 : [string, string] +> : ^^^^^^^^^^^^^^^^ + +const map1: MappedTuple1 = ["42", "true"]; // ✅ Should pass +>map1 : [string, string] +> : ^^^^^^^^^^^^^^^^ +>["42", "true"] : [string, string] +> : ^^^^^^^^^^^^^^^^ +>"42" : "42" +> : ^^^^ >"true" : "true" > : ^^^^^^ -// Complex unions within tuples -type NestedUnion = [string, [boolean | number]]; ->NestedUnion : NestedUnion -> : ^^^^^^^^^^^ +// --- Nested Tuples --- -const nested: NestedUnion = ["test", [true]]; // Valid ->nested : NestedUnion -> : ^^^^^^^^^^^ ->["test", [true]] : [string, [true]] -> : ^^^^^^^^^^^^^^^^ ->"test" : "test" -> : ^^^^^^ ->[true] : [true] +// Deeply nested tuple - Should trigger TS2590 +type DeepTuple = [string, [boolean | number, [boolean | number, [boolean | number]]]]; +>DeepTuple : DeepTuple +> : ^^^^^^^^^ + +type Nested = [...DeepTuple, string]; // ❌ Should trigger TS2590 +>Nested : [string, [number | boolean, [number | boolean, [number | boolean]]], string] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +const deep: Nested = ["root", [true, [42, [false]]], "leaf"]; // Should fail with TS2590 +>deep : [string, [number | boolean, [number | boolean, [number | boolean]]], string] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>["root", [true, [42, [false]]], "leaf"] : [string, [true, [number, [false]]], string] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>"root" : "root" > : ^^^^^^ +>[true, [42, [false]]] : [true, [number, [false]]] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ >true : true > : ^^^^ - -const nested2: NestedUnion = ["test", [42]]; // Valid ->nested2 : NestedUnion -> : ^^^^^^^^^^^ ->["test", [42]] : [string, [number]] -> : ^^^^^^^^^^^^^^^^^^ ->"test" : "test" +>[42, [false]] : [number, [false]] +> : ^^^^^^^^^^^^^^^^^ +>42 : 42 +> : ^^ +>[false] : [false] +> : ^^^^^^^ +>false : false +> : ^^^^^ +>"leaf" : "leaf" > : ^^^^^^ ->[42] : [number] -> : ^^^^^^^^ + +// --- Invalid Cases --- + +// Expected type mismatches (non-TS2590 failures) +const invalidConcat1: Result1 = ["hello", 42]; // ❌ Error: Missing boolean +>invalidConcat1 : [string, number, boolean] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>["hello", 42] : [string, number] +> : ^^^^^^^^^^^^^^^^ +>"hello" : "hello" +> : ^^^^^^^ +>42 : 42 +> : ^^ + +const invalidMap1: MappedTuple1 = [42, true]; // ❌ Error: Expected strings +>invalidMap1 : [string, string] +> : ^^^^^^^^^^^^^^^^ +>[42, true] : [number, boolean] +> : ^^^^^^^^^^^^^^^^^ >42 : 42 > : ^^ +>true : true +> : ^^^^ diff --git a/tests/cases/compiler/tupleComplexity.ts b/tests/cases/compiler/tupleComplexity.ts index 7b53e3a174036..c58911da01834 100644 --- a/tests/cases/compiler/tupleComplexity.ts +++ b/tests/cases/compiler/tupleComplexity.ts @@ -1,21 +1,44 @@ -// Tuple union with simple cases - should not produce TS2590 -type TupleUnion = [string, number] | [boolean, string]; -const example1: TupleUnion = ["hello", 42]; // Valid -const example2: TupleUnion = [true, "world"]; // Valid +// Tests for TS2590: "Expression produces a union type that is too complex to represent" -// Complex tuple concatenation - TS2590 currently triggered +// --- 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, ...U]; -type Result = ConcatTuple<[number, string], [boolean]>; -// Result should be inferred as [number, string, boolean] -const concatenated: Result = [1, "foo", true]; // Valid +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 -// Map types on tuples +// --- Mapped Types on Tuples --- + +// Simple mapping - Should work type Stringify = { [K in keyof T]: string }; -type Mapped = Stringify<[number, boolean]>; -// Should infer as [string, string] -const mapped: Mapped = ["123", "true"]; // Valid - -// Complex unions within tuples -type NestedUnion = [string, [boolean | number]]; -const nested: NestedUnion = ["test", [true]]; // Valid -const nested2: NestedUnion = ["test", [42]]; // Valid +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