TypeScript Version: 3.4.0-dev.201xxxxx
Search Terms: mapped type overload
Code
interface Overloads {
foo(a: string): void;
foo(a: number, b: string): void;
}
/** Converts all properties of an object to Promises and all methods to return Promises */
type ProxiedObject<T> = {
[P in keyof T]: T[P] extends (...args: infer Arguments) => infer R
? (...args: Arguments) => Promise<R>
: Promise<T[P]>
};
declare let x: ProxiedObject<Overloads>;
x.foo("abc"); // Error: [ts] Expected 2 arguments, but got 1. [2554]
x.foo(123, "abc");
Expected behavior:
No error, overload should be maintained.
This makes it impossible to use this pattern with popular types that contain overloads, like Rx Observable pipe()/subscribe().
The ProxiedObject type is used in https://github.com/GoogleChromeLabs/comlink.
Actual behavior:
Overload gets lost, compile error when trying to call the first overload.
Playground Link: link
When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types (this would require us to support typeof for arbitrary expressions, as suggested in #6606, or something similar).
From #21496. I suspect this is probably a design limitation.
But that was before we had tuple types. This overload:
interface Overloads {
foo(a: string): void;
foo(a: number, b: string): void;
}
is equivalent to
interface NoOverloads {
foo(...args: [string] | [number, string]): void;
}
which works correctly. I would use that workaround, but I don't have control over libraries that use overloads. I would at least expect the compiler to treat the above interface equivalently to the below.
If the return type is different, it's also possible with conditional types:
interface Overloads {
foo(a: string): string;
foo(a: number, b: string): number;
}
interface NoOverloads {
foo<P>(...params: P): P extends [number, string] ? number : string;
}
So I don't see a reason why overloads would still be impossible for the compiler to handle
The tuple approach seems plausible; perhaps that could be a proposal included in the issue?
The conditional type approach elicits further problems because generic parameters do not get unified.
I would just like to point out that
interface NoOverloads {
foo(...args: [string] | [number, string]): void;
}
const Bar: NoOverloads = {
foo: function (a, b) {
return;
}
}
actually does not work currently.
Check it out on the playground - a and b both get the type number | string
I'm personally very interested in seeing typeable overloads. I'm struggling to get TypeScript to accept my current typings for one of my projects: here it is.
Not sure if it's the same issue but I came here to report this behavior:
type Post = { body: string }
type Member = { name: string }
type methods = {
on(event: 'new_post', payload: Post): void
on(event: 'member_add', payload: Member): void
}
type Params = Parameters<methods['on']>
// Expected Params type: ["new_post", Post] | ["member_add", Member]
// Current behavior: ["member_add", Member] (only gets the last one)
_Related use case: https://github.com/probot/probot/pull/858_
Any news on this? Just hit this bug myself.
Most helpful comment
Not sure if it's the same issue but I came here to report this behavior:
Playground
_Related use case: https://github.com/probot/probot/pull/858_