0

I have an interface like this coming from an 3rd party lib:

MyInterface{
    foo(arg: string): Promise<any>;
    foo(arg: string, ...params: any[]): Promise<any>;

    foo<T>(arg: string): Promise<T>;
    foo<T>(arg: string, ...params: any[]): Promise<T>;

    bar(arg: string, callback?: (err: Error, row: any) => void): Promise<number>;
    bar(arg: string, ...params: any[]): Promise<number>;
}

And I want to delegate the interface methods to an implementation of the same type, like this:

MyClass implements MyInterface {

  private impl:MyInterface = ...
  
  foo(..) //how to do it right ??


  // TS2393: Duplicate function implementation.
  bar(arg: string, callback?: (err: Error, row: any) => void): Promise<number>{
     return impl.bar(sql,callback);
  }

  // TS2393: Duplicate function implementation.
  bar(arg, ...params: any[]): Promise<number>{
      return impl.bar(arg,params);
  }
}

I have no idea how to implement the delegation correctly, so the right impl methods are called.

Neither TypeScript function overloading Nor Is there a way to do method overloading in TypeScript? is helping me to make the correct delegation.

Chriss
  • 5,157
  • 7
  • 41
  • 75
  • What do you mean neither is helping? What happens when you use their answers? – jonrsharpe Nov 25 '20 at 15:40
  • And please don't [repost](https://stackoverflow.com/q/65005769/3001761), delete one or the other. – jonrsharpe Nov 25 '20 at 15:40
  • I am not able to apply the answers given to my use case, maybe to the limited knowledge. I delete the other one, nobody can answer to it either... – Chriss Nov 25 '20 at 15:44
  • Have you *tried* anything, even? You seem to have posted exactly the same question you had two hours ago, did you not learn anything from either of them? – jonrsharpe Nov 25 '20 at 15:53

2 Answers2

0

Not the best solution, but still some workaround:

interface MyInterface {
   foo(arg: string): string
   foo(arg: number): number
}

class Foo implements MyInterface {
   foo(arg: string): string;
   foo(arg: number): number;
   // foo(arg: number): string; -> error, if you uncomment this line, please comment line above

   foo(arg: string | number) {
      if (typeof arg === 'string') {
         return arg
      }
      return 12 as number
   }
}

const result = new Foo().foo(2)

The bad news: you should write all overloads. AFAIK, it is impossible to map interface with overloads

The good news: TS will complain if you provide function overload which is incompatible with implemented interface.

Update

There is an alternative way.

You may not need extends Foo class by MyInterface.

interface MyInterface {
   foo(arg: string): string
   foo(arg: number): number
}

class Foo {
   foo(arg: string): string;
   foo(arg: number): number;
   foo(arg: string | number) {
      if (typeof arg === 'string') {
         return arg
      }
      return 12 as number
   }
}

// sorry, I can't find the link to the author of this type utility
type DeepEqual<X, Y> =
   (<T>() => T extends X ? 1 : 2) extends
   (<T>() => T extends Y ? 1 : 2) ? true : false;

/**
 * Here you can just check if types are deep equal or not
 * Try to change overloadings for `foo` method and you
 * will see that Result type will be falsy
 */
type L = Deep<Foo, MyInterface>
0

When method calls should be delegate to similar methods signatures, the delegated method can decide how to handle the given parameters.

The overloaded method signatures boil down to the most generic overload (somewhat different to static typed languages). Therefore the more specific methods declared in the interface are not required to be implemented, but there declaration can be included in the class that implements the interface as a compiler hint. In this case the rest aka vararg parameter (...arg:any[]) comes in handy since it renders a generic method signature.

 class MyClass implements MyInterface {
   constructor(private delegate:MyInterface){}

  // these 2 methods below can't be declared in this class since they have
  // the same signature as the ones with <T> type argument (type-erasure?)   
  // foo(arg: string, ...params: any[]): Promise<any>;
  // foo(arg: string): Promise<any>; 

  //compiler hint, the method below will be called at runtime
  foo<T>(arg: string): Promise<T>;
  foo<T>(arg: string, ...params: any[]): Promise<T>{
    //we leave the responsibility to handle the given arguments to the `delegate` implementation 
    return this.delegate.foo(arg, ...params); 
  }

  // this method declaration has no implicit implementation, at runtime 
  // the more generic method below will be called, this method acts
  // as a compiler "hint" and can be ommitted
  bar(arg: string, callback?: (err: Error, row: any) => void): Promise<number>; 
  bar(arg, ...params: any[]): Promise<number>{
      return this.delegate.bar(arg,params);
  }
}
Chriss
  • 5,157
  • 7
  • 41
  • 75