6

I currently have an interface with overloaded functions like so:

export interface IEvents {
  method(): boolean;
  on(name: 'eventName1', listener: (obj: SomeType) => void): void;
  on(name: 'eventName2', listener: (obj: SomeType) => void): void;
  on(name: 'eventName3', listener: (obj: SomeType) => void): void;
  on(name: 'eventName4', listener: (obj: SomeType) => void): void;
  on(name: 'eventName5', listener: (obj: SomeType) => void): void;
  on(name: 'eventName6', listener: () => void): void;
  on(name: 'eventName7', listener: (obj: SomeType) => void): void;
  on(name: 'eventName8', listener: (obj: SomeType) => void): void;
}

I am trying to get the union type of event names like so:

eventName1 | eventName2 | ...

I have tried the following, but when I infer the type it seems to only pick one of the name values and not a union of all of them.

export type TEventExtension<T extends IEvents> {
  [K in keyof T]: K extends 'on' ? TEventListenerName<T[K]> : never;
}[keyof T];
export type TEventListenerName<T> = T extends (name: infer N, listener: (obj?: infer E) => void) => void ? N : never;
const ext: TEventExtension<IEvents> = void 0 as any; // Type: 'eventName8'

I have also tried using an accumulator type to keep track of the unions, but Typescript doesnt allow recursive generics.

Any ideas on how I can accomplish this?

Edit: The interface with the overloaded definitions exists in an external module. I am trying to avoid c+ping from the external definitions to my definitions, and instead have it build the type automatically.

Pat
  • 649
  • 1
  • 8
  • 24
  • `on(name: "eventName1" | "eventName2", listener:() =>void)` – Jonas Wilms May 22 '18 at 16:56
  • Not sure you can turn the overloads of `on` into a union type (can't think of a way maybe someone else has an idea). A workaround might be the approach presented here https://stackoverflow.com/questions/50369299/can-i-reuse-the-parameter-definition-of-a-function-in-typescript/50375712#50375712 – Titian Cernicova-Dragomir May 22 '18 at 17:00
  • @JonasW. That's not what I am trying to accomplish. I can easily hard-code the event names in the interface, but I am trying to create a type that takes an external interface defined by a module and returns the unions of possible event names. – Pat May 22 '18 at 17:00

2 Answers2

1

This doesn't need to be a separate answer, but it's hard to get this in a comment: overloads specifically do not work the way you want with type inference in 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.

declare function foo(x: string): number;
declare function foo(x: number): string;
declare function foo(x: string | number): string | number;
type T30 = ReturnType<typeof foo>;  // string | number

If you can't turn the overloads into a single function with a union of parameters (using conditional types to get listener for "eventName6" correct), then I don't know of a way to do this programmatically.

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • If I understand correctly, the link you provided is from the `TypeScript 2.8` changelog. Is this still impossible to do in `TypeScript 4.4`? – andras Nov 06 '21 at 07:21
0

I don't think you can. Using type infer with this example:

interface SomeType {

}

export interface IEvents {
  method(): boolean;
  on(name: "eventName1", listener: (obj: SomeType) => void): 13;
  on(name: "eventName2", listener: (obj: SomeType) => void): void;
  on(name: "eventName3", listener: (obj: SomeType) => void): undefined;
  on(name: "eventName4", listener: (obj: SomeType) => void): 14;
  on(name: "eventName5", listener: (obj: SomeType) => void): 10;
  on(name: "eventName6", listener: () => void): "hola";
  on(name: "eventName8", listener: (obj: SomeType) => void): 200;
  on(name: "eventName7", listener: (obj: SomeType) => void): void;
}

const x: ReturnType<IEvents["on"]> = 200;

It blames about x is not void, but one of the possible ReturnType of on in IEvents is 200. Looks like it only tries with the latest one.

So I guess that if you try to do something similar with the first argument of the function, it will only grab the latest one.

You have here the definition of ReturnType:

https://github.com/Microsoft/TypeScript/blob/release-2.8/lib/lib.d.ts#L1386

lilezek
  • 6,976
  • 1
  • 27
  • 45