reconbot/streaming-iterables

`consume` types and `AnyIterable`

achingbrain opened this issue · 1 comments

It doesn't look like I can use the AnyIterable type with consume, even though it supports both Iterable<T> and AsyncIterable<T> as input:

import { consume } from 'streaming-iterables'
import type { AnyIterable } from 'streaming-iterables'

// specify return type otherwise typescript works out this is really `number[]`
function createSyncSource (): AnyIterable<number> {
  return [1, 2, 3]
}

// specify return type otherwise typescript works out this is really `AsyncGenerator<number, void, undefined>`
async function * createAsyncSource (): AnyIterable<number> {
  yield * [1, 2, 3]
}
const sourceArr = createSyncSource()
const sourceGen = createAsyncSource()

// works if I cast to the underlying type
consume(sourceArr as number[])
consume(sourceGen as AsyncIterable<number>)

// does not select the correct overload based on the possible types of input
consume(sourceArr)
consume(sourceGen)

The error is:

error TS2769: No overload matches this call.
Overload 1 of 2, '(iterable: Iterable<number>): void', gave the following error.
  Argument of type 'AsyncIterable<number>' is not assignable to parameter of type 'Iterable<number>'.
    Property '[Symbol.iterator]' is missing in type 'AsyncIterable<number>' but required in type 'Iterable<number>'.
Overload 2 of 2, '(iterable: AsyncIterable<number>): Promise<void>', gave the following error.
  Argument of type 'AsyncIterable<number>' is not assignable to parameter of type 'AsyncIterable<number>'.
    Property '[Symbol.asyncIterator]' is missing in type 'Iterable<number>' but required in type 'AsyncIterable<number>'.

If I add an additional overload to the type definition that's the same as the actual implementation it starts to work:

export function consume<T>(iterable: Iterable<T>): void
export function consume<T>(iterable: AsyncIterable<T>): Promise<void>
export function consume<T>(iterable: AnyIterable<T>): Promise<void> | void // <-- new overload
export function consume<T>(iterable: AnyIterable<T>) {
  if (iterable[Symbol.asyncIterator]) {
    return _consume(iterable)
  }
  for (const val of iterable as Iterable<T>) {
    // do nothing
  }
}

But then the return type is Promise<void> | <void>. I tried doing something clever like:

export declare type UnwrapToVoidOrVoidPromise<M extends AnyIterable<any>> = M extends Iterable<any> ? void : M extends AsyncIterable<any> ? Promise<void> : never;

but it's still Promise<void> | <void>. Maybe that's ok, I'm not sure.

I'm not 100% sure what to do here. I think adding void would be fine, I'd be for a pr.