Iterable cannot confirm generic T in function

Yuck, yeah, I see that the default inference doesn’t work deeply enough to unroll Iterable<Iterable<T>> into T. It’s not that surprising if you look at how the typings for Iterable are defined in the relevant library:

interface Iterable<T> {
    [Symbol.iterator](): Iterator<T>;
}

An Iterable<T> has a symbol-keyed method whose return type is Iterator<T>, which itself is defined as:

interface Iterator<T, TReturn = any, TNext = undefined> {
    next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
    return?(value?: TReturn): IteratorResult<T, TReturn>;
    throw?(e?: any): IteratorResult<T, TReturn>;
}

where all the methods return IteratorResult<T, ...>, which is defined to be a discriminated union type

type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;

whose members are

interface IteratorYieldResult<TYield> {
    done?: false;
    value: TYield;
}

interface IteratorReturnResult<TReturn> {
    done: true;
    value: TReturn;
}

of which only one has a value property of the relevant type T you’re trying to find.

So to turn a type X of Iterable<T> into T, the compiler needs to do something like Extract<ReturnType<ReturnType<X[typeof Symbol.iterator]>['next']>, { done?: false }>['value'].

I think there’s probably some depth limit after which the compiler gives up trying to infer things. You can see that inferring T from Iterable<T> works (maybe about 5 or 6 layers of nesting), but inferring T from Iterable<Iterable<T>> is just too deep (10 or 12 layers?):

type N = number[][] extends Iterable<infer T> ? T : never; // number[] 👍
type O = number[][] extends Iterable<Iterable<infer T>> ? T : never; // unknown 👎

That leads me to the following workaround: make a type alias to explicitly operate on one layer of Iterable, and then use that twice:

type DeIterable<T extends Iterable<any>> = T extends Iterable<infer U> ? U : never;

You can see this works:

type Okay = DeIterable<DeIterable<number[][]>>; // number 👍

And now flat() can be defined like this:

function flat<II extends Iterable<Iterable<any>>>(
  t: II
): Iterable<DeIterable<DeIterable<II>>> {
  return [...t][0]
}

Where the input value is the generic type II, and we use DeIterable on it twice to get the T you wanted before:

const flatted = flat(iterable) //return Iterable<number[]>  

Looks good, now!

Playground link to code

Leave a Comment