Skip to content

Mapping over overloaded functions of an interface results in overloads being dropped #53439

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

Closed
steveluscher opened this issue Mar 22, 2023 · 2 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@steveluscher
Copy link

steveluscher commented Mar 22, 2023

Bug Report

🔎 Search Terms

mapped types, mapping, map, interfaces, overloads, function, ReturnType, keyof

🕗 Version & Regression Information

This is the behavior in every version I tried between 4.9.5 and 5.1.0-dev.20230322, and I reviewed the FAQ for entries about ‘overload.’

⏯ Playground Link

Playground link with relevant code

💻 Code

Imagine an interface with two function overloads.

interface Api {
  parseNumber(input: string, useBigInt: true): bigint
  parseNumber(input: string, useBigInt: false): number
}

🙁 Actual behavior

You can easily pull out a union of function names, a union of the function signatures, but when you ask for a union of the return types you only get the last one in declaration order.

type TMethodNames = keyof Api; // GOOD: `parseNumber`
type TMethodImplementations = Api[keyof Api]; // GOOD: both signatures
type TMethodReturns = ReturnType<TMethodImplementations> // BAD: only `number` rather than `number | bigint`

Imagine that you wanted to map over these functions to create a new interface with async versions of each function.

type AsyncApi = {
  [TMethodName in keyof Api]: (...args: Parameters<Api[TMethodName]>) => Promise<ReturnType<Api[TMethodName]>>
};

const asyncApi = null as unknown as AsyncApi;
asyncApi.parseNumber('123', false); // GOOD: return type is `Promise<number>`
asyncApi.parseNumber('123', true); // BAD: signature is completely missing

🙂 Expected behavior

I would like Typescript to map over each overload.

type AsyncApi = {
  [TMethodName in keyof Api]: (...args: Parameters<Api[TMethodName]>) => Promise<ReturnType<Api[TMethodName]>>
};

// I would expect the following type to be produced:
// {
//   parseNumber(input: string, useBigInt: true): Promise<bigint>
//   parseNumber(input: string, useBigInt: false): Promise<number>
// }
@MartinJohns
Copy link
Contributor

type TMethodReturns = ReturnType // BAD: only number rather than number | bigint

This is a design limitation. ReturnType<T> is a using inference within conditional type, and inference within conditional types do not play well together with overloads.

This is documented: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types

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.

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Mar 22, 2023
@jcalz
Copy link
Contributor

jcalz commented Mar 23, 2023

Duplicate of #29732

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants