I have taken a zip
generator function, and I'm trying to infer the result type. Given zip(A, B)
with A Iterable<string>
and B Iterable<number>
I'd want to infer the return type Generator<[string, number][]>
Here's what I got so far:
declare function zip<T extends Iterable<any>[]>(...iterables: T): Generator<T>:
The problem is that it infers a type union Generator<[string[], number[]]>
instead.
The closest I think I got is something like this:
declare function zip<
T extends Iterable<any>[],
K = T extends Iterable<infer K>[] ? K : never,
>(...iterables: T): Generator<[...K[]]>
but that still just yields a Generator<(string | number)[]>
, trying to infer it on the spot I just get Generator<any[]>
:
declare function zip<T extends Iterable<any>[]>(
...iterables: T
): Generator<[...(T extends Iterable<infer K>[] ? K : never)]>
My desired output is:
declare const A: string[];
declare const B: Iterable<number>;
const z = zip(A, B);
// ^? const z: Generator<[string, number][]>
I would be inclined to use the following call signature:
declare function zip<T extends any[]>(
...iterables: { [I in keyof T]: Iterable<T[I]> }
): Generator<T[]>;
Here the generic type parameter T
corresponds to the element type of the expected output, so in your zip(A, B)
example, where A
is Iterable<X>
and B
is Iterable<Y>
, then T
would be [X, Y]
.
Then the iterables
rest parameter is of the mapped array/tuple type { [I in keyof T]: Iterable<T[I]> }
. A mapped array/tuple turns arrays/tuples into arrays/tuples and it essentially iterates only over the numeric/numeric-like indexes of the array/tuple. Meaning that you can think of the I
in keyof T
as iterating over "0"
, "1"
, etc., up to the largest index of the tuple. Thus {[I in keyof T]: Iterable<T[I]>}
will turn a tuple of types into a tuple of iterables of those types (e.g., [X, Y]
becomes [Iterable<X>, Iterable<Y>]
). Because it's a homomorphic mapped type (see What does "homomorphic mapped type" mean?), TypeScript is able to infer T
from { [I in keyof T]: Iterable<T[I]> }
.
Let's test it out:
declare const A: string[];
declare const B: Iterable<number>;
const z = zip(A, B);
// ^? const z: Generator<[string, number][]>
Looks good. Given the rest input argument of type [string[], Iterable<number>]
, the compiler is able to infer T
as [string, number]
, and then the output is of type Generator<[string, number][]>
, as desired.