sshmaxime
sshmaxime

Reputation: 537

Change function arguments based on function in param

I have this:

type myType = {};

const alpha = (a: string, b:string) => {};
const beta = (a: number, b:number) => {};

export const deploy = async <C extends myType>(toDeployContract: C): C => {}

What I want to achieve is that deploy accept a function as parameter like that:

export const deploy = async <C extends myType>(func: (...args: any[]) => any, toDeployContract: C): C => {}

And that doing so, typed deploy last parameters automatically based on the passed function. Basically copying every parameter of the pass function.

// If alpha function
deploy(alpha, "hello", "world");

// If beta function
deploy(beta, 1, 2);

Is that doable ?

Upvotes: 2

Views: 714

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42188

It's very doable! You will want your deploy function to have a generic type parameter that represents the type of the function. You can make use of the built-in utility types Parameters<T> and ReturnType<T> to get the arguments and return type of the function.

I'm not exactly sure how your C extends myType comes into play here. It seems like it's the return type of the function? You could use C as a second generic type parameter but I would recommend leaving it off.

export const deploy = async <T extends (...args: any[]) => any>(
  func: T, 
  ...args: Parameters<T>
): Promise<ReturnType<T>> => {
    return func(...args);
}

We say that deploy is based on a type T which must be a function. The first argument of deploy is the function T and the remaining arguments are the arguments of that function. deploy returns a Promise which resolves to the return type of the function T.

This allows the usages that you want and gives errors if there is a mismatch between the function and the arguments.

// If alpha function
deploy(alpha, "hello", "world");

// If beta function
deploy(beta, 1, 2);

// Expect error
deploy(alpha, 1, 2); // Argument of type 'number' is not assignable to parameter of type 'string'.(2345)

Typescript Playground Link


If you wanted to use your C type, that might look something like this. We say that T is a function which must return a value that is assignable to C.

type myType = {
    a: string;
};

const alpha = (a: string, b:string) => ({a, b});
const beta = (a: number, b:number) => ({a, b});

export const deploy = async <C extends myType, T extends (...args: any[]) => C>(
  func: T,
  ...args: Parameters<T>
): Promise<C> => {
    return func(...args);
}

The alpha function is still fine because it returns {a: string; b: string;} which extends {a: string}. But you can no longer use the beta function because its return type is not assignable to myType.

So the C parameter allows you to enforce a restriction on what type of function you can deploy with.

Typescript Playground Link

Upvotes: 2

Related Questions