Skip to content

Commit

Permalink
Fix getTupleElementTypes to handle tuple types correctly and add deta…
Browse files Browse the repository at this point in the history
…iled 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.
  • Loading branch information
RyanMyrvold committed Nov 25, 2024
1 parent 9c6b2a2 commit 251c8ca
Show file tree
Hide file tree
Showing 6 changed files with 442 additions and 155 deletions.
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'.

76 changes: 51 additions & 25 deletions tests/baselines/reference/tupleComplexity.js
Original file line number Diff line number Diff line change
@@ -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 extends any[], U extends any[]> = [...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<T extends any[]> = { [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
Loading

0 comments on commit 251c8ca

Please sign in to comment.