Typescript: Can't get the type of the nth argument of an overloaded method / polymorphic method

Created on 28 Sep 2020  ·  10Comments  ·  Source: microsoft/TypeScript


TypeScript Version: 4.1.0-dev.20200928


Search Terms: constructor parameters overload polymorphic

Code

export default class Zoom {

  constructor(opt_options?:{}){
    return;
  }

  on(type: 'change', listener: any): void;
  on(type: 'error', listener: any): void;
  on(type: 'propertychange', listener: any): void;

  on() {
    return true;
  }
}

type EventType = Parameters<InstanceType<typeof Zoom>["on"]>[0];


const w1:EventType = "change"; // Should typecheck OK, but does not
const w2:EventType = "error"; // Should typecheck OK, but does not
const w3:EventType = "propertychange"; // OK because it's the last

Expected behavior:

I use a library which defines a class like this:

// node_modules/@types/ol/control/Zoom.d.ts

export default class Zoom {

  constructor(opt_options?:{});

  on(type: 'change', listener: any): void;
  on(type: 'error', listener: any): void;
  on(type: 'propertychange', listener: any): void;

}

Now, I would like to get the type of the first argument of the on method.

I expect to get this kind of result:

type EventType = "change" | "error" | "propertychange"

Actual behavior:

But sadly, when I try:

type EventType = Parameters<InstanceType<typeof Zoom>["on"]>[0];

The result is like this:

type EventType = "propertychange"

Only the last declaration seems to be accessible.

Here is a screenshot of VSCode showing this:

VScode screenshot with only propertychange

On the other hand, typescript still knows the actual type, because VSCode shows me this:

enter image description here

So there must be a way to get this working.... Any idea?

Playground Link: https://www.typescriptlang.org/play?ts=4.1.0-dev.20200928#code/KYDwDg9gTgLgBAE2AMwIYFcA28DGnUDOBcAWhBALZwDeAULXHDhAHYExTo4zQAUEYGAH0BMAJasCAfgBc1AL4BKOo0ZRgMdFBYBuBnHn7G+1rxgBPMMBlwA5DgAWqFgHNgtgDRxMY9sBbAUDbO5oo2AG4QYgh6jKYWVja2gVDQnt6+MP6BwSyhEVExJixmltZ2YKlWsOaOzm7pPn4BQXAhYXCR0XrFvIo0RnDqmtpwHOjAsQa0hrQJwHAAouH+MAAqZXAAvHAACqhQqBQagQQAPACSbDDOOMAbVmfzEMik5BQAfADaAESsPwBdb4ABgBPVozGucAA7gBGGTLVYPBY7H51VzAH46OAAehxcAAyg4IFgEGMyo5gDgANZwADyAGkvAAjdDwBAQYDEFgQGAQyTwaEAJgRKxY602qJS0CxuPxRJJmDJ80pNPpTLgrPZnO5vP5UOhAGZRUjJXAfpUBIELOi3LK8erNVSMAQFmIYLZiDAHAt8OwgA

Related Issues:

Question on stackoverflow https://stackoverflow.com/questions/64107789/how-to-get-the-type-of-the-nth-argument-of-an-overloaded-method-polymorphic-me

Design Limitation

All 10 comments

https://www.typescriptlang.org/docs/handbook/advanced-types.html#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.

Conditional types and overloaded signatures do not play well together. There is no way to get this working.

This makes sense, but why does VSCode succeeds in finding the alternatives? I think this auto completion is based on the TS language server

This makes sense, but why does VSCode succeeds in finding the alternatives?

I'm not sure what you mean. Do you mean the auto completion in your screenshot? That operators on a completely different value, it's not using a conditional type.

There is no way to get this working.

Well, there's always this... but it's not pretty.

@crubier if you're trying to strongly type an EventEmitter interface (or something similar), the following pattern works pretty well for me:
https://github.com/AlCalzone/node-zwave-js/blob/86ee24d69b8172c4c2ab8d6dd0ca90dc1569424d/packages/zwave-js/src/lib/controller/Controller.ts#L102-L145

Thank you @AlCalzone but I dont want to modify the type of the event emitter-like interface, I just want to use it.

@jcalz your solution sounds like it could work... I might try aha

@MartinJohns

Do you mean the auto completion in your screenshot?

Yes

That operators on a completely different value, it's not using a conditional type.

Ok. There does seem to be a design limitation behind the scenes here indeed, because the way to get there might be different, but the type of these two strings (in my two screenshots) should be exactly the same in the end. I mean, as a user this is what I expect.

I'm running into the same issue. I can definitely see why it's a design limitation though. I have no control over the events as they're from a third party library, so I can't use a simple fix like the pattern suggested by AlCalzone. And the overload suggest by jcalz only supports so many different events.

My goal is "simple", to be able to map a listener's parameters to a function in my class based on the name of the event. At least, it sounds simple. In practice it is apparently complex.

I haven't quite mapped the overload to the functionality I desire (type completion on my "run" function) - not even sure if it's possible. But it seems like I should figure out some other pattern to make this work.

here is my playground

Another variant - I extrapolated from jcalz's design to make a "solution" to fit mine. Not sure if it's workable though. The main drawback is if the function type interface doesn't have enough members the EventParameter type falls back to "any". So you can't just make one mega type to cover it (which is why I believe you had the fallbacks in the overload).

Also, doesn't really fit the ideal of keeping it simple for library user's - the goal is to let them extend the "runner" and specify the type in the constructor. This latest design requires that they specify the type in two areas - the type parameter and the constructor options.

It's fun in theory, but it'd be nice if the logic to infer could recursively infer or have a spread syntax of some sort.

Playground

@jcalz

Well, there's always this... but it's not pretty.

I will pretend that I didn't see this. 🤢

Was this page helpful?
0 / 5 - 0 ratings