kos
kos

Reputation: 5364

TypeScript: override a method return type that has overloads

I'm working on a opensource library that makes Proxies of Rx Observables.

(I'll use Box<A> instead of Observable<A> for simplicity)

It takes in a Box<A> and returns a type { [P in keyof A]: Box<A[P]> }.

With one nuance: all methods on this new type should also return a Box of that method result, while keeping it's params types. So:

Box<{ m(a:string):any }>
// becomes
{ m(a:string):Box<any> }

The issue I'm facing is that when I try to proxy A that has overloaded methods on it — I'm only redefining one single method definition, not the whole overload group.

Simplified issue:

type Proxy<O> = { [P in keyof O]: Wrap<O[P]> }

type Wrap<O> = O extends (...a: infer P) => infer R
  ? (...args: P) => Proxy<R>
  : Proxy<O>

class A {
  m(a: number): any;
  m(a: string): any;
  m(...a:any[]) {}
}

let p = {} as Proxy<A>;
// p.m resolves to a single override
// m(a: string): void;
p.m('1') // > returns Proxy<any>
p.m(1)   // > Error: 1 is not string

Try this snippet in TS playground

Is it even possible?

Please, help me with this typing. Thanks!

Notes:

Everything already works fine without typings!

You can check out the library here: rxjs-proxify (types defined in src/proxify.ts)

Upvotes: 1

Views: 1173

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249706

There is a problem with mapping overloaded functions. If you just use O extends (...a: infer P) => infer R you will actually always get the last overload. There is no way to get the arguments for an arbitrary number of overloads, but you can create a conditional type to get the overloaded arguments for up to a certain number of overloads using the technique outlined here

type Proxy<O> = { [P in keyof O]: OverloadedWrap<O[P]> }

type OverloadedWrap<T> =
    T extends
        { (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; (...args: infer A4): infer R4 } ? 
        { (...args: A1): Proxy<R1>; (...args: A2): Proxy<R2>; (...args: A3): Proxy<R3>; (...args: A4): Proxy<R4>; }:
    T extends
        { (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; } ? 
        { (...args: A1): Proxy<R1>; (...args: A2): Proxy<R2>; (...args: A3): Proxy<R3>; }:
    T extends
        { (...args: infer A1): infer R1; (...args: infer A2): infer R2; } ? 
        { (...args: A1): Proxy<R1>; (...args: A2): Proxy<R2>; }:
    T extends
        { (...args: infer A1): infer R1; } ? 
        { (...args: A1): Proxy<R1>; }:
    Proxy<T>

type Wrap<O> = O extends (...a: infer P) => infer R
    ? (...args: P) => Proxy<R>
    : Proxy<O>

class A {
    m(a: boolean): any;
    m(a: number): any;
    m(a: string): any;
    m(...a: any[]) { }
}

let p = {} as Proxy<A>;
p.m('1') // > returns Proxy<any>
p.m(1)   // > returns Proxy<any>
p.m

Playground Link

Upvotes: 2

Related Questions