1

Can't explain it better than what's in the title, so here's an example:

declare class myClass {
  myMethod(num: number): number; // declaration 1
  myMethod(str: string, num: number): number; // declaration 2
}

let func1: myClass['myMethod'] = (num: number) => 0;

let func2: myClass['myMethod'] = (str: string, num: number) => 0;

The above doesn't compile, outputting the following errors:

error TS2322: Type '(num: number) => number' is not assignable to type '{ (num: number): number; (str: string, num: number): number; }'.
  Types of parameters 'num' and 'str' are incompatible.
    Type 'string' is not assignable to type 'number'.
error TS2322: Type '(str: string, num: number) => number' is not assignable to type '{ (num: number): number; (str: string, num: number): number; }'.

If I comment out either of:

  • declaration 1 and func1 or
  • declaration 2 and func2,

compilation is successful.

Multiple declarations of the same method is something that happens quite often in declaration files of some open source packages, e.g. aws-sdk.

1 Answer 1

1

Multiple function or method declarations describe overloads. Overloaded functions or methods have one or more call signatures which are part of the declaration of the type. You can represent this set of call signatures either as repeated function/method/callable signatures in an interface/class/scope, or as the intersection of all the call signatures. An overloaded function must be callable in each of the ways described by the call signatures.

When you implement an overloaded function or method, you use a single implementation signature which is not seen from the outside... it's an implementation detail of the function. But the implementation signature needs to be able to handle all the call signatures. If it can't, you have a problem.

I'll rewrite your code as a single overloaded function interface MyMethod:

interface MyMethod {
  (num: number): number; // declaration 1
  (str: string, num: number): number; // declaration 2
}

let func1: MyMethod = (num: number) => 0; // error
let func2: MyMethod = (str: string, num: number) => 0; // error

That's basically the same as your code, and it has the same problem. func1 is not a valid MyMethod. You can only call func1(123), but you can't call func1("abc",123). But a MyMethod has to support both calls. Similarly, func2 only supports the second call signature and not the first.

Now consider this:

let funcBoth: MyMethod = (strOrNum: string | number, num?: number) => 0; // okay

This compiles just fine. That's because the implementation signature of funcBoth can support being called like funcBoth(123) and like funcBoth("abc", 123):

funcBoth(123); // okay
funcBoth("abc", 123); // okay

Now, the implementation signature would also have supported being called like funcBoth(123, 456), but the annotation of funcBoth as MyMethod does not let you do that:

funcBoth(123, 456); // error! bad first argument  

So the implementation signature is hidden from callers, and so inside your function implementation you can safely rely on the fact that callers will not pass in unexpected parameters.


If you want the behavior of calling func1 or func2 depending on how funcBoth is called, you have to do the dispatching yourself by checking arguments:

let f1 = (num: number) => 0; 
let f2 = (str: string, num: number) => 0; 

let f12: MyMethod = (strOrNum: string | number, num?: number) =>
  typeof strOrNum === "number" ? f1(strOrNum) : f2(strOrNum, num!);

Okay, hope that helps; good luck!

Link to code

Sign up to request clarification or add additional context in comments.

1 Comment

Amazing answer. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.