Search code examples
typescripttypescript-generics

TypeScript error: Type parameter 'T' has a circular constraint


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.


Solution

  • 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!