Typescript: Get type of parameter list of a function (parametersof?)

Created on 27 Jul 2018  路  13Comments  路  Source: microsoft/TypeScript

Search Terms

type of function's parameters list; type of arguments list; tuples;

Suggestion

There are functions a() and b(). Function a() have the same signature (parameters) as function b().
Instead of manually copying list of parameters with their types from b() to a(), there should be a way to assign types of parameters to a() from b().

function a(...args: /* possible syntax starts */ parametersof b /*possible syntax ends*/ ) {
  b(...args)
}

function b (a: string, b: number) { /* ... */}

a('foo', 1) // ok
a('foo', 'bar') // compiler error "Argument of type 'bar' is not assignable to parameter of type 'number'"

parametersof ... should probably return a tuple.

Related question on Stackoverflow

Use Cases

  1. Decorators or simply reusing existing functions:
function a(...args: parametersof b) {
  // ... do some stuff 
  b(...args)
}
  1. API that provides lazy-loaded functions or provides functions based on environment:
import someFunc from './someFunc'
export const someAPI = {

  someFunc, // normal one, we just re-exporting this func

  async lazyFunc(...args: parametersof eagerFunc) { 
    const {eagerFunc} = await import('./eagerFunc') 
    return eagerFunc(...args)
  }, // lazy func. helps to reduce main bundle

  get node() {
    if (runningInNode()) {
      return {
        async nodeFunc(...args: parametersof nodeFunc) {
          const {nodeFunc} = await import('./nodeFunc') 
          return nodeFunc(...args)
        }
      }
    }
  } // here we do not add code, that is supposed to work only in node.js, to frontend bundle)
}

Current workarounds and their faults

function a(a: string, b: number) {
  b(...args)
}

function b (a: string, b: number) {/*...*/}

Above practice have these disadvantages:
1) When changing b's signature, we also have to change a's one. That is little bit harder maintenance and a window for new errors.
2) Contradicts with DRY principle

Comparison to tuples

I am aware of new "tuples" feature in TS 3.0, which is great! However using tuples for this use case requires creating new types, while "parametersof" solution will end up in minimum possible amount of code and maximum readability.

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. new expression-level syntax)
Question

Most helpful comment

Somewhere, @ahejlsberg proposed adding the following definition to lib.d.ts:

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

I think this is the right way to go here over a new operator.

If it's useful, here's ReturnType as well:

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

All 13 comments

Somewhere, @ahejlsberg proposed adding the following definition to lib.d.ts:

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

I think this is the right way to go here over a new operator.

If it's useful, here's ReturnType as well:

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

@bterlson thank you for quick response!

My thoughts on the solutions provided by you:

  1. It's soo cryptic. I have to take some time to decode it... this infer thing... and what ternary does actually do. And what is supposed to be T (I guess a function?).
  2. Where can I read more about this solution? I honestly searched, but failed to find. Or maybe can you explain?

P.S. And yes! I forgot about return types, they are also should have some way to be assigned from one func to another.

@anurbol if it gets added to lib.d.ts you can just use Parameters<some function type> without understanding how it works (much like how Pick or Record work today).

Probably the best resource to understand this stuff is the 2.8 release blog. Parameters and ReturnType are conditional types using the infer keyword in the conditional to tell TS to infer T's argument list or return type respectively.

Also, here's an example of using Parameters:

function foo(a: number, b: string) {
  return true;
}
type p = Parameters<typeof foo>;
// p = [number, string]

Thank you, @bterlson! I love TS even more now! :heart: And ReturnType (BTW one does not have to add it to lib.d.tsm it is already included in the core lib), fantastic!

@bterlson using TS 3.0.1, it seems like the definition above for Parameters just returns an empty Object type. Ex:

type Parameters<T> = T extends (... args: infer T) => any ? T : never; 
const x: test = (a: number) => 5;
type Params = Parameters<typeof x>;
const a: Params = ['should-not-work']; // works, but shouldn't
function t(...args: Params) {} // does not work, but should work

Yields

[ts] A rest parameter must be of an array type.
type Params = {}

Any ideas on what might be wrong? Or is re-using function argument types for new functions not yet supported?

EDIT: Nevermind, this does work in 3.0. I had a dependency in my test repo that was pulling in the wrong version.

is there any possibility to extract parameter names as well? e.g.

function foo(a: number, b: string) {
  return true;
}
type p = ParametersWithNames<typeof foo>;
// p = { a: number, b: string }

edit: probably I want too much and parameter names don't relate to type system..

@pleerock There is no way to extract the parameter names to an object type.

Parameter names will however be preserved if you extract form one function and spread to another function:

function foo(x: number, y: number) {

}

//function bar(x: number, y: number): void
function bar(...a: Parameters<typeof foo>) {

}

Playground Link

Thanks @dragomirtitian. That's interesting behavior. However I wanted to create an object type out of parameter names, e.g. { x: number, y: number }

What if you only have one parameter and want that parameter, not an array with that parameter?

function foo(a: number) {
  return true;
}
type p = Parameters<typeof foo>;
// p = number

@dianjuar

function foo(a: number) {
  return true;
}
type p = Parameters<typeof foo>[0];

Playground Link

In a similar vein to this, is it possible to first define the args expected and then use them in both functions? For example...

type CommonArgs = {
  a: number;
  b: number;
}

function foo(...a: CommonArgs) {

}

function bar(...a: CommonArgs) {

}

It doesn't really make sense in the above snippet alone but what I am interested in is reducing boilerplate when it comes to function overloading. If we want the return type to be narrowed based on an input

// foo can return two different types based upon an input, say a boolean

function foo(a: number, b?: boolean): string | number {
  if (b === false) {
    return a;  // return type is number
  }
  return '' + a;  // return type is string
}

const value = foo(10, false); // <= typeof value: string | number
Math.abs(value); // Argument of type 'string | number' is not assignable to parameter of type 'number'

// this can be solved with
function foo(a: number, b: true): string;
function foo(a: number, b: false): number;
function foo(a: number, b?: boolean) {
  if (b === false) {
    return a;
  }
  return '' + a;
}
const value = foo(10, false); // correctly infers return type as number

This works but is only a trivial number of args, so could be more boilerplate to copy and paste. Is it possible for something like...

type CommonArgs = {
  a: number;
  // c, d, e, etc...
}

function foo(...a: CommonArgs & { b: true }): string
function foo(...a: CommonArgs & { b: false }): number
function foo(a: number, b?: boolean) {
  if (b === false) {
    return a;
  }
  return '' + a;
}

I have attempted using conditional types but have not managed to do so far...

EDIT

Additionally it would be nice to add some type-safety to the function implementation, along the lines of:

type CommonArgs = {
  a: number;
  b?: boolean;
  // c, d, e, etc...
}
type CommonReturnType = string | number;
type CommonFunction = (...a: CommonArgs): CommonReturnType;

function foo(...a: CommonArgs & { b: true }): string
function foo(...a: CommonArgs & { b: false }): number
foo: CommonFunction = function(a: number, b?: boolean) {
  return {}; // compiler identifies this return type is not allowed
}

@bhishp
I think the parameters only exist in value space: They are not keys(string | number) of an object but variables.
Even you get a type like [arg0: string, arg1, number] by writing Parameters<some-function-type>,
the parameter names are lost in the type space.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bgrieder picture bgrieder  路  3Comments

blendsdk picture blendsdk  路  3Comments

Roam-Cooper picture Roam-Cooper  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

manekinekko picture manekinekko  路  3Comments