## Search Terms tuple narrow, prefer tuple ## Suggestion This is a meta-suggestion over the top of some of the cases in #16896 and #26113, basically at current it's quite repetitive to specify overloads for tuple types and we tend to wind up things like this: ```ts // etc up to how ever many overloads might be needed function zipLongest<T1, T2, T3, T4>( iterables: [Iterable<T1>, Iterable<T2>, Iterable<T3>, Iterable<T4>] ): Iterable<[T1, T2, T3, T4]> function zipLongest([]): [] function zipLongest<T>(iterables: Array<Iterable<T>>): Iterable<Array<T>> ``` We can already specify the type if we know only tuples are going to be input e.g.: ```ts type Unwrap<T> = T extends Iterable<infer R> ? R : never type ZipUnwrapped<T> = { [P in keyof T]: Unwrap<T[P]> } declare function zipLongest<Ts extends Array<Iterable<any>>>(iterables: Ts): Iterable<ZipUnwrapped<Ts>> const strs = ['foo', 'bar', 'baz', 'boz'] const nums = [1, 2, 3, 4] const zipPair: [string[], number[]] = [strs, nums] const z = zipLongest(zipPair) ``` In this case the type of `z` is correctly inferred to be `Iterable<[string, number]>`, but if we remove the type declaration on `zipPair` then we get that the inferred type of `z` is `Iterable<(number | string)[]>`. Basically what I propose is adding some way to specify that an array should be narrowed to the most precise tuple possible if we can. There's a couple approaches that might be used: ###### Approach 1 Use the `[...TupleParam]` spread syntax already mentioned in #26113 and when this syntax is seen narrow to the best tuple we could get. So the above declaration would become: ```ts // The only difference is iterables: Ts has become iterables: [...Ts] declare function zipLongest<Ts extends Array<Iterable<any>>>(iterables: Ts): Iterable<ZipUnwrapped<Ts>> ``` This has the downside that it might for any `Ts` in `[Some, Tuple, Params, ...Ts]` to be narrowed even when the wider type was what was intended. ##### Approach 2 Add some new syntax that specifies that if a certain value can be narrowed into a tuple then we should do so: ```ts // Arbitrary syntax 1 declare function zipLongest( iterables: [...tuple Ts] ): Iterable<ZipUnwrapped<Ts>> // Arbitrary syntax 2 declare function zipLongest<Ts extends [!...Iterable<any>]>( iterables: Ts ): Iterable<ZipUnwrapped<Ts>> ``` The syntax isn't really all that important. The main downside of this approach is that we need two overloads, one for the narrowed tuple and one for the non-narrowed tuple. Additionally extra syntax would need to be supported and maintained. ## Examples Other than the above example another example that would benefit is the builtin `lib.promise.d.ts` which specifies overloads in this repetitive overloading pattern. We could write something like `Promise.all` like so: ```ts type UnwrapMaybePromiseLike<T> = T extends PromiseLike<infer R> ? R : T type UnwrappedArray<T extends Array<any>> = { [P in keyof T]: UnwrapMaybePromiseLike<T[P]> } interface PromiseConstructor { all<Ts extends Array<any>>( promises: [...Ts] ): UnwrappedArray<Ts> } ``` And inference would just work. ```ts // a would inferred to be [number, number] const a = Promise.all([1, 2]) // b would be inferred to be [string, number] const b = Promise.all(['foo', Promise.resolve(12)]) ``` ## Checklist My suggestion meets these guidelines: * [?] This wouldn't be a breaking change in existing TypeScript / JavaScript code * [✔] This wouldn't change the runtime behavior of existing JavaScript code * [✔] This could be implemented without emitting different JS based on the types of the expressions * [✔] This isn't a runtime feature (e.g. new expression-level syntax) EDIT: Realized `Promise.race` was supposed to be `Promise.all` oops.