I'm trying to define a type which gets a function type as a generic parameter and returns a function type which is the same as the input function type except it has one more argument at the end:
type AugmentParam<F extends (...args: any[]) => any, ExtraParam> = F extends (
...args: infer Args
) => infer R
? (
...args: [
...Args,
ExtraParam
]
) => R
: never
An example of the usage:
type F = (x: number) => boolean
type F2 = AugmentParam<F, string> // (x: number, arg2: string) => boolean
...Args
seems not working, however if I change it to something like this it works:
type AugmentParam<F extends (...args: any[]) => any, ExtraParam> = F extends (
...args: infer Args
) => infer R
? (
...args: [
Args[0],
Args[1] /* Spread doesn't work here, so it doesn't work for arbitrary number of arguments :( */,
ExtraParam
]
) => R
: never
But it only works for a specific number of arguments and I need to define one such type for each n-ary function.
TypeScript can fairly easily represent prepending a type onto a tuple type, called Cons<H, T>
like this:
type Cons<H, T extends readonly any[]> =
((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never
type ConsTest = Cons<1, [2, 3, 4]>;
// type ConsTest = [1, 2, 3, 4]
You can use this definition along with conditional mapped tuple types to produce a Push<T, V>
to append a type onto the end of a tuple:
type Push<T extends readonly any[], V> = Cons<any, T> extends infer A ?
{ [K in keyof A]: K extends keyof T ? T[K] : V } : never
type PushTest = Push<[1, 2, 3], 4>;
// type PushTest = [1, 2, 3, 4]
but this definition of Push
is fragile. If the T
tuple has optional elements, or if it comes from the parameter list of a function, you'll notice that the compiler "shifts" the optional markers and parameter names one element to the right:
type Hmm = (...args: Push<Parameters<(optStrParam?: string) => void>, number>) => void;
// type Hmm = (h: string | undefined, optStrParam?: number) => void
Parameter names are not really part of the type so while it's annoying it's not affecting the actual type. Appending an argument after an optional one is... strange, so I'm not sure what the right behavior there. Not sure if these are dealbreakers for you, but be warned.
Anyway your AugmentParam
would look like:
type AugmentParam<F extends (...args: any[]) => any, ExtraParam> =
(...args: Extract<Push<Parameters<F>, ExtraParam>, readonly any[]>)
=> ReturnType<F>
and it works (with the earlier caveats):
type F = (x: number) => boolean
type F2 = AugmentParam<F, string>
// type F2 = (h: number, x: string) => boolean
type F3 = AugmentParam<F2, boolean>
// type F3 = (h: number, h: string, x: boolean) => boolean
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