The type given by ReturnType
seems to depend on the order the overload signatures are written
function applyChanges1(input: string): number
func
This is a known limitation. The TypeScript team's recommendation is to include a "most general" overload signature as your last overload signature, e.g.:
function applyChanges1(input: string): number
function applyChanges1(input: number): string
function applyChanges1(input: number | string): number | string
function applyChanges1(input: number | string): number | string {
return typeof input === "number" ? input.toString() : input.length
}
Titian Cernicova-Dragomir has a nicer alternate solution in his answer.
As Matt McCutchen points this is a limitation of ReturnType
and in general conditional types and multiple overload signatures.
We can however construct a type that will return all overloaded return types for up to an arbitrary number of overloads:
function applyChanges1(input: string): number
function applyChanges1(input: number): string
function applyChanges1(input: number | string): number | string {
return typeof input === "number" ? input.toString() : input.length
}
function applyChanges2(input: number): string
function applyChanges2(input: string): number
function applyChanges2(input: number | string): number | string {
return typeof input === "number" ? input.toString() : input.length
}
type OverloadedReturnType<T> =
T extends { (...args: any[]) : infer R; (...args: any[]) : infer R; (...args: any[]) : infer R ; (...args: any[]) : infer R } ? R :
T extends { (...args: any[]) : infer R; (...args: any[]) : infer R; (...args: any[]) : infer R } ? R :
T extends { (...args: any[]) : infer R; (...args: any[]) : infer R } ? R :
T extends (...args: any[]) => infer R ? R : any
type RetO1 = OverloadedReturnType<typeof applyChanges1> // string | number
type RetO2 = OverloadedReturnType<typeof applyChanges2> // number | string
The version above will work for up to 4 overload signatures (whatever they may be) but can easily (if not prettily) be extended to more.
We can even get a union of possible argument types in the same way:
type OverloadedArguments<T> =
T extends { (...args: infer A1) : any; (...args: infer A2) : any; (...args: infer A3) : any ; (...args: infer A4) : any } ? A1|A2|A3|A4 :
T extends { (...args: infer A1) : any; (...args: infer A2) : any; (...args: infer A3) : any } ? A1|A2|A3 :
T extends { (...args: infer A1) : any; (...args: infer A2) : any } ? A1|A2 :
T extends (...args: infer A) => any ? A : any
type RetO1 = OverloadedArguments<typeof applyChanges1> // [string] & [number]
type RetO2 = OverloadedArguments<typeof applyChanges2> // [number] & [string]
I had a similar problem -- I needed to pick the ReturnType
of the exact overload, based on the arguments I have.
e.g.:
function applyChanges1(input: string): number
function applyChanges1(input: number): string
function applyChanges1(input: boolean): object
function applyChanges1(input: number | string | boolean): number | string | object {
return typeof input === "number" ? input.toString()
: typeof input === "boolean" ? { input }
: input.length;
}
// Needed:
type Ret11 = ReturnTypeWithArgs<typeof applyChanges1, [string]> // number
type Ret12 = ReturnTypeWithArgs<typeof applyChanges1, [number]> // string
type Ret13 = ReturnTypeWithArgs<typeof applyChanges1, [boolean]> // object
type Ret14 = ReturnTypeWithArgs<typeof applyChanges1, [number | string]> // number | string
type Ret15 = ReturnTypeWithArgs<typeof applyChanges1, [number | boolean]> // string | object
type Ret16 = ReturnTypeWithArgs<typeof applyChanges1, [number | string | boolean]> // number | string | object
So I created the following ReturnTypeWithArgs
utility based on the beautiful answer of @Titian above and using Extract
.
type ReturnTypeWithArgs<T extends (...args: any[]) => any, ARGS_T> =
Extract<
T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; (...args: infer A4): infer R4; } ? [A1, R1] | [A2, R2] | [A3, R3] | [A4, R4] :
T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; } ? [A1, R1] | [A2, R2] | [A3, R3] :
T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; } ? [A1, R1] | [A2, R2] :
T extends { (...args: infer A1): infer R1; } ? [A1, R1] :
never,
[ARGS_T, any]
>[1]
It works like a charm!
Playground Link
PS. In my real case I have 7 overloads, so wish me luck! ;D