-
Notifications
You must be signed in to change notification settings - Fork 12.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add types for iterator helpers proposal #58222
Conversation
The DOM typings are maintained at https://github.com/microsoft/TypeScript-DOM-lib-generator |
More concretely, I'll need someone to tell me how to handle the change: changing the DOM typings to use |
Would the following work?
This would allow what you're looking for: keeps compatibility for pre-esnext targets, while allowing the helper methods to be merged into Sparse illustration: // @target: esnext
// @lib: esnext
// @filename: lib.es2015.iterable.d.ts
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>;
}
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
interface IterableIterator<T> extends Iterator<T> {
[Symbol.iterator](): IterableIterator<T>;
}
interface NativeIterator<T, TReturn = void, TNext = undefined> extends Iterator<T, TReturn, TNext> {
[Symbol.iterator](): NativeIterator<T, TReturn, TNext>;
}
interface Generator<T = unknown, TReturn = any, TNext = unknown> extends NativeIterator<T, TReturn, TNext> {
// etc.
}
interface Array<T> /* etc. */ {
// was: [Symbol.iterator](): IterableIterator<T>;
[Symbol.iterator](): NativeIterator<T>;
// was: entries(): IterableIterator<[number, T]>;
entries(): NativeIterator<[number, T]>;
// etc.
}
// @filename: lib.esnext.iterator.d.ts
interface NativeIterator<T, TReturn, TNext> {
map<U>(callbackfn: (value: T, index: number) => U): NativeIterator<U>;
// etc.
}
// @filename: test.ts
const mappedArrayIterator = ['a', 'b', 'c'].entries().map(([k, s]) => k * s.charCodeAt(0)); // NativeIterator<number>
const castToIterableIterator: IterableIterator<number> = mappedArrayIterator; // NativeIterator<T> remains assignable to IterableIterator<T>, for what it's worth As for the question of extending |
Nice, that sounds like a good approach to me. |
@bakkot the approach I am experimenting with to support // lib.esnext.iterator.d.ts
/// <reference lib="es2015.iterable" />
export {};
// Abstract type that allows us to mark `next` as `abstract`
declare abstract class Iterator<T> {
abstract next(value?: undefined): IteratorResult<T, void>;
}
// Merge all members of `NativeIterator<T>` into `Iterator<T>`
interface Iterator<T> extends globalThis.NativeIterator<T, void, undefined> {}
// Capture the `Iterator` constructor in a type we can use in the `extends` clause of `IteratorConstructor`.
type NativeIteratorConstructor = typeof Iterator;
declare global {
// Global `NativeIterator<T>` interface that can be augmented by polyfills
interface NativeIterator<T, TReturn, TNext> {
// prototype elements
}
// Global `IteratorConstructor` interface that can be augmented by polyfills
interface IteratorConstructor extends NativeIteratorConstructor {
// static elements
}
var Iterator: IteratorConstructor;
}
// lib.es2015.iterable.d.ts
...
interface NativeIterator<T, TReturn = void, TNext = undefined> extends Iterator<T, TReturn, TNext> {
[Symbol.iterator](): NativeIterator<T>;
}
... And in use: new Iterator<number>(); // ts(2511): Cannot create an instance of an abstract class.
class C extends Iterator<number> {} // ts(2515): Non-abstract class 'C' does not implement inherited
// abstract member next from class 'Iterator<number>'. Lib references can't really be referenced as modules, and even if you could this provides no exports. However, we will still augment the global scope with the types defined in |
There need to be a few other changes so that it can also be used with generators, though. |
src/lib/esnext.iterator.d.ts
Outdated
find<S extends T>(predicate: (value: T, index: number) => value is S): S | undefined; | ||
find(predicate: (value: T, index: number) => unknown): T | undefined; | ||
|
||
readonly [Symbol.toStringTag]: "Iterator"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest defining this as
readonly [Symbol.toStringTag]: "Iterator"; | |
readonly [Symbol.toStringTag]: string; |
otherwise subclasses of Iterator
won't be able to redefine it.
src/lib/esnext.iterator.d.ts
Outdated
|
||
declare var Iterator: (abstract new <T>() => NativeIterator<T>) & IteratorConstructor; | ||
|
||
// TODO BEFORE MERGING: update all existing IterableIterator-return methods to return NativeIterator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As suggested elsewhere, I would suggest you define NativeIterator
as follows in the es2015 libs and then just update all of the IterableIterator
references to NativeIterator
:
interface NativeIterator<T, TReturn = void, TNext = undefined> extends Iterator<T, TReturn, TNext> {
[Symbol.iterator](): NativeIterator<T, TReturn, TNext>;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I split that into a separate PR, or do it here?
src/lib/esnext.iterator.d.ts
Outdated
@@ -0,0 +1,133 @@ | |||
interface NativeIterator<T, TReturn = void, TNext = undefined> extends Iterator<T, TReturn, TNext> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The export {}
trick mentioned in my earlier comment can address the abstract next()
method definition.
src/lib/esnext.iterator.d.ts
Outdated
@@ -0,0 +1,133 @@ | |||
interface NativeIterator<T, TReturn = void, TNext = undefined> extends Iterator<T, TReturn, TNext> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The word choose "Native" is not so good. Do we have other options?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rbuckton does the interface merging make any existing "custom" Iterator objects fail type validation in target:esnext, since they won't implement the new Iterator.prototype methods? Edit: I overlooked the module hacking. Very nice. |
It does not merging the current Iterator interface, but create a new one called NativeIterator |
@bakkot is correct. This should not affect anyone implementing a custom After this is in we may want to add a shortcuts for it to |
This reverts commit bd51b00. (and subsequent commits)
Updated almost exactly following this comment, except that I had BuiltinIterator's A consequence of this change is that the type of Also, passing declare abstract class Iterator<T> {
abstract next(value?: undefined): IteratorResult<T, void>;
} means that classes attempted to implement I have test cases illustrating the difficulties:
I see a few approaches to improving this last issue:
|
One detail not captured by the types here is that the iterator returned by helpers like |
We could probably add |
Do helpers like |
Regarding the typing issues, I'm hoping #58243 might help with that. |
Nope, just Suggestions for the name? |
I suppose that's fine. @DanielRosenwasser, do you have any thoughts on the name for this type? |
Assuming we land #58243 today or tomorrow, I'd like to try to get this PR updated and ready to merge tomorrow as well. In addition to updating from |
@rbuckton I'm happy either way; if it's easier for you to just push the changes yourself rather than leaving feedback that's fine by me. Otherwise I'll try to get to your feedback asap, though I can't commit to having it done tomorrow. (Or if it's easier for you to just make a new PR, that's also fine.) I did the initial update on |
#58243 has merged. I'm happy to align this PR to get it merged today. |
I've made updates to align with the changes in #58243, though it required a few small compromises. If necessary, we can refine this somewhat after its in the beta. |
The TypeScript team hasn't accepted the linked issue #54481. If you can get it accepted, this PR will have a better chance of being reviewed. |
I realize I neglected to run the user test suite before merging, though I expect it will only call out projects that were already called out by #58243. Running that suite now in case that assumption turns out to be false. @typescript-bot run dt |
Also running benchmarks as I expect I need to put up a follow-up PR that adds a @typescript-bot perf test |
Starting jobs; this comment will be updated as builds start and complete.
|
Why |
ref: #59388 |
Fixes #54481.
I have only minimal tests because almost all the types are copied directly from Array with very minimal changes (mostly dropping the third "array" parameter to callbacks and the "thisArg" parameter to callback-taking methods, and returning
NativeIterator<T>
instead ofT[]
). But I'm happy to make the tests more extensive if you'd like.For reviewing:
BuiltinIterator
andAsyncBuiltinIterator
types and adopt them everywhere except the.generated
files.AsyncBuiltinIterator
isn't necessary yet but I figured we might as well do this plumbing work now.The "rebaseline" commits are just running
npm run test -- --no-lint; npx hereby baseline-accept
and committing the result.Current status (as of 2024-04-25):
CloseableBuiltinIterator
or something to mean "built-in iterator with areturn
method"I'll address the outstanding todos one #58243 lands.