-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Overload gets lost in mapped type with conditional type #29732
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Comments
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. 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: probot/probot#858 |
Any news on this? Just hit this bug myself. |
This limitation has been a thorn in my side for multiple projects. Can anything be done to assist? |
@ClickerMonkey For non generic overloaded functions you can use the approach described here to improve the situation |
I stumbled upon this issue while trying to generate generic overloads programmatically. Here's a workaround: Unions are not treated like overloads, but intersections are. So, we just need to find a way to create an intersection of interfaces containing the overloads. We can do that by wrapping the function signatures in interfaces. |
Thank you @graup for the workaround and the example I made your Playground simpler so it's easier to follow the idea: |
Hi @felixfbecker! // still work in progress, I'm trying to further simplify it.
export type SetState<T> = undefined extends T ? {
(value: T | ((draft: T) => T | void), meta?: { logger?: Logger }): T,
(value: undefined | typeof nothing | ((draft: T) => typeof nothing), meta?: { logger?: Logger }): undefined,
(value: (draft: T) => Promise<T | void>, meta?: { logger?: Logger }): Promise<T>,
(value: (draft: T) => Promise<typeof nothing>, meta?: { logger?: Logger }): Promise<undefined>
} : {
(value: T | ((draft: T) => T | void), meta?: { logger?: Logger }): T,
(value: (draft: T) => Promise<T | void>, meta?: { logger?: Logger }): Promise<T>,
} It is very difficult to convert that to use only inference. 🍻 |
Since we can't use tuple in rest param type, I don't think we can use conditional type to replace function overloads. e.g. type F = {
() => number,
(value: string) => string
}
// vs
type F = <P extends Array<string>>(...args: P): P extends [] ? number : (P extends [string] ? string : never)
// this won't work
<P extends [] | [string]>(...args: P): ...
let f: F
f('a', 'b') // function overload prevents this, but conditional type can't also, user can't easily see what params As compare to function overload where you can name the arguments properly. |
Arrived here from the same core issue, but with a different use case. // reduced script
type SplitParameter = Parameters<string['split']>[0];
function split(source: string, by: SplitParameter): string[] {
return source.split(by);
} This only found the ES2015Splitter variant of Checking the rest of the comments, I ended up doing a union, but it still failed the overloads: type ES5Delimiter = string | RegExp;
type ES2015Splitter = {
[Symbol.split](string: string, limit?: number): string[];
};
function split(source: string, by: ES5Delimiter|ES2015Splitter): string[] {
// @ts-expect-error this fails both overloads :(
return source.split(by);
}
// completely superfluous guards
function safeSplit(source: string, by: ES5Delimiter|ES2015Splitter): string[] {
if (by instanceof RegExp || typeof by === 'string') {
return source.split(by);
}
return source.split(by);
} |
I do have a solution, if anyone is interested in a better solution. Existing ones do things like assume there's only 4 overloads, turn it into a union, remove the props on the functions, or many other things that break the fidelity of the overloads. Here's a basic type that gets the overload signatures of a function: interface FunctionSignature {
parameters: readonly unknown[];
returnType: unknown;
}
// Returns a `FunctionSignature[]`.
type GetFunctionOverloads<
T extends AnyFunction,
Shape extends object = ToObject<T>,
// This would be the first part to change if you want to change the return type.
Signatures extends FunctionSignature[] = [],
> = Shape extends T
? Signatures
: T extends AddSignature<Shape, infer Params, infer Return>
? GetFunctionOverloads<
T,
AddSignature<Shape, Params, Return>,
// This would be the second.
[{ parameters: Params, returnType: Return }, ...Signatures]
>
: Signatures Of course this doesn't actually modify the signatures, just return them. My playground goes into some examples of actually mapping the overload signatures and it's pretty thoroughly documented! |
TypeScript Version: 3.4.0-dev.201xxxxx
Search Terms: mapped type overload
Code
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
The text was updated successfully, but these errors were encountered: