To eliminate the final parameter from a function type, you can utilize conditional type inference:
type ExcludeLastParam<T extends (...args: any) => any> =
T extends (...args: [...infer I, any]) => infer R ? (...args: I) => R : never;
This code snippet compares T
with (...args: [...I, any]) => R
, where the compiler deduces I
and R</code using the <code>infer
keyword. It is worth noting that [...I, any]
represents a variadic tuple type. By matching [...I, any]
against a tuple type (e.g., [a: string, b: Callback]
), the compiler infers I
to be the "initial" portion of the tuple without the last element (as the any
type matches anything) (like, for instance, [a: string]
).
Let's put it to the test:
type Callback = (msg: string) => void;
type A = (a: string, b: Callback) => void;
type B = ExcludeLastParam<A>;
// type B = (a: string) => void
Also,
type C = (a: string, b: number, c: boolean, d: Date) => string;
type D = ExcludeLastParam<C>
// type D = (a: string, b: number, c: boolean) => string
It works as expected!
Your approach doesn't succeed because even though the Parameters<T>
utility type can extract the parameter list as a tuple type, the Exclude<T, U>
utility type does not filter tuple types; instead, it filters union types. Since Parameters<A>
corresponds to the type [a: string, b: Callback]
, excluding Callback
will not have an effect (since Parameters<A>
is not a union... or maybe it's a single member union, and that member is not compatible with Callback
).
Playground link to code