type FuncHasArgumentsCheck<T extends (...args: any[]) => any> =
T extends (...args: []) => any
? never // zero arguments
: T;
// Extracts the first argument type, or `never` if no arguments are found.
type FuncFirstArgumentType<T extends FuncHasArgumentsCheck<T>> =
T extends (first: infer TFirst, ...args: any[]) => any
? TFirst
: never;
TypeScript shows error in this code:
Type parameter 'T' has a circular constraint.(2313)
Why can't it resolve this seemingly simple type check constraint and where exactly the "circularity" is?
FuncHasArgumentsCheck
doesn't depend on FuncFirstArgumentType
so I can't see what it finds 'circular' here.
It because you have T
extending itself in FuncFirstArgumentType
. You want it to instead be:
type FuncFirstArgumentType<T extends (...args: unknown[]) => unknown> =
T extends (first: infer TFirst, ...args: unknown[]) => unknown
? TFirst
: never;
But if no arguments are defined, your type will be unknown
instead of never
. Therefore, you can amend the above code to include another extends
check on the inferred type.
type FuncFirstArgumentType<T extends (...args: unknown[]) => unknown> =
T extends (first: infer TFirst, ...args: unknown[]) => unknown
? TFirst extends unknown
? never
: TFirst
: never;
This will get you the type of your first argument in the given function and incases where there are no arguments, it will return never
. In addition, you no longer need the original FuncHasArgumentsCheck
.
However, I think you're trying to over complicate everything. TypeScript provides a bunch of utility types for purposes like this. For your FuncHasArgumentsCheck
type, you can instead use TypeScripts Parameters
type utility which returns an array of parameter types. i.e.,
function tempFunc(a: number, b: string) {}
type FuncArgs = Parameters<typeof tempFunc>;
// FuncArgs = [number, string]
You can then index FuncArgs
to get the parameter type you want. Of course, this does not work if tempFunc
has no parameters as FuncArgs
will be empty and will throw a type error if you try and index it. To solve this, you can use the following generic type:
type FirstParam<T extends unknown[]> = T[0] extends never ? never : T[0];
This takes in the list of param types (i.e, FuncArgs
) and returns the type of the first argument if it exists, Else, it returns undefined.
Hope this helps!