Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ehmicky opened this issue Apr 21, 2024 · 6 comments
Assignees
Labels
Needs More Info The issue still hasn't been fully clarified

Comments

@ehmicky
Copy link

ehmicky commented Apr 21, 2024

πŸ”Ž 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
@fatcerberus
Copy link

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'.

@ehmicky
Copy link
Author

ehmicky commented Apr 21, 2024

Thanks for looking into this @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.

@fatcerberus
Copy link

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

@ehmicky
Copy link
Author

ehmicky commented Apr 21, 2024

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.

@RyanCavanaugh
Copy link
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

@RyanCavanaugh
Copy link
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.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Apr 23, 2024
@ahejlsberg ahejlsberg added Needs More Info The issue still hasn't been fully clarified and removed Needs Investigation This issue needs a team member to investigate its status. labels May 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info The issue still hasn't been fully clarified
Projects
None yet
Development

No branches or pull requests

4 participants