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][]>
>Solution :
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.