title: Method overloading with TypeScript does not infer argument types
body: <p>It's possible to overload class methods in TypeScript by declaring each method, and then adding a single implementation that fulfills the contracts of all the declared overloaded methods, typically by using union types. This is great because it enables overloading while still hiding the implementation details from the calling code - the implementation with the union types is kept hidden from the callers.</p>
  <p>However, it seems that from the implemented method's perspective, the TypeScript compiler does not always infer the types of arguments properly in an &quot;OK if it's not overload #1 and not overload #2 then it must be overload #3&quot; kind of way, like it usually does for union types. Consider the following:</p>
  <pre><code>class Foo {
    // API has three overloaded 'foo' methods
    public foo(age: number): void;
    public foo(name: string): void;
    public foo(foo: Foo, name: string): void;
  
    // Implementation with union types to accomodate for all three overloads
    // Here, 'name' must be optional (string | undefined) to fit overloads #1 and #2, 
    // but if 'x' is a 'Foo', 'name' must be a string
    public foo(x: number | string | Foo, name?: string): void {
      if (typeof x === 'number') {
        const age = x;
        console.log(`You called overload #1, I got age ${age}`);
      } else if (typeof x === 'string') {
        const name = x;
        console.log(`You called overload #2, I got name ${name}`);
      } else {
        // You would think that the compiler now understands that 'name' must be a string, but
        // it still considers it to be string | undefined.
        //console.log(name.substring(0)); // fails with error TS2532: Object is possibly 'undefined'
        // So we need another, seemingly pointless, check for the type of 'name':
        if (typeof name === 'string') {
          const foo = x;
          console.log(`You called overload #3, I got foo ${foo} and name ${name}`);
        } else {
          // This shouldn't happen, it fits none of the overloaded methods, so the caller must 
          // have simply abused the interface
          console.log(`Oh noes I got ${x} and ${name}`);
        }
      }
    }
  
    toString() {
      return 'Foo';
    }
  }
  
  new Foo().foo(42);
  new Foo().foo('John Doe');
  new Foo().foo(new Foo(), 'John Doe');
  new Foo().foo(new Foo(), undefined as any); // Uh-oh!
  </code></pre>
  <p>How come, in the <code>else</code> branch at line 19, the compiler doesn't realize that <code>name</code> must be of type <code>string</code> rather than <code>string | undefined</code>?
  I mean, of course you can always abuse an interface by using <code>undefined as any</code> etc, to provoke runtime errors by pretending to pass a string while actually passing <code>undefined</code>. But that's true with any TS code. To me it seems like the compiler should be able to figure out that if we're not in overload #1 and not in overload #2, then we must be in overload #3, where <code>name</code> is in fact of type <code>string</code> .</p>
  <p>Interestingly enough, if the <code>name.substring(0)</code> code is uncommented, calling <code>tsc foo.ts</code> with <code>tsc</code> version 4.1.4 succeeds (but in runtime, <code>node foo.js</code> will of course throw an error on the last line where <code>undefined as any</code> is passed), while with <code>ts-node</code> 9.1.1, you get a <em>compile error</em>:</p>
  <pre><code>$ npx ts-node foo.ts
  ⨯ Unable to compile TypeScript:
  foo.ts:22:19 - error TS2532: Object is possibly 'undefined'.
  
  22       console.log(name.substring(0)); // fails with error TS2532: Object is possibly 'undefined'
                       ~~~~
  </code></pre>
  <p>Is this simply a shortcoming in the compiler's handling of overloads or is there something I should be doing differently? Also, what is the reason for the difference in behaviour between <code>tsc</code> and <code>ts-node</code>? I would guess that <code>tsc-node</code> simply transpiles the code just-in-time and then runs the transpiled code, but it actually produces a compile error, not a runtime error, while <code>tsc</code> does not.</p>
  <p>PS. If there's anything I'd wish for TS 5 it would be to enable writing separate overload implementations and not having to care about an obscure union type implementation, but let the compiler add the logic to determine which overload is being called automatically! :)</p>