Skip to content

TS2590: Expression produces a union type that is too complex to represent, with simple file using TuplesΒ #58268

Open

Description

@ehmicky

πŸ”Ž Search Terms

TS2590.

There are several issues referring to TS2590, but none with the specific, simple reproduction code below.

πŸ•— Version & Regression Information

This changed between versions 5.0.0-dev.20221117 and 5.0.0-dev.20221118

⏯ Playground Link

https://www.typescriptlang.org/dev/bug-workbench/?ssl=2&ssc=16&pln=2&pc=1#code/PTAEAEDMEsBsFMB2BDAtvAXKA9o+A6AFwGcBYAKBAlmgCMt5i8APQi6VAB2wCdDQA3tELxUAGlCEAnp3igAKgFdOCAJIjUAX1CQe2VKADk+YIQDu2QwG4KFAMa5i-YaIDC2TlKxKV8daNAAXlAXVBtyCiooOCQ0TEkLIjJyeGZuPlAAE3g7WGQeOQdEJxCNb2U1DXCKVPT+aVkFCr8NAB4fBCDQAG0ARgBdCQAFPVk+KS7DAAZDAD4ugQoASAA3ZFhFeI65VJFETOIegdAAHx6AJn7lpYB+UBGPeHHQXaQDoxnTo2J9eEMvwyQXgAUWQdgAFv8zoZUMhOFCjDBYCIeAjDAVMoo7H8ARisfAAErQADm4MIaJg+wp0H2qn2qWpsFgaIcngA6sJwTS0UhCDxoIw0QBreBSYhotYbQUAmm5RTZcUAyB5QgAWThFJVaOQ5Out3uoye0herDehxFUmwkCavlAd223QeY2k-VAWEU9Mp8Eyevdnpp3uufuyXsy4U0ViAA

πŸ’» Code

tsc --lib esnext one.ts 
one.ts:3:7 - error TS2590: Expression produces a union type that is too complex to represent.

3 const itemCopy: TupleItem = item;
        ~~~~~~~~

Found 1 error in one.ts:3
// one.ts
import {item, type TupleItem} from './two';

const itemCopy: TupleItem = item;
// two.ts
export declare const item: TupleItem;

export type TupleItem<Tuple = [1], Property = '0'> = {
	value: Tuple extends [1] | [2]
		? Property extends '0' | 'some' | 'forEach' | 'map' | 'filter' | 'reduce' | 'reduceRight' | 'find' | 'findIndex' | 'fill' | 'copyWithin' | 'entries' | 'keys' | 'values' | 'includes' | 'flatMap' | 'flat' | 'at'
			? Property extends keyof Tuple ? Tuple[Property] : undefined
			: undefined
		: undefined;
};

πŸ™ Actual behavior

The types fail due to: TS2590: Expression produces a union type that is too complex to represent.

πŸ™‚ Expected behavior

The types above are quite simple. Even though the union type has ~20 values, that should not be enough to make the types fail.

Additional information about the issue

Small changes in the code above will fail to reproduce the error, such as:

  • Merging both files into a single one
  • Not using a declare const line
  • Not using generics, or not using generic default types
  • Using Tuple extends ... instead of {value: Tuple extends ...}
  • Not using --lib esnext

Activity

  1. fatcerberus commented on Apr 21, 2024

    @fatcerberus

    Even with everything in one file, this code just doesn't seem to work at all:
    TS Playground

    Type 'Property' cannot be used to index type 'Tuple'.

  2. ehmicky commented on Apr 21, 2024

    @ehmicky
    Author

    Thanks for looking into this Bruce Pascoe (@fatcerberus).

    That's a different error. This one can be fixed by using this instead:

    export type TupleItem<Tuple = [1], Property = '0'> = {
    	value: Tuple extends [1] | [2]
    		? Property extends '0' | 'some' | 'forEach' | 'map' | 'filter' | 'reduce' | 'reduceRight' | 'find' | 'findIndex' | 'fill' | 'copyWithin' | 'entries' | 'keys' | 'values' | 'includes' | 'flatMap' | 'flat' | 'at'
    			? Property extends keyof Tuple ? Tuple[Property] : undefined
    			: undefined
    		: undefined;
    };

    That failure is expected and relates to whether Property can index Tuple.

    The bug above instead relates to TypeScript creating a big union type, which makes type checking crash, which is different. To be able to reproduce it, it seems like 2 separate files (exactly identical to the above) must be used.

  3. fatcerberus commented on Apr 21, 2024

    @fatcerberus

    That failure is expected and relates to whether Property can index Tuple.

    The code as written in the OP produces that error, including when split into two files. That's confusing since it's not the error message you're reporting and makes it look like the repro itself is erroneous.

    For what it's worth I wasn't able to reproduce the too-large-union-type error using the bug workbench (which, unlike the playground, does support multiple files):
    Bug Workbench

  4. ehmicky commented on Apr 21, 2024

    @ehmicky
    Author

    The code as written in the OP produces that error, including when split into two files. That's confusing since it's not the error message you're reporting and makes it look like the repro itself is erroneous.

    I have updated the original issue to reflect this, and avoid the confusion with that other unrelated error.

    For what it's worth I wasn't able to reproduce the too-large-union-type error using the bug workbench (which, unlike the playground, does support multiple files):

    Oh I did not know the Bug Workbench supported multiple files.

    You could not reproduce the bug because it requires using --lib esnext.

    The following is a reproduction of the bug using the Bug Workbench.

    That link works. However, interestingly enough, if you click on the URL (which adds ?lib=lib.esnext.d) then refresh the page, the Bug Workbench fails to load.

  5. RyanCavanaugh commented on Apr 23, 2024

    @RyanCavanaugh
    Member

    Quick notes

    The stack looks like this

    We're testing if

    TupleItem<[1], "0"> --assignable to--> TupleItem
    

    To do this we compute the variance of TupleItem using instantiation with two marker types

    TupleItem<?_1, Property> --assignable to--> TupleItem<?_2, Property>
    

    Then we go down the structural comparison on .value, which results in checking

    ? extends [1] | [2] ? Property extends "0" | "some" | "forEach" | "map" | "filter" | "reduce" | "reduceRight" | "find" | "findIndex" | "fill" | "copyWithin" | ... 6 more ... | "at" ? Property extends keyof ? ? ?[Property] : undefined : undefined : undefined
    
    --assignable to-->
    
    ? extends [1] | [2] ? Property extends "0" | "some" | "forEach" | "map" | "filter" | "reduce" | "reduceRight" | "find" | "findIndex" | "fill" | "copyWithin" | ... 6 more ... | "at" ? Property extends keyof ? ? ?[Property] : undefined : undefined : undefined
    

    This eventually hits the limiting case

                        // We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of
                        // the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type
                        // exceeds 100000 constituents, report an error.
                        if (!checkCrossProductUnion(typeSet)) {
    

    because typeSet has all these types:

    '1 | 2'
    '((predicate: (value: 1, index: number, array: 1[]) => unknown, thisArg?: any) => boolean) | ((predicate: (value: 2, index: number, array: 2[]) => unknown, thisArg?: any) => boolean)'
    '((callbackfn: (value: 1, index: number, array: 1[]) => void, thisArg?: any) => void) | ((callbackfn: (value: 2, index: number, array: 2[]) => void, thisArg?: any) => void)'
    '(<U>(callbackfn: (value: 1, index: number, array: 1[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: 2, index: number, array: 2[]) => U, thisArg?: any) => U[])'
    '{ <S extends 1>(predicate: (value: 1, index: number, array: 1[]) => value is S, thisArg?: any): S[]; (predicate: (value: 1, index: number, array: 1[]) => unknown, thisArg?: any): 1[]; } | { <S extends 2>(predicate: (value: 2, index: number, array: 2[]) => value is S, thisArg?: any): S[]; (predicate: (value: 2, index...'
    '{ (callbackfn: (previousValue: 1, currentValue: 1, currentIndex: number, array: 1[]) => 1): 1; (callbackfn: (previousValue: 1, currentValue: 1, currentIndex: number, array: 1[]) => 1, initialValue: 1): 1; <U>(callbackfn: (previousValue: U, currentValue: 1, currentIndex: number, array: 1[]) => U, initialValue: U): U;...'
    '{ (callbackfn: (previousValue: 1, currentValue: 1, currentIndex: number, array: 1[]) => 1): 1; (callbackfn: (previousValue: 1, currentValue: 1, currentIndex: number, array: 1[]) => 1, initialValue: 1): 1; <U>(callbackfn: (previousValue: U, currentValue: 1, currentIndex: number, array: 1[]) => U, initialValue: U): U;...'
    '{ <S extends 1>(predicate: (value: 1, index: number, obj: 1[]) => value is S, thisArg?: any): S | undefined; (predicate: (value: 1, index: number, obj: 1[]) => unknown, thisArg?: any): 1 | undefined; } | { ...; }'
    '((predicate: (value: 1, index: number, obj: 1[]) => unknown, thisArg?: any) => number) | ((predicate: (value: 2, index: number, obj: 2[]) => unknown, thisArg?: any) => number)'
    '((value: 1, start?: number | undefined, end?: number | undefined) => [1]) | ((value: 2, start?: number | undefined, end?: number | undefined) => [2])'
    '((target: number, start: number, end?: number | undefined) => [1]) | ((target: number, start: number, end?: number | undefined) => [2])'
    '(() => IterableIterator<[number, 1]>) | (() => IterableIterator<[number, 2]>)'
    '(() => IterableIterator<number>) | (() => IterableIterator<number>)'
    '(() => IterableIterator<1>) | (() => IterableIterator<2>)'
    '((searchElement: 1, fromIndex?: number | undefined) => boolean) | ((searchElement: 2, fromIndex?: number | undefined) => boolean)'
    '(<U, This = undefined>(callback: (this: This, value: 1, index: number, array: 1[]) => U | readonly U[], thisArg?: This | undefined) => U[]) | (<U, This = undefined>(callback: (this: This, value: 2, index: number, array: 2[]) => U | readonly U[], thisArg?: This | undefined) => U[])'
    '(<A, D extends number = 1>(this: A, depth?: D | undefined) => FlatArray<A, D>[]) | (<A, D extends number = 1>(this: A, depth?: D | undefined) => FlatArray<A, D>[])'
    '((index: number) => 1 | undefined) | ((index: number) => 2 | undefined)'
    

    I believe the definitions in esnext are near the bottom; the large number of unions here means the cross-product is very large compared to when those definitions aren't present

  6. RyanCavanaugh commented on Apr 23, 2024

    @RyanCavanaugh
    Member

    Not really sure this is going to be "fixable". What's the desired behavior of TupleItem ? It's probably possible to write this in a way that doesn't do this computation.

  7. added
    Needs More InfoThe issue still hasn't been fully clarified
    and removed
    Needs InvestigationThis issue needs a team member to investigate its status.
    on May 8, 2024
  8. added
    Possible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some cases
    and removed
    Needs More InfoThe issue still hasn't been fully clarified
    on Aug 7, 2024
  9. daviareias commented on Jan 17, 2025

    @daviareias

    This is just typescript avoiding creating a huge type, I got this when I was trying to create a template literal that was the representation of a base 64 5 number plus a sign (e.g. "+00 00 00 00 00"), which equates to 2147483648 combinations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Domain: check: Big UnionsThe root cause is ultimately that big unions interact poorly with complex structuresHelp WantedYou can do thisPossible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some cases

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions