L_K
L_K

Reputation: 2986

TypeScript overloads doesn't chose the expected one

Let's say I have some code below:


type Func1<T1> = (arg1: T1) => any;

type FuncArray<T1> = (arg1: T1, b: string) => any

interface Callable {
    <T>(fn: Func1<T>, arg1: T): string
    <T1>(fn: FuncArray<T1>, arg1: T1): number
}

const call: Callable = (...args: any): any => { }

interface Config {
    url?: string
    headers?: { [K: string]: string | number }
}

interface Requests {
    (config: Config): string
    (url: string, config?: Config): string
}

const request: Requests = (url: string | Config, config?: Config) => '123'

const h = call(request, {}) // error: Argument of type 'Requests' is not assignable to parameter of type 'FuncArray<string>'.
  // Types of parameters 'config' and 'arg1' are incompatible.
    // Type 'string' has no properties in common with type 'Config'.

The error indicate that call is using its second overload signature, which I thought it would use its first overload in this specific case.

My understanding is that request should match Requests first overload, and then call(request, {}) matches Callable first overload, but actually it does't. Where do I get it wrong?

So my question is Why call(request, {}) does not match <T>(fn: Func1<T>, arg1: T): string?

Upvotes: 0

Views: 52

Answers (1)

jcalz
jcalz

Reputation: 327744

The issue doesn't have to do with Callable's overloads. You can see this for yourself by commenting out the second overload:

interface Callable {
    <T>(fn: Func1<T>, arg1: T): string
    //<T1>(fn: FuncArray<T1>, arg1: T1): number
}

Then you see the error:

const h = call(request, {}) // error!
// -------------------> ~~
// Argument of type '{}' is not assignable to parameter of type 'string'.

So the compiler has looked at request and inferred that T is type string, and then {} does not match string and you get an error. So the issue is that the compiler does not accept call(request, {}) as a match for Callable's first overload.

If you uncomment Callable's second overload, the compiler sees that it doesn't match the second overload either, and the error changes to "no overload matches this call". So let's not worry about FuncArray.


So why does call(request, {}) not match the first Callable overload?

The problem is that Requests is an overloaded function interface, and generic type parameter inference cannot do overload resolution at the same time. It's a design limitation of TypeScript. When the compiler sees call(request, {}) it has to infer the type of T. Instead of trying to determine which of the two overloads of Requests it should try to match against, it just picks the last one. And (url: string, config?: Config)=> string matches Func1<string>. And everything goes wrong from there.


So what can you do? The easiest thing is to manually specify the generic parameter to relieve the compiler of the burden of inferring it:

const h = call<Config>(request, {}) // okay

Once you specify that T is Config, the compiler then can do overload resolution on request and verify that, yes, request is a valid Func1<Config>. Similarly you can widen the type of request via type assertion to just Func1<Config>, so that T is correctly inferred:

const i = call(request as Func1<Config>, {}); // okay

Either way should work.


Okay, hope that helps; good luck!

Playground link to code

Upvotes: 1

Related Questions