Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: Get the type of the last parameter of a function type

Tags:

typescript

Assume I have a function type, e.g.

type somefntype = (a: number, b: string, c: boolean) => void

I need the type of that function types last parameter:

type lastparamtype = LastParameter<somefntype> // lastparamtype == boolean

How do I define LastParameter?

like image 478
ksm0816 Avatar asked May 29 '19 21:05

ksm0816


2 Answers

Update: TypeScript 4.0 introduced variadic tuple types, which means that Last can now be implemented simply as

type Last<T extends any[]> = T extends [...infer I, infer L] ? L : never; 

And the rest works as normal:

type LastParameter<F extends (...args: any)=>any> = Last<Parameters<F>>;
type somefntype = (a: number, b: string, c: boolean) => void
type lastparamtype = LastParameter<somefntype> // boolean

Playground link to code


TypeScript 3.0 introduced tuple types in rest and spread expressions, specifically to allow programmatic conversion between the types of function parameter lists and tuples. There's a type alias called Parameters<F> defined in the standard library that returns the parameter list of a function as a tuple.

It's annoying but possible to get the last element of a tuple type with a fixed but arbitrary length:

// Tail<T> returns a tuple with the first element removed
// so Tail<[1, 2, 3]> is [2, 3]
// (works by using rest tuples)
type Tail<T extends any[]> = 
  ((...t: T)=>void) extends ((h: any, ...r: infer R)=>void) ? R : never;

// Last<T> returns the last element of the tuple
// (works by finding the one property key in T which is not in Tail<T>)
type Last<T extends any[]> = T[Exclude<keyof T, keyof Tail<T>>];

Putting those together you get

type LastParameter<F extends (...args: any)=>any> = Last<Parameters<F>>;

And we can test it:

type somefntype = (a: number, b: string, c: boolean) => void
type lastparamtype = LastParameter<somefntype> // boolean

Looks good to me. Link to code

like image 57
jcalz Avatar answered Dec 27 '22 05:12

jcalz


Here is yet a another solution which is a slight modification of jcalz answer to make it more generic using some recursive conditional types:

type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
type Tail<T extends any[]> =
    ((...t: T) => any) extends ((_: any, ...tail: infer U) => any)
        ? U
        : [];
type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;

type Last<T extends any[]> = {
    0: Last<Tail<T>>
    1: Head<T>
}[
    HasTail<T> extends true ? 0 : 1
];

type LastType = Last<Parameters<somefntype>>; // boolean

It might be interesting to consider replacing any with unknown as well.

like image 41
Christian Ivicevic Avatar answered Dec 27 '22 06:12

Christian Ivicevic