Ran Lottem
Ran Lottem

Reputation: 486

Infer function parameter types and spread them in Typescript

Given a function type F, I want to create a new type that's "composable before" F, meaning it takes and returns the same arguments that F takes. For example:

const stringToNumber = (s: string) => s.length;
const stringToString: ComposableOn<(s: string) => number> = (s: string) => s + s;

const composedResult = stringToNumber(stringToString('a'));

I've not been able to properly define the type ComposableOn, though. Here are things I've tried:

type ComposableBefore<F extends (...args: any) => any> = (args: Parameters<F>) => Parameters<F>;
type StoN = (s: string) => number;

const sToS: ComposableBefore<StoN> = (s: string) => s + s; // error: the type is [string] => [string] and not string => string
type ComposableBefore<F extends (...args: any) => any> = Parameters<F> extends Array<infer U>? (...args: Parameters<F>) => U: never;

const complex: ComposableBefore<(a: string, b: number, c: { d: number, e: string }) => number> = (a, b, c) => c; // not good either, since it can return a value of any type of the original function's argument types.

What would be a correct way to type this?

Upvotes: 2

Views: 2699

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249656

You can use ... to spread the Parameters directly in the function signature. As for the return, since you want to support multiple parameters, the return type should be a tuple and thus you will need to spread the return of your function:

const stringToNumber = (s: string) => s.length;
const stringToString: ComposableBefore<(s: string) => number> = (s: string) => [s + s];

const composedResult = stringToNumber(...stringToString('a'));

type ComposableBefore<F extends (...args: any) => any> = (...args: Parameters<F>) => Parameters<F>;
type StoN = (s: string) => number;

You could also consider adding a special case for single parameter functions, if this is the common use case:

const stringToNumber = (s: string) => s.length;
const stringToString: ComposableBefore<(s: string) => number> = (s: string) => s + s;

const composedResult = stringToNumber(stringToString('a'));

const multiStringToNumber = (s: string, s2: string) => s.length + s2.length;
const multiStringToString: ComposableBefore<typeof multiStringToNumber> = (s: string, s2: string) => [s + s, s2 + s2];

const multiComposedResult = multiStringToNumber(...multiStringToString('a', 'b'));

type ComposableBefore<F extends (...args: any) => any> =
    F extends (a: infer U) => any ? (...args: Parameters<F>) => U :
        (...args: Parameters<F>) => Parameters<F>;

Upvotes: 1

Related Questions