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

Variadic element of tuple-like intersection types are spreading incorrectly #40945

Open
ENvironmentSet opened this issue Oct 5, 2020 · 6 comments
Labels
Bug A bug in TypeScript
Milestone

Comments

@ENvironmentSet
Copy link

ENvironmentSet commented Oct 5, 2020

TypeScript Version: 4.0.3

Search Terms: variadic tuple, variadic element, intersection type, tuple-like type

Code

type T1 = [...unknown[] & []]; // never[]
type T2 = [...number[] & [1, 2, 3]]; // (3 | 1 | 2)[]

Expected behavior: for T1, I expected it to be [](empty tuple type) and for T2, I expected it to be [1, 2, 3].

Actual behavior: Both T1 and T2 became Array rather than tuple somehow. (T1 is deduced to never[], T2 is deduced to (3 | 1 | 2)[])

detailed explanation

In some perspective, unknown could be recognized as type of all types. Therefore, [unknown] is type of all type-level 1-tuples and intuitively, unknown[] is type of all type-level n-tuples.

So [] <: unknown[] is true (i.e. all empty type-level tuple is subtype of all type-level n-tuples) and you can check it simply with this code:

type M = [] & unknown[] extends [] ? [] extends [] & unknown[] ? true : false : false; // true

However, in spite of the fact that [] is subtype of unknown[] therefore [] & unknown[] is actually [], variadic element of [] & unknown[] somehow spreads as never[], which is incorrect and unintuitive.

Playground Link: Playground

@andrewbranch
Copy link
Member

incorrect and unintuitive

Unintuitive, yes; not as good as we could do, yes; but “incorrect” is somewhat debatable. (As an aside, this issue led to a great conversation about whether never[] is conceptually the same as [], despite specific differences in their assignability.) At any rate, since never[] is not assignable to [], that’s a good argument for T1 being “incorrect,” but (3 | 1 | 2)[] is clearly a valid representation of [1, 2, 3]; it’s just not a very good one 😄

Out of curiosity, since there are 4 👍 on an issue that seems somewhat arcane, is this coming up in practice somewhere?

@ENvironmentSet
Copy link
Author

I was building type-level programming library since TS 4.1 finally added recursive conditional type alias. But after few hours, I encountered this issue and talked with others in twitter for a few days. That 👍seems to be added by people I know or discussed with me about this.

@ENvironmentSet
Copy link
Author

ENvironmentSet commented Oct 8, 2020

I agree on that T1 is not being incorrect, sorry for misusing word. However, I think we all agree on that ( 3 | 2 | 1 )[] is not a proper or useful typing at all at this point since some important informations are discarded and it might cause some unexpected problems. 🙂

(Sorry for loose formatting, I wrote this comment with my mobile phone...)

@andrewbranch andrewbranch added Bug A bug in TypeScript Effort: Moderate Requires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual". Help Wanted You can do this labels Oct 8, 2020
@andrewbranch andrewbranch added this to the Backlog milestone Oct 8, 2020
@sethveale
Copy link

I was building type-level programming library since TS 4.1 finally added recursive conditional type alias. But after few hours, I encountered this issue and talked with others in twitter for a few days. That 👍seems to be added by people I know or discussed with me about this.

Similar scenario for me. I was trying to map nested tuple types.

@andrewbranch andrewbranch removed Effort: Moderate Requires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual". Help Wanted You can do this labels Oct 27, 2020
@mkantor
Copy link
Contributor

mkantor commented Mar 11, 2022

This also becomes an array type, even though both sides of the intersection were tuples to begin with:

type X = [...[1, 2] & [1, 2 | 3]]
//   ^? type X = (2 | 1)[]

@geoffreytools
Copy link

geoffreytools commented Mar 11, 2022

Actually, this is also broken [...[1] & [unknown]]

I had a similar "issue" when trying to get a tuple back from a tuple augmented with other properties.

Investigation led me to the discovery that even [...([1,2,3] & unknown[])] was lossy

You will find bellow my different attempts at getting the tuple back. Explicitly iterating over the numeric keys worked but I was surprised I had to go this far.

type A = [1,2,3] & { foo: unknown };

// -> (3 | 1 | 2)[]
type B = [...A]

// -> { [x: number]: 3 | 1 | 2 }
type C = { [K in keyof A as K extends number ? K : never]: A[K] }

// -> [2 | 1 | 3]
type D = CloneTuple<A>

type CloneTuple<T extends unknown[], R extends unknown[]=[]> =
    T extends [infer A, ...infer Rest]
    ? CloneTuple<Rest, [...R, A]>
    : R

// -> [1, 2, 3]
type E = ToTuple<A>

type ToTuple<
    T extends unknown[],
    L extends number=T['length'],
    C extends number = 0,
    R extends unknown[]=[]
> = C extends L ? R : ToTuple<
    T,
    L,
    [1,2,3,4,5,6,7,8,9,10][C],
    [...R, T[C]]
>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
5 participants