Typescript: IterableIterator<T>.next().value is now any instead of T

Created on 10 Sep 2019  ·  7Comments  ·  Source: microsoft/TypeScript


TypeScript Version: 3.6.2


Search Terms: iterableiterator iterator

Code

declare function getIterator(): IterableIterator<number>;
const iter = getIterator()
const { value } = iter.next()

Expected behavior:
value: number (TS 3.5)

Actual behavior:
value: any

Playground Link:

NOTE: Playground is running TS 3.5 and exhibits "Expected behavior"
http://www.typescriptlang.org/play/#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwHMQMBJDEGKDHGACgEoAueMiqAIwhFcupgB5UyALbsKAPgDcAKDB4AzhnhZyMeAF5CxHlRoNZCpQG94ANygRkCAL4blqgHSoQADwz6gA

Related Issues:

Needs Investigation Rescheduled

Most helpful comment

The root cause is that IterableIterator<T> uses Iterator<T>, only specifying the type of yielded values. The type of returned values (TReturn) defaults to any. As a result, iter.next() is of type IteratorResult<number, any> which equals IteratorYieldResult<number> | IteratorReturnResult<any>. The former has { value: number } and the latter has { value: any }, so the union becomes { value: any }.

That said: IteratorResult is a discriminated union type, so you can use its done property to find out whether you're dealing with an IteratorYieldResult or an IteratorReturnResult. This would also make your code sample more correct: if iter is empty, value will probably not be a number. Unfortunately, you can't use a destructuring assignment then, since that loses the "connection" between done and value.

const iter = getIterator();
const result = iter.next();
if (!result.done) {
    const value = result.value;
    // value is a number here
}

All 7 comments

The root cause is that IterableIterator<T> uses Iterator<T>, only specifying the type of yielded values. The type of returned values (TReturn) defaults to any. As a result, iter.next() is of type IteratorResult<number, any> which equals IteratorYieldResult<number> | IteratorReturnResult<any>. The former has { value: number } and the latter has { value: any }, so the union becomes { value: any }.

That said: IteratorResult is a discriminated union type, so you can use its done property to find out whether you're dealing with an IteratorYieldResult or an IteratorReturnResult. This would also make your code sample more correct: if iter is empty, value will probably not be a number. Unfortunately, you can't use a destructuring assignment then, since that loses the "connection" between done and value.

const iter = getIterator();
const result = iter.next();
if (!result.done) {
    const value = result.value;
    // value is a number here
}

Okay, fair enough. I had been using done but I didn't realize the type was discriminated.

I specifically ran into this with calling numberArray[Symbol.iterator]() – could that type be narrowed since we know the behavior of array iterators? If done is true, then value will be undefined.

Yeah, it's unfortunate that TReturn defaults to any, since most iterators return { done: true, value: undefined } once they're done. I guess it may be needed for backwards compatibility with older code? 🤷‍♂

Related issue:
https://github.com/microsoft/TypeScript/issues/33241

Turns out you can’t rely on the checking the done property to narrow the type of value so in your example above value is still of type any.

Apparently this only happens when strictNullChecks is off (see comment). If possible, you can try turning on strictNullChecks (or even beter: strict).

The problem was that we previously conflated the yielded type and the return type in generators. Unfortunately, defaulting to any was necessary for backwards compatibility. Ideally I would default TReturn to void, but that caused significant breaks in real-world code.

What about interface Array<T> { [Symbol.iterator]: … }? Can that iterator be typed better than any?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Antony-Jones picture Antony-Jones  ·  3Comments

bgrieder picture bgrieder  ·  3Comments

dlaberge picture dlaberge  ·  3Comments

MartynasZilinskas picture MartynasZilinskas  ·  3Comments

jbondc picture jbondc  ·  3Comments