Davis Yoshida
Davis Yoshida

Reputation: 1785

Typescript require arguments to match type signature of first argument

I have a Class A, which has function f (possibly overwritten by subclasses).

I would like to define a function, g so that g(cls, ...args) would only work if cls is a subclass of A and args matches the type signature of cls.f. Is there a way to do this?

Edit: I have changed the above to reflect the fact that the first argument to g is actually a constructor of a subclass of A, rather than an instance.

Upvotes: 2

Views: 659

Answers (1)

jcalz
jcalz

Reputation: 329808

Sure, you can use tuples in rest/spread types to get this since TS 3.0:

declare class A {
  f(...args: any[]): any;
}
declare function g<T extends A>(val: T, ...args: Parameters<T['f']>): void;

declare class B extends A {
  f(x: string, y: number): boolean;
}
declare const b: B;
g(b, "hey", 123); // okay
g(b, 123, "hey"); // error

The tuple rest/spread inference is hidden inside the Parameters type function defined in the standard library as:

type Parameters<T extends (...args: any[]) => any> = 
  T extends (...args: infer P) => any ? P : never;

Is that what you're looking for?


EDIT: if val is supposed to be a constructor, then you could do it like this:

declare function g<T extends A>(
  val: new(...args:any[])=>T, 
  ...args: Parameters<T['f']>
): void;

declare class B extends A {
  f(x: string, y: number): boolean;
}
g(B, "hey", 123); // okay
g(B, 123, "hey"); // error

Or essentially the same thing:

declare function g<T extends new (...args: any[]) => A>(
  val: T,
  ...args: Parameters<InstanceType<T>['f']>
): void;

depending on whether you want T to be a constructor type or an instance type.

Upvotes: 4

Related Questions