I can add element to the begining of the tuple, or remove it from there
type ShiftTuple<T extends any[]> = ((...t: T) => void) extends ((x: infer X, ...r: infer R) => void) ? R : never;
type UnshiftTuple<X, T extends any[]> = ((x: X, ...t: T) => void) extends ((...r: infer R) => void) ? R : never;
But I have difficulties with doing the same things with the last element instead of the first.
Even if I write the function
function f<X, Z extends any[]>(x: X, ...args: Z) {
return [...args, x]
}
typescript says it returns any[]
.
Is there a way to push new type into the end of the tuple like (insupported)
export type PushTuple<T extends any[], X> = [...T, X];
UPDATE FOR TS4.0+
TypeScript 4.0 introduced variadic tuple types, which supports Push
ing to the end of a tuple in a much more straightforward way... like this:
type Push<T extends readonly any[], V> = [...T, V];
OLD ANSWER FOR TS3.9-:
I usually define Push
in terms of what you're calling Unshift
(but which I call Cons
), like this:
type Cons<H, T extends readonly any[]> =
((head: H, ...tail: T) => void) extends ((...cons: infer R) => void) ? R : never;
type Push<T extends readonly any[], V>
= T extends any ? Cons<void, T> extends infer U ?
{ [K in keyof U]: K extends keyof T ? T[K] : V } : never : never;
The way Push<T, V>
works is to make a tuple one element longer than T
(which it does with Cons
) and then it maps over it. It fills the initial elements of the output with the corresponding elements from T
, and then anything left over it fills in V
. The only element index in a mapped tuple of length n+1 that is not an index of a tuple of length n is the index n itself, so that means the last element of the new tuple is V
. (I made sure to distribute over T
in case it's a union.)
Note that this only works for non-edge cases... don't expect readonly
, optional, and rest tuple elements or non-tuple arrays to behave well; you'd have to work around those restrictions yourself if you want to define it.
RESUME REGULAR ANSWER:
So then you can define f()
like this:
function f<X, Z extends any[]>(x: X, ...args: Z): Push<Z, X>;
function f(x: any, ...args: any[]): any[] {
return [...args, x]
}
(note that I use an overload to free myself from worrying about the compiler trying and failing to understand that the implementation complies with Push<Z, X>
:
And it works as you expect:
const s = f(4, "hey", false);
// const s: [string, boolean, number]
console.log(s); // ["hey", false, 4]
Okay, hope that helps; good luck!
Link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With