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

OverloadParameters and OverloadReturnType #585

Open
roydukkey opened this issue Mar 30, 2023 · 6 comments
Open

OverloadParameters and OverloadReturnType #585

roydukkey opened this issue Mar 30, 2023 · 6 comments
Labels
help wanted Extra attention is needed type addition

Comments

@roydukkey
Copy link

roydukkey commented Mar 30, 2023

Basically native Parameter and ReturnType do not handle overloaded interfaces.

Here is a workaround: microsoft/TypeScript#14107 (comment)

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • The funding will be given to active contributors.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar
@sindresorhus
Copy link
Owner

Looks useful. I have had this need before too.

@sindresorhus sindresorhus added help wanted Extra attention is needed type addition labels Apr 2, 2023
@xusiyuan841028
Copy link

// support 5 times overload at most
type OverloadedReturnType<T> = 
    T extends { (...args: any[]) : infer R; (...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 ; (...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;

function fn5(a: boolean): boolean;
function fn5(a: string): string;
function fn5(a: number): number;
function fn5(a: null): null;
function fn5(a: { a: string }): { a: string };
function fn5(a: string | number | boolean | null | { a: string } | { a: number }) : {a: string } | string | number | boolean | null | { a: number } {
  return a;
}

// OK
type RFn5 = OverloadedReturnType<typeof fn5>;

function fn6(a: boolean): boolean;
function fn6(a: string): string;
function fn6(a: number): number;
function fn6(a: null): null;
function fn6(a: { a: string }): { a: string };
function fn6(a: { a: number }): { a: number };
function fn6(a: string | number | boolean | null | { a: string } | { a: number }) : {a: string } | string | number | boolean | null | { a: number } {
  return a;
}

// boolean type is lost
type RFn6 = OverloadedReturnType<typeof fn6>;
```ts

@xusiyuan841028
Copy link

xusiyuan841028 commented Apr 13, 2023

But can't implement OverloadedParameters ...

pls refer to
microsoft/TypeScript#14107
microsoft/TypeScript#32164
https://github.com/microsoft/TypeScript/blob/main/src/deprecatedCompat/deprecations.ts#L65
https://github.com/microsoft/TypeScript/blob/main/src/compiler/types.ts#L9851

because inferring parameters from overload function type which is a union of function types, creates intersections instead of a unions ....

type OverloadedParametersA<T> = 
  T extends { (...args: Array<infer P>): any; (...args: Array<infer P>): any; (...args: Array<infer P>): any; (...args: Array<infer P>): any; (...args: Array<infer P>): any; } ? P :
  T extends { (...args: Array<infer P>): any; (...args: Array<infer P>): any; (...args: Array<infer P>): any; (...args: Array<infer P>): any; } ? P :
  T extends { (...args: Array<infer P>): any; (...args: Array<infer P>): any; (...args: Array<infer P>): any; } ? P :
  T extends { (...args: Array<infer P>): any; (...args: Array<infer P>): any; } ? P :
  T extends { (...args: Array<infer P>): any; } ? P : never;

function fn5(a: boolean): boolean;
function fn5(a: string): string;
function fn5(a: number): number;
function fn5(a: null): null;
function fn5(a: { a: string }): { a: string };
function fn5(a: string | number | boolean | null | { a: string } | { a: number }) : {a: string } | string | number | boolean | null | { a: number } {
  return a;
}

type PA = OverloadedParametersA<typeof fn5>; // actually, it is [boolean] & [string] & [number] & [null] & [{ a: string}] = never

type OverloadedParametersB<T> = 
  T extends { (...args: infer P): any; (...args:  infer P): any; (...args: infer P ): any; (...args: infer P): any; (...args:  infer P): any; } ? P :
  T extends { (...args: infer P): any; (...args:  infer P): any; (...args: infer P): any; (...args: infer P): any; } ? P :
  T extends { (...args: infer P): any; (...args:  infer P): any; (...args: infer P): any; } ? P :
  T extends { (...args: infer P): any; (...args:  infer P): any; } ? P :
  T extends { (...args: infer P): any; } ? P : never;

type PB = OverloadedParametersB<typeof fn5>; // actually, it is [boolean] & [string] & [number] & [null] & [{ a: string}] = never

type OneOf<P> = P extends Array<infer P> ? P : never;
type OverloadedParametersC<T> = 
  T extends { (...args: infer P): any; (...args:  infer P): any; (...args: infer P ): any; (...args: infer P): any; (...args:  infer P): any; } ? OneOf<P> :
  T extends { (...args: infer P): any; (...args:  infer P): any; (...args: infer P): any; (...args: infer P): any; } ? OneOf<P> :
  T extends { (...args: infer P): any; (...args:  infer P): any; (...args: infer P): any; } ? OneOf<P> :
  T extends { (...args: infer P): any; (...args:  infer P): any; } ? OneOf<P> :
  T extends { (...args: infer P): any; } ? OneOf<P> : never;

type PC = OverloadedParametersB<typeof fn5>; // actually, it is [boolean] & [string] & [number] & [null] & [{ a: string}] = never

@Dimava
Copy link

Dimava commented Apr 29, 2023

It's easier to split the function into overloads first and then extract returns/parameters
https://tsplay.dev/wR025m

function fn5(a: boolean): boolean;
function fn5(a: string): string;
function fn5(a: number): number;
function fn5(a: null): null;
function fn5<T extends 'a' | 'b'>(a: T): T;
function fn5(a: any) {
    return a;
}
type OverloadsToTuple3<T> =
    | T extends {
        (...args: infer P1): infer R1;
        (...args: infer P2): infer R2;
        (...args: infer P3): infer R3;
    } ? [
        (...args: P1) => R1,
        (...args: P2) => R2,
        (...args: P3) => R3,
    ] : OverloadsToTuple2<T>;

type OverloadsToTuple<T> =
    | OverloadsToTuple5<T>

type PD = OverloadsToTuple<typeof fn5>
//   ^?
// type PD = [(a: boolean) => boolean, (a: string) => string,
//   (a: number) => number, (a: null) => null, (a: "a" | "b") => "a" | "b"]

type Params = Parameters<OverloadsToTuple<typeof fn5>[number]>
//   ^?
// type Params = [a: boolean] | [a: string] | [a: number] | [a: null] | [a: "a" | "b"]

type Returns = ReturnType<OverloadsToTuple<typeof fn5>[number]>
//   ^?
// type Returns = string | number | boolean | null

type Match = Extract<OverloadsToTuple<typeof fn5>[number], (s: 'a') => any>
//   ^?
// type Match = ((a: string) => string) | ((a: "a" | "b") => "a" | "b")

type MatchReturn = ReturnType<Extract<OverloadsToTuple<typeof fn5>[number], (s: 'a') => any>>
//   ^?
// type MatchReturn = string

@steveluscher
Copy link

OMG @Dimava, you literally saved my entire project with this comment. solana-labs/solana-web3.js#1760

@mcriley821
Copy link

mcriley821 commented Mar 5, 2024

This kind of gives an erroneous tuple back when there's less than 5 overloads:

function fn3(a: boolean): boolean;
function fn3(a: string): string;
function fn3(a: number): number;
function fn3(a: any) {
    return a;
}

type Overloads = OverloadsToTuple<typeof fn3>
//   ^?
// [(a: boolean) => boolean, (a: boolean) => boolean, (a: boolean) => boolean, (a: string) => string, (a: number) => number]

This can be improved just by de-duping the tuple:

/**
 * Dedupe tuple elements.
 * For example,
 * ```
 *   type Set = Dedupe<[number, string, boolean, string, number]>;
 *   // Set == [number, string, boolean]
 * ```
 */
export type Dedupe<T extends unknown[], A extends unknown[] = []> = T extends [infer P, ...infer R]
  ? P extends A[number]
    ? Dedupe<R, A>
    : Dedupe<R, [...A, [P] extends [boolean] ? boolean : P]>
  : A;

type OverloadsToTuple<T> =
    | Dedupe<OverloadsToTuple5<T>>

type Overloads = OverloadsToTuple<typeof fn3>
//   ^?
// [(a: boolean) => boolean, (a: string) => string, (a: number) => number]

Edit:
After thinking about this behavior, I realized that the duplication is from the largest interface extends being true. It seems to happen no matter the overload count, being either filled or truncated. By filled, I mean the compiler seems to fill the interface with the first signature and write the overloads into the last n entries.

So you can essentially remove OverloadsToTuple{N}, replace it with an interface with an obscene amount of signatures, and let Dedupe reduce the tuple:

type Dedupe<T extends unknown[], A extends unknown[] = []> = T extends [infer P, ...infer R]
  ? P extends A[number]
    ? Dedupe<R, A>
    : Dedupe<R, [...A, [P] extends [boolean] ? boolean : P]>
  : A;

type Overloads<T> = Dedupe<
  T extends {
    (...args: infer P1): infer R1;
    (...args: infer P2): infer R2;
    (...args: infer P3): infer R3;
    (...args: infer P4): infer R4;
    (...args: infer P5): infer R5;
    (...args: infer P6): infer R6;
    (...args: infer P7): infer R7;
    (...args: infer P8): infer R8;
    (...args: infer P9): infer R9;
    (...args: infer P10): infer R10;
    (...args: infer P11): infer R11;
    (...args: infer P12): infer R12;
    (...args: infer P13): infer R13;
    (...args: infer P14): infer R14;
    (...args: infer P15): infer R15;
    (...args: infer P16): infer R16;
    (...args: infer P17): infer R17;
    (...args: infer P18): infer R18;
    (...args: infer P19): infer R19;
    (...args: infer P20): infer R20;
    // ... 
  } ? [
    (...args: P1): R1,
    (...args: P2): R2,
    (...args: P3): R3,
    (...args: P4): R4,
    (...args: P5): R5,
    (...args: P6): R6,
    (...args: P7): R7,
    (...args: P8): R8,
    (...args: P9): R9,
    (...args: P10): R10,
    (...args: P11): R11,
    (...args: P12): R12,
    (...args: P13): R13,
    (...args: P14): R14,
    (...args: P15): R15,
    (...args: P16): R16,
    (...args: P17): R17,
    (...args: P18): R18,
    (...args: P19): R19,
    (...args: P20): R20,
    // ...
  ] : never
>;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed type addition
Projects
None yet
Development

No branches or pull requests

6 participants