Repository: TypeScript
Issue: Make calling "pseudo-overloads" behave more like calling true overloads 
Labels: Suggestion,In Discussion

# Suggestion

## 🔍 Search Terms

rest tuple, union, overloads, contextual typing

## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

Bring the behavior when calling "pseudo-overloaded" functions (functions whose rest parameter is of a union of tuple types) in line with the behavior when calling "true overloaded" functions.




## 📃 Motivating Example

When you call a true overloaded function, the compiler will, for example, contextually type a callback parameter based on the resolved call signature:

```ts
function overloads(v: string, f: (v: string) => void): void;
function overloads(v: number, f: (v: number) => void): void;
function overloads() { }

overloads(123, (v: number) => v.toFixed());
overloads("abc", (v: string) => v.toUpperCase());
overloads(123, v => v.toFixed()); // okay
```

Whereas the "pseudo-overloaded" version of this (which IntelliSense presents as overloads as of #38234), the compiler is not able to do such contextual typing:

```ts
function restTuple(...args:
    [v: string, f: (v: string) => void] |
    [v: number, f: (v: number) => void]
) { }
// 1/2 restTuple(v: string, f: (v: string) => void): void
// 2/2 restTuple(v: numbger, f: (v: number) => void): void

restTuple(123, (v: number) => v.toFixed());
restTuple("abc", (v: string) => v.toUpperCase());
restTuple(123, v => v.toFixed()); // error!
// ----------> ~
// Parameter 'v' implicitly has an 'any' type.
```

It would be nice (although I understand that it might not be easily done) if callers could treat pseudo-overloads as true overloads, or at least more like true overloads. 

## 💻 Use Cases

None of these are show-stoppers, btw

### allow refactoring of overloads/generics to pseudo-overloads without affecting callers

In situations where the return type of a function does not depend on the parameter types, you can get some better behavior inside a function implementation with a pseudo-overload instead of either a generic that extends a union (see #13995) or a true overload (#18533):

```ts
function gen<K extends "s" | "n">(type: K, val: { s: string, n: number }[K]): string {
    return (type === "s") ? val.toUpperCase() : val.toFixed(); // error! can't narrow K this way
}

function ovl(type: "s", val: string): string;
function ovl(type: "n", val: number): string;
function ovl(type: "s" | "n", val: string | number) {
    return (type === "s") ? val.toUpperCase() : val.toFixed(); // error! type and val are uncorrelated    
}

function pseudo(...args: [type: "s", val: string] | [type: "n", val: number]): string {
    const [type, val] = args;    
    (type === "s") ? val.toUpperCase() : val.toFixed(); // error, type and val are still uncorrelated, #30581
    
    return (args[0] === "s") ? args[1].toUpperCase() : args[1].toFixed(); // but this works!
}
```

I'm not thrilled about not being able to destructure `args` before the check of `args[0]`, but at least it's possible to get some discriminated union type checking in the implementation.  From the caller's side, though, it would be nice if I didn't have to worry about the difference between `ovl()`'s and `pseudo()`'s call signatures.  And since IntelliSense presents them as overloads in some situations, it can lead to confusion when two "same" functions behave differently at call sites.

### programmatic generation of overloaded function call signatures:

If pseudo-overloads behaved more like overloads from the caller side, I'd have no reservations suggesting something like this:

```ts
type Params = [type: "s", val: string] | [type: "n", val: number] | [type: "b", val: boolean]
declare const p: (...args: Params) => string;

p("s", "");
p("n", 1);
p("b", true);
```

Otherwise, I need to do something union-to-intersection-like the following:

```ts
declare const o:
    (Params extends infer P ? P extends Params ?
        (x: (...args: P) => string) => void : never : never
    ) extends ((x: infer F) => void) ? F : never;

o("s", "");
o("n", 1);
o("b", true);
```
which is fun but ugly.

----

[Playground link](https://www.typescriptlang.org/play?ts=4.1.5#code/GYVwdgxgLglg9mABAJwKYGcoBUQAcA2qAFAHRkCGyA5ugFwCwAUIi4gNoButimyMYVADSJg3Ilx5Q+AgJSIAvAD5EHODAAmAXUQAfJq3YSwIALYAjVMmGjE47sfOW5SlWq1M5Ab0QBfJgHp-RABGfwAmFAxsPEI7SWkhETEJXn4qZ2VVDRluLPUAoLDwyMwcAmIjUzMqS2tk+yqnBUy3HNcNJiY0UpjiYLCAZmE4hwtkDJUSKDgAMRgAD1R1IhkZAG4uqLLYgCJyMwgd4ZSpNImOKbgAVVxcSwBhcnRiVY3GTZ7yon6hlWbJ6ZzRbLV6IQKISzIODIACEBUQAFokciUcoAH7wgAKlHIJlQUEsiAA5BwiYgYCYCDAIDAoPgAJ6IAAWT0Q5CQRPZ9LJUHpdxInUYoEgsAQiDgHEs+Dg5HU6DiqQEdVsJwS51auTcb2F0HgSAlUplcpGjSsSRVDUc43+eTaeW14F1YoNyGlsvlXl8gqYLrdxp+x0tY3OlyBSxW6x9ktdRvlewORwt8TONsuNzuyEezwjb19se+g2EHFTgIW4dB4LgAGtyPTve8GzrRUgamAADwAaQh8wJYDliB26B2ugHYB2iiIvLu3A7RfI+G43joyaViDAQcJPjYHc0bUVVEQnn0rDQUBAyCQk75qAU8nkA6HcgA-Cp52nbg8ni9ELk36XgSsaxgkEkLQjCiAQOyRJQGulBQgA7ogXZQEyMDoIg8G1kwfiNo6zbihw+BXtOD6Jhw87cPue6nAIDoinqBFEVOqDcDsY5zgua6mtRCR0U6+qEcRLGkSObFkRRK4HjoXFWl4x4sKe56Xsxt73oOOzPq++DvhmWbfr+2n-uWQHgqBsKICp7LqFpbJoIgjrQmg+DkAS1msNhgpNgxuDPCA6hwKQFDUMubDMaxQ4cZRNFUNo0mhderHsVpG7ILuUUJIe8kQQgmDsMxHHaPelA0G8BhCappGaeRhnXB+mZfisP5aaGZYgiZIHIFCZqWX2NmUDemAwPg+D2ZAjmoM5rnCAAxAMAAMACsAAcwSCgYikXrYxXoGwc2FXelWIC+21sMEmg6Z+2ZyNwJ1nS1AHrMBiBmCAMEoWhGHQlW6BwowOFMCp2LILi6H3vFJHqZFkmxXlCWjuJnGjJYMPg8JOxmAj3BmHAcCEOymhMOoqAQM5dkQDlMG4GIZAkNt3BAyDEz7m8TC4EQkMDhpbxs2JwjBJGjA8xjwhSCAqAC4KRMk-12VgLlcAMMwrBEAzJjoagPaoH26H8MAhKYkdiAGxrvb9qr6FPllZXzNTQU0PTTPReqGhNWAqDRq77uWFlcgm1r-ZEEQNvkmAevIIgMzO+omkzJ70Ys4wAUczsXM+uzSX87m7PCxZyBixLjBAA)

Related issues: 
#31977: better intellisense for discriminated union tuples
#38234: treat functions with unions of rest tuples as a rest argument as "pseudo-overloads"