TypeScript - generic being incorrectly inferred as unknown

From TypeScript specifications:

Type parameters may be referenced in parameter types and return type annotations, but not in type parameter constraints, of the call signature in which they are introduced.

Given your function signature,

<T, R extends (...args: any) => AxiosPromise<T>>(
  func: R, ...args: Parameters<R>
): [T | undefined, boolean, AxiosError | undefined]

, my interpretation of above statement is, that T appears in the type parameter constraint signature extends (...args: any) => AxiosPromise<T> of parameter R and can therefore not be resolved properly. unknown is just the implicit default constraint type of generic type parameters.

So these contrived examples would work:

declare function fn0<T, U extends T>(fn: (t: T) => U): U
const fn0Res = fn0((arg: { a: string }) => ({ a: "foo", b: 42 }))  // {a: string; b: number;}

declare function fn1<T, F extends (args: string) => number>(fn: F, t: T): T
const fn1Res = fn1((a: string) => 33, 42) // 42

In the next two samples the compiler infers T to be unknown, because T is only referenced in the call signature constraint of U and not used in function parameter code locations for further compiler hints:

declare function fn2<T, U extends (args: T) => number>(fn: U): T
const fn2Res = fn2((arg: number) => 32) // T defaults to unknown

declare function fn3<T, U extends (...args: any) => T>(fn: U): T
const fn3Res = fn3((arg: number) => 42) // T defaults to unknown

Possible solutions (choose, what fits best)

1.) You can introduce type parameters T and R just for the function parameters and return type:

declare function useClientRequest2<T, R extends any[]>(
    func: (...args: R) => Promise<T>,
    ...args: R
): [T | undefined, boolean, AxiosError | undefined]

const [data] = useClientRequest2(fooGetter, 'url.com'); // data: Foo | undefined

2.) Here is an alternative with conditional types (a bit more verbose):

declare function useClientRequestAlt<R extends (...args: any) => Promise<any>>(
    func: R,
    ...args: Parameters<R>
): [ResolvedPromise<ReturnType<R>> | undefined, boolean, AxiosError | undefined]

type ResolvedPromise<T extends Promise<any>> = T extends Promise<infer R> ? R : never
const [data2] = useClientRequestAlt(fooGetter, 'url.com'); // const data2: Foo | undefined

Playground


I can't answer why it does not work. However, I would do something like this:

type UnpackedAxiosPromise<T> = T extends AxiosPromise<infer U> ? U : T; 

function useClientRequest<R extends (...args: any) => any>(
    func: R,
    ...args: Parameters<R>
  ): [UnpackedAxiosPromise<ReturnType<R>> | undefined, boolean, AxiosError | undefined] {
    //irrelevant
  }

Tags:

Typescript