Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in 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