Home > Software engineering >  TypeScript fails to infer type of an iterable
TypeScript fails to infer type of an iterable

Time:11-25

I got stuck trying to make TypeScript correctly infer an underlying iterable type, for function spread below, which is supposed to take an iterable of iterable types, infer type of the underlying iterable and return a new iterable with that underlying type.

interface Output<T, R> {
    (i: Iterable<T>): Iterable<R>;
}

function pipe<T, A>(i: Iterable<T>, p0: Output<T, A>): Iterable<A>;
function pipe(i: Iterable<any>, ...p: Output<any, any>[]): Iterable<any> {
    return []; // dummy iterable
}

// I'm trying to implement this function:
function spread<T extends Iterable<R>, R>(): Output<T, R> {
    return (iterable: Iterable<T>) => ({
        [Symbol.iterator](): Iterator<R> {
             return null as any; // dummy, for now
        }
    });
}

pipe(['text'], spread());
// expected types: spread => Output<string, string>, pipe => Iterable<string>
// actual types: spread => Iterable<string, unknown>, pipe => Iterable<unknown>

pipe([[1, 2], [3, 4]], spread());
// expected types: spread => Output<number[], number>, pipe => Iterable<number>
// actual types: spread => Output<number[], unknown>, pipe => Iterable<unknown>

In my mind, function spread has the right template signature, but in the two examples above, the iterable type resolves into unknown, which I cannot understand why.

The underlying spread, in the meantime, infers into <T, unknown>, and so pipe fails to infer the type also.


This is for iter-ops library that I'm working on. I have implemented numerous operators, without problems, but got stuck with the spread type inference.

UPDATE

Thanks to Titian Cernicova-Dragomir, I was able to properly finish the spread operator :)

CodePudding user response:

If I understand the code correctly spread will transform an Iterable<Iterable<T>> into and Iterable<T>.

This means that the function returned by spread should have the signature (iterable: Iterable<Iterable<T>>) => Iterable<T>. Using your types it should be Output<Iterable<T>, T>. So there is no need for the R argument as the input and output are related

interface Output<T, R> {
    (i: Iterable<T>): Iterable<R>;
}

function pipe<T, A>(i: Iterable<T>, p0: Output<T, A>): Iterable<A>;
function pipe(i: Iterable<any>, ...p: Output<any, any>[]): Iterable<any> {
    return []; // dummy iterable
}

function spread<T>(): Output<Iterable<T>, T> {
    return (iterable: Iterable<Iterable<T>>) => ({
        [Symbol.iterator](): Iterator<T> {
             return null as any; // dummy, for now
        }
    });
}

let r = pipe(['text'], spread()); // Iterable<string>

let r2 = pipe([[1, 2], [3, 4]], spread()); // Iterable<number>

Playground Link

  • Related