I\'ve found out how to push type to the end of the tuple:
type Cons =
((head: H, ...tail: T) => void) extends ((...cons
UPDATE: TypeScript 4.0 will feature variadic tuple types, which will allow more flexible built-in tuple manipulation. Push
will be simply implemented as [...T, V]
.
Pre-TS4.0 answer:
Ugh, why?! Ahem, I mean, I can maybe do this, but the more type-juggling involved, the less I'd recommend doing this for anything important. There's a library called ts-toolbelt which is close to being "officially supported" by TypeScript (although it doesn't work in the Playground, at least not yet, so I'm not about to make an Stack Overflow answer that requires it) where you can probably build something that works.
How I'd approach this is to convert tuples with optional elements to unions of tuples without them. Unfortunately, I'm missing a built-in way to take a number type like 6
and get a tuple of that length. So I'm making a hardcoded list of tuples-of-various-lengths that I can map over. You can extend it if you need this to work on longer tuples. Here we go:
type Cons =
((head: H, ...tail: T) => void) extends ((...cons: infer R) => void) ? R : never;
That's just the standard Cons<1, [2,3,4]>
becomes [1,2,3,4]
.
type Tup = [[], [0], [0, 0], [0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]; // make as long as you need
That's the big list of tuples. So Tup[4]
is [0,0,0,0]
, etc.
type TruncateTuple = Extract<
Tup[N] extends infer R ? { [K in keyof R]: K extends keyof T ? T[K] : never }
: never, readonly any[]>;
That type takes a tuple T
and a length N
and truncates T
to length N
. So TruncateTuple<[1,2,3,4], 2>
should be [1,2]
. It works by getting a tuple of length N
from Tup
, and maps over it with properties from T
.
type OptTupleToUnion =
TruncateTuple, T['length']>;
Here's the main event... OptTupleToUnion
takes a tuple T
and produces a union from it of non-optional tuples. It works by truncating Required
(that is, T
with optional elements turned into required ones) to length T['length']
which is the union of possible lengths of T
. So OptTupleToUnion<[1,2,3?,4?]>
should become [1,2] | [1,2,3] | [1,2,3,4]
.
Then I'll rename my old Push
out of the way to _Push
:
type _Push
= T extends any ? Cons extends infer U ?
{ [K in keyof U]: K extends keyof T ? T[K] : V } : never : never;
and make Push
act on OptTupleToUnion
instead of T
:
type Push = T extends any ?
_Push, V> : never;
(with the same T extends any ? ..T.. : never
to make sure unions get distributed)
Let's see if it works:
type A = Push<[1, 2, 3], 4>; // [1, 2, 3, 4]
type B = Push<[1, 2, 3?], 4>; // [1, 2, 3, 4] | [1, 2, 4]
Yay, looks good.