Repository: TypeScript
Issue:  Return types of intersection of functions are incomplete and depend on order of declaration - an algorithm to fix it.
Labels: Suggestion,Awaiting More Feedback

### 🔍 Search Terms

#57002
#41874
intersection of functions, return type, incomplete, declaration order dependent



### ✅ Viability Checklist

- [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 isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- [X] This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

### ⭐ Suggestion

The return type of an intersection of functions should be *at least* complete and independent of the order of declaration of the functions.


### 📃 Motivating Example

*Example 1: intersection of non-overload functions*
```ts
interface A0 {
    foo(): string;
}

interface B0 {
    foo(): number;
}

declare const ab0: A0 & B0;
declare const ba0: A0 & B0;

const rab = ab0.foo(); // actual string, expecting string | number
const rba = ba0.foo(); // actual number, expecting string | number
```

*Example 2: intersection of overload functions*
```ts
interface A1 {
    bar(x:1): 1;
    bar(x:2): 2;
}
interface B1 {
    bar(x:2): "2";
    bar(x:3): "3";
}

declare const ab1: A1 & B1;

const rab11 = ab1.bar(1); // actual 1, expecting 1
const rab12 = ab1.bar(2); // actual 2, expecting 2 | "2"
const rab13 = ab1.bar(3); // actual 3, expecting 3

declare const ba1: B1 & A1;

const rba11 = ba1.bar(1); // actual 1, expecting 1
const rba12 = ba1.bar(2); // actual "2:, expecting 2 | "2"
const rba13 = ba1.bar(3); // actual 3, expecting 3
```

*Example 3: intersection of functions with object return types*
```ts
interface A2 {
    bar(): {a: string};
}
interface B2 {
    bar(): {b: string};
}

declare const ab2: A2 & B2;
declare const ba2: B2 & A2;

const rab21 = ab2.bar(); // actual: {a: string}, expecting: {a: string} | {b: string}
const rba21 = ba2.bar(); // actual: {b: string}, expecting: {a: string} | {b: string}
```


#### Current algorithm:
- Treat `g = g[0] & g[1] & ...` as though it were an ordered overload function  `{ g[0] ; g[1] ; ....}`.
- Let `args` be the arguments.
- return `ReturnType<chooseOverload(g, args)>`

The current algorithm treats `g` exactly as though it were an ordered overload function  `{ g[0] ; g[1] ; ....}`. Therefore `args` can match at most one intersection member `g[i]`, resulting in an incomplete and declaration order dependent return type.



#### Proposed algorithm # 1:

- Let `g = g[0] & g[1] & ...` be the intersection of functions.
- Let `args` be the arguments.
- `returnType = never` // initialize
- for `g[i]` in `g`
    - if `g[i]` is an overload function
        - `returnType = returnType | ReturnType<chooseOverload(g[i], args)>`
    - else if `args` extends `Parameters<g[i]>` then
        -  `returnType = returnType | ReturnType<g[i]>`
- return `returnType`


This is expected to work for the above examples.


#### Proposed algorithm # 2:

Compared to the current algorithm, Proposed algorithm # 1 is a more expensive computation, but it is also complete and is not dependent on declaration order.  If that computation is too expensive, then a simpler algorithm could be used:

- The return type is union over `i` of  `ReturnType<g[i]>`.  If `g[i]` is an overload function, it is calculated as the return type of the catch-all (a.k.a. cover) case for that overload sequence.

Justification:
- Let `cover(g)` be the catch-all case (a.k.a. the cover) of `g`.  i.e. for each parameter index `paramIndex`, `Parameters<cover(g)>[paramIndex]` is the union over `i` of `Parameters<g[i]>[paramIdex]`, and `ReturnType<cover(g)>` is the union over `i` of `ReturnType<g[i]>`.   `cover(g)` is the smallest upper bound of `g` that can be represented with a single non-overload function.  

In the cases of examples 1 and 3, the answer wouldn't change, proposal # 2  is gives identical return type to that calculated with proposal # 1.


### 💻 Use Cases

Getting accurate return types from intersections of functions.

> What workarounds are you using in the meantime?

When A and B are instances of a generic function, e.g.
```ts
type G<T> = { bar: <T>() => T };
type A = G<{a:string}>;
type B = G<{b:string}>;
```
then a workaround is to use the type
```ts
type AB = G<{a:string, b:string}>;
```
even though the type `AB` is wider than the intersection of `A` and `B`.

That workaround is similar in nature to using Proposed Algorithm # 2.