Description
lib Update Request
Configuration Check
My compilation target is ESNext
and my lib is the default
.
Missing / Incorrect Definition
The TS lib defs for Iterator
, Iterable
, and IterableIterator
treat them as if they are all fictitious interfaces (a.k.a. protocols). The lib def's inheritance structure looks like this:
interface Iterator<T, TReturn = any, TNext = undefined> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return?(value?: TReturn): IteratorResult<T, TReturn>;
throw?(e?: any): IteratorResult<T, TReturn>;
}
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
interface IterableIterator<T> extends Iterator<T> {
[Symbol.iterator](): IterableIterator<T>;
}
However, while Iterable
and arguably IterableIterator
are protocols, Iterator
is an actual JS entity. See MDN docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator This has some practical issues, because what TS terms as IterableIterator
is the actual JS Iterator
class, but what TS terms as Iterator
is what JS regards as "iterator-like" or "iterator protocol". For example, the def for new Set().values()
says it returns an IterableIterator
, while in fact it should return an instance of the JS Iterator
instance.
This becomes more of an issue after the iterator-helpers proposal, which is going to expose Iterator
as an actual global that people can use as extends
. For example:
class MyClass {
static #MyClassIterator = class extends Iterator {
next() { ... }
};
[Symbol.iterator]() {
return new MyClass.#MyClassIterator();
}
}
Under the current lib def, #MyClassIterator
won't be seen as iterable by TS, because it extends Iterator
, not IterableIterator
.
On the other hand, if we change the definition of Iterator
to say it has [Symbol.iterator]
(mirroring what happens in JS), then it will break code of the following kind:
function stepIterator(it: Iterator) {
return it.next();
}
stepIterator({ next() {} });
Of course you could assume that almost all userland iterators are built-in and therefore inherit from Iterator
and have @@iterator
, but it's a breaking change nonetheless.
The question thus arises that when iterator-helpers lands, where should we put the new definitions on. Right now, I'm using core-js to polyfill these methods, and when writing declarations, I chose to put them on IterableIterator
, so that new Set().values()
works:
declare global {
interface IterableIterator<T> {
filter(
predicate: (value: T, index: number) => unknown,
): IterableIterator<T>;
map<U>(mapper: (value: T, index: number) => U): IterableIterator<U>;
toArray(): T[];
}
}
...but this is suboptimal, for obvious reasons (it means the Iterator
class doesn't actually have these methods).