Skip to content

Commit

Permalink
Type iterator helpers (#9232)
Browse files Browse the repository at this point in the history
Summary:
Fixes #9230. Context in that issue.

See [code-level comments and stacked commits on the PR](#9232).

## Changes

This PR is mostly additive, but there are some change. At least one is breaking.

### Iterators
- `Iterator` is now a class representing the built-in, instead of a type representing an `IterableIterator` protocol.
   - This is the breaking change. Places that were using `Iterator` to represent the protocol need to do one of:
      - Switch to `$IteratorProtocol` to continue accepting custom implementations.
      - Wrap value with `Iterator.from` to get `Iterator`s.
   - The class now has three generic args, like `$Iterator`, but supplies defaults for the last two for backwards compatibility and simple uses.
- `$Iterator` is now a type alias for this class. It can likely be deprecated and removed.
- `$IteratorProtocol` is comparable to the old `$Iterator`, but does not require implementors to also implement
`$Iterable` so that it imposes no extra constraints on `Iterator.from`.

### Iterables
- `Iterable` and `$Iterable` now only constrain `@iterator` to `$IteratorProtocol`. The return value does not need to be iterable. Note that `Iterator.@iterator` returns an `Iterator` which is implements both the iterable and iterator protocols.

### Generators
- `Generator` is now a type alias for a subclass of `Iterator`, rather than an interface. I [don't believe it's possible to implement a custom Generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator#constructor) so I believe this is correct.

## Consistency
I prioritized minimizing changes over internal consistency with this PR. One path back to consistency:
- Rename `$Iterable` to `$IterableProtocol` for consistency with `$IteratorProtocol` (optionally remove the $ prefix from both).
- Remove `$Iterator`.
This would have to be gradual migrations.

Pull Request resolved: #9232

Test Plan: $ make && bash runtests.sh bin/flow

Reviewed By: SamChou19815

Differential Revision: D66393981

fbshipit-source-id: 290fc3850d27c351f419363039b141f37a4174f6
  • Loading branch information
Adam Hull authored and facebook-github-bot committed Nov 26, 2024
1 parent 28adc18 commit 68da336
Show file tree
Hide file tree
Showing 46 changed files with 589 additions and 332 deletions.
134 changes: 126 additions & 8 deletions lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ declare var Math: {
*/
declare class $ReadOnlyArray<+T> {
@@iterator(): Iterator<T>;
/**
/**
* Returns a string representation of an array. The elements are converted to string using their toLocalString methods.
*/
toLocaleString(): string;
Expand Down Expand Up @@ -1742,24 +1742,142 @@ type IteratorResult<+Yield,+Return> =
...
};

interface $Iterator<+Yield,+Return,-Next> extends $Iterable<Yield,Return,Next> {
@@iterator(): $Iterator<Yield,Return,Next>;
/**
* The iterator protocol expected by built-ins like Iterator.from().
* You can implement this yourself.
*/
interface $IteratorProtocol<+Yield,+Return=void,-Next=void> {
next(value?: Next): IteratorResult<Yield,Return>;
}
type Iterator<+T> = $Iterator<T,void,void>;

/**
* The built-in Iterator abstract base class. Iterators for Arrays, Strings, and Generators all inherit from this class.
* Extend this class to implement custom iterators that support all iterator helper methods.
* Note that you can use `Iterator.from()` to get an iterator helper wrapping any object that implements
* the iterator protocol.
*/
declare class Iterator<+Yield,+Return=void,-Next=void> implements $IteratorProtocol<Yield,Return,Next>, $Iterable<Yield,Return,Next> {
@@iterator(): Iterator<Yield,Return,Next>;
next(value?: Next): IteratorResult<Yield,Return>;
/**
* Returns a new iterator that yields that values returned by calling the argument callback on each value yielded by this iterator.
* @param callbackfn A function that accepts up to two arguments: the yielded value and its index. The map method calls the callbackfn function once per iteration.
*/
map<U>(callbackfn: (value: Yield, index: number) => U): Iterator<U, void, mixed>;
/**
* Returns a new iterator that yields only those elements of the iterator for which the provided predicate returns a truthy value.
* @param callbackfn A function that accepts up to two arguments: the yielded value and its index. The filter method calls the predicate function once per iteration.
*/
filter(callbackfn: typeof Boolean): Iterator<$NonMaybeType<Yield>, void, mixed>;
/**
* Returns a new iterator that yields only those elements of the iterator for which the provided predicate returns a truthy value.
* @param callbackfn A function that accepts up to two arguments: the yielded value and its index. The filter method calls the predicate function once per iteration.
*/
filter<Refined: Yield>(callbackfn: (value: Yield, index: number) => implies value is Refined): Iterator<Refined, void, mixed>;
/**
* Returns a new iterator that yields only those elements of the iterator for which the provided predicate returns a truthy value.
* @param callbackfn A function that accepts up to two arguments: the yielded value and its index. The filter method calls the predicate function once per iteration.
*/
filter(callbackfn: (value: Yield, index: number) => mixed): Iterator<Yield, void, mixed>;
/**
* Returns a new iterator that yields up to the given number of elements in this iterator and then terminates.
* @param limit The maximum number of values to yield.
*/
take(limit: number): Iterator<Yield, void, mixed>;
/**
* Returns a new iterator that yields values from this iterator after skipping the provided count. If the this iterator has fewer than limit elements, the new iterator will be completed the first time next() is called.
* @param count The number of values to drop.
*/
drop(count: number): Iterator<Yield, void, mixed>;
/**
* Returns a new iterator that takes each value yielded by this iterator, runs it through the argument mapping function to get another iterable, and yields each value returned by these mapped iterables.
* @param callbackfn A function that accepts up to two arguments: the yielded value and its index. The map method calls the callbackfn function once per iteration. The callback must return an iterable.
*/
flatMap<U>(callbackfn: (value: Yield, index: number) => $IteratorProtocol<U, mixed, void> | $Iterable<U, mixed, void>): Iterator<U, void, mixed>;
/**
* Calls the argument "reducer" callback function on each value produced by this iterator, passing in the return value from the calculation on the preceding iteration. The final result of running the reducer across all elements is a single value.
* Exhausts this iterator. Will hang if it's infinite.
* @param callbackfn A function that accepts up to three arguments: the accumulated value, the current value, and the current index.
* @param initialValue If initialValue is specified, it is used as the initial value to start
* the accumulation. If omitted, the first call of the callback gets the first two iterated values.
*/
reduce<U>(
callbackfn: (previousValue: Yield | U, currentValue: Yield, index: number) => U,
): Yield | U;
/**
* Calls the argument "reducer" callback function on each value produced by this iterator, passing in the return value from the calculation on the preceding iteration. The final result of running the reducer across all elements is a single value.
* Exhausts this iterator. Will hang if it's infinite.
* @param callbackfn A function that accepts up to three arguments: the accumulated value, the current value, and the current index.
* @param initialValue If initialValue is specified, it is used as the initial value to start
* the accumulation. If omitted, the first call of the callback gets the first two iterated values.
*/
reduce<U>(
callbackfn: (previousValue: U, currentValue: Yield, index: number) => U,
initialValue: U
): U;
/**
* Creates a new array from the values yielded by this iterator.
* Exhausts this iterator. Will hang if it's infinite.
*/
toArray(): Array<Yield>;
/**
* Calls the argument function for each value yielded by the iterator. Any returned values are ignored.
* Exhausts this iterator. Will hang if it's infinite.
* @param callbackfn A function that accepts up to two arguments: the yielded value and its index.
*/
forEach(callbackfn: (value: Yield, index: number) => mixed): void;
/**
* Calls the argument function for each value yielded by the iterator.
* Returns true if the predicate function ever returns a truthy value.
* Exhausts this iterator. Will hang if it's infinite.
* @param callbackfn A function that accepts up to two arguments: the yielded value and its index.
*/
some(callbackfn: (value: Yield, index: number) => mixed): boolean;
/**
* Calls the argument function for each value yielded by the iterator.
* Returns true if the predicate function returns a truthy value for all iterated values.
* Exhausts this iterator. Will hang if it's infinite.
* @param callbackfn A function that accepts up to two arguments: the yielded value and its index.
*/
every(callbackfn: (value: Yield, index: number) => mixed): boolean;
/**
* Calls the argument function for values yielded by the iterator until one returns a truthy value.
* Returns the first value that produced a truthy predicate.
* Returns undefined if the iterator completes without a match.
* Iterates this iterator. Will hang if it's infinite and has no matches.
* @param callbackfn A function that accepts up to two arguments: the yielded value and its index.
*/
find<Refined: Yield>(predicate: (value: Yield, index: number) => implies value is Refined): Refined | void;
find(callbackfn: (value: Yield, index: number) => mixed): Yield | void;
/**
* This method allows wrapping objects that implement the iterator protocol in an Iterator to get the built-in helpers.
* @returns The argument object if it's already an Iterator subclass, or a wrapping Iterator if the argument objects implements the iterable or iterator protocols.
*/
static from<+Yield,+Return,-Next>(source: $IteratorProtocol<Yield,Return,Next> | $Iterable<Yield,Return,Next>): Iterator<Yield,Return,Next>;
}

type $Iterator<+Yield,+Return,-Next> = Iterator<Yield,Return,Next>;

/**
* The iterable protocol expected by built-ins like Array.from().
* You can implement this yourself.
*/
interface $Iterable<+Yield,+Return,-Next> {
@@iterator(): $Iterator<Yield,Return,Next>;
@@iterator(): $IteratorProtocol<Yield,Return,Next>;
}
type Iterable<+T> = $Iterable<T,void,void>;

interface Generator<+Yield,+Return,-Next> {
@@iterator(): $Iterator<Yield,Return,Next>;
next(value?: Next): IteratorResult<Yield,Return>;
// The generator class is not exposed in the global namespace.
declare class $Generator<+Yield,+Return,-Next> extends Iterator<Yield,Return,Next> {
return<R>(value: R): IteratorResult<Yield,R|Return>;
throw(error?: any): IteratorResult<Yield,Return>;
}

/**
* Built-in Generator objects returned by generator functions.
*/
type Generator<+Yield,+Return,-Next> = $Generator<Yield,Return,Next>;

declare function $iterate<T>(p: Iterable<T>): T;

/* Async Iterable/Iterator/Generator */
Expand Down
44 changes: 22 additions & 22 deletions tests/async/async.exp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ References:
async.js:9:30
9| async function f1(): Promise<boolean> {
^^^^^^^ [2]
<BUILTINS>/core.js:1984:24
1984| declare class Promise<+R = mixed> {
<BUILTINS>/core.js:2102:24
2102| declare class Promise<+R = mixed> {
^ [3]


Expand All @@ -31,8 +31,8 @@ References:
async.js:28:48
28| async function f4(p: Promise<number>): Promise<boolean> {
^^^^^^^ [2]
<BUILTINS>/core.js:1984:24
1984| declare class Promise<+R = mixed> {
<BUILTINS>/core.js:2102:24
2102| declare class Promise<+R = mixed> {
^ [3]


Expand Down Expand Up @@ -99,8 +99,8 @@ undefined in type argument `R` [2]. [incompatible-return]
^^^^^^ [1]

References:
<BUILTINS>/core.js:1984:24
1984| declare class Promise<+R = mixed> {
<BUILTINS>/core.js:2102:24
2102| declare class Promise<+R = mixed> {
^ [2]


Expand Down Expand Up @@ -142,8 +142,8 @@ References:
async_return_void.js:1:32
1| async function foo1(): Promise<string> {
^^^^^^ [2]
<BUILTINS>/core.js:1984:24
1984| declare class Promise<+R = mixed> {
<BUILTINS>/core.js:2102:24
2102| declare class Promise<+R = mixed> {
^ [3]


Expand All @@ -160,8 +160,8 @@ References:
async_return_void.js:5:32
5| async function foo2(): Promise<string> {
^^^^^^ [2]
<BUILTINS>/core.js:1984:24
1984| declare class Promise<+R = mixed> {
<BUILTINS>/core.js:2102:24
2102| declare class Promise<+R = mixed> {
^ [3]


Expand All @@ -181,8 +181,8 @@ References:
async_return_void.js:9:32
9| async function foo3(): Promise<string> {
^^^^^^ [2]
<BUILTINS>/core.js:1984:24
1984| declare class Promise<+R = mixed> {
<BUILTINS>/core.js:2102:24
2102| declare class Promise<+R = mixed> {
^ [3]


Expand Down Expand Up @@ -220,11 +220,11 @@ string [2] in type argument `Yield` [3]. [incompatible-return]
^^^^^^ [1]

References:
<BUILTINS>/core.js:2047:58
2047| T extends $ReadOnlyArray<mixed> ? {[K in keyof T]: Awaited<T[K]>} :
<BUILTINS>/core.js:2165:58
2165| T extends $ReadOnlyArray<mixed> ? {[K in keyof T]: Awaited<T[K]>} :
^^^^^^^^^^^^^ [2]
<BUILTINS>/core.js:1778:27
1778| interface AsyncGenerator<+Yield,+Return,-Next> {
<BUILTINS>/core.js:1896:27
1896| interface AsyncGenerator<+Yield,+Return,-Next> {
^^^^^ [3]


Expand All @@ -237,14 +237,14 @@ string [1] is incompatible with number [2] in type argument `Yield` [3]. [incomp
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

References:
<BUILTINS>/core.js:2047:58
2047| T extends $ReadOnlyArray<mixed> ? {[K in keyof T]: Awaited<T[K]>} :
<BUILTINS>/core.js:2165:58
2165| T extends $ReadOnlyArray<mixed> ? {[K in keyof T]: Awaited<T[K]>} :
^^^^^^^^^^^^^ [1]
generator.js:21:45
21| async function *genError1(): AsyncGenerator<number, void, void> {
^^^^^^ [2]
<BUILTINS>/core.js:1778:27
1778| interface AsyncGenerator<+Yield,+Return,-Next> {
<BUILTINS>/core.js:1896:27
1896| interface AsyncGenerator<+Yield,+Return,-Next> {
^^^^^ [3]


Expand All @@ -258,8 +258,8 @@ Cannot yield `1` because number [1], a primitive, cannot be used as a subtype of
^ [1]

References:
<BUILTINS>/core.js:1789:7
1789| : $Iterable<Yield, Return, Next>;
<BUILTINS>/core.js:1907:7
1907| : $Iterable<Yield, Return, Next>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [2]


Expand Down
1 change: 1 addition & 0 deletions tests/autocomplete/autocomplete.exp
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,7 @@ Flags: --pretty
{"name":"Intl$PluralRules","type":"typeof Intl$PluralRules"},
{"name":"isFinite","type":"(number: mixed) => boolean"},
{"name":"isNaN","type":"(number: mixed) => boolean"},
{"name":"Iterator","type":"typeof Iterator"},
{
"name":"JSON",
"type":"{|+parse: (text: string, reviver?: (key: any, value: any) => any) => any, +stringify: ((value: null | string | number | boolean | interface {} | $ReadOnlyArray<mixed>, replacer?: ?((key: string, value: any) => any) | Array<any>, space?: string | number) => string) & ((value: mixed, replacer?: ?((key: string, value: any) => any) | Array<any>, space?: string | number) => string | void)|}"
Expand Down
20 changes: 19 additions & 1 deletion tests/babel_loose_array_spread/babel_loose_array_spread.exp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ References:
^^^^^^^^^^^^^^^^^ [2]


Error --------------------------------------------------------------------------------------------------- apply.js:11:15

Cannot call `f.apply` because `$IteratorProtocol` [1] is incompatible with `Iterator` [2] in the return value of
property `@@iterator` of type argument `A`. [incompatible-call]

apply.js:11:15
11| f.apply(null, it); // Error
^^

References:
<BUILTINS>/core.js:1866:19
1866| @@iterator(): $IteratorProtocol<Yield,Return,Next>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1]
<BUILTINS>/core.js:1149:17
1149| @@iterator(): Iterator<T>;
^^^^^^^^^^^ [2]


Error ------------------------------------------------------------------------------------------------- iterables.js:2:5

`$Iterable` [1] is incompatible with `$ReadOnlyArray` [2]. [incompatible-type]
Expand Down Expand Up @@ -187,4 +205,4 @@ References:



Found 11 errors
Found 12 errors
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ it into an object and attempt to use it as a subtype of an interface. [incompati
^ [1]

References:
<BUILTINS>/core.js:1751:11
1751| interface $Iterable<+Yield,+Return,-Next> {
<BUILTINS>/core.js:1865:11
1865| interface $Iterable<+Yield,+Return,-Next> {
^^^^^^^^^ [2]


Expand All @@ -23,8 +23,8 @@ it into an object and attempt to use it as a subtype of an interface. [incompati
^ [1]

References:
<BUILTINS>/core.js:1751:11
1751| interface $Iterable<+Yield,+Return,-Next> {
<BUILTINS>/core.js:1865:11
1865| interface $Iterable<+Yield,+Return,-Next> {
^^^^^^^^^ [2]


Expand Down
24 changes: 12 additions & 12 deletions tests/bigint/bigint.exp
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,23 @@ Cannot call `BigInt` with `null` bound to `value` because: [incompatible-call]
^^^^ [1]

References:
<BUILTINS>/core.js:2774:18
2774| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
<BUILTINS>/core.js:2892:18
2892| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
^^^^^^^ [2]
<BUILTINS>/core.js:2774:28
2774| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
<BUILTINS>/core.js:2892:28
2892| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
^^^^^^ [3]
<BUILTINS>/core.js:2774:37
2774| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
<BUILTINS>/core.js:2892:37
2892| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
^^^^^^ [4]
<BUILTINS>/core.js:2774:46
2774| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
<BUILTINS>/core.js:2892:46
2892| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
^^^^^^ [5]
<BUILTINS>/core.js:2774:55
2774| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
<BUILTINS>/core.js:2892:55
2892| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
^^^^^^^^^^^^ [6]
<BUILTINS>/core.js:2774:70
2774| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
<BUILTINS>/core.js:2892:70
2892| static (value: boolean | string | number | bigint | interface {} | $ReadOnlyArray<mixed>): bigint;
^^^^^^^^^^^^^^^^^^^^^ [7]


Expand Down
8 changes: 4 additions & 4 deletions tests/component_type/component_type.exp
Original file line number Diff line number Diff line change
Expand Up @@ -573,8 +573,8 @@ References:
everything_implicit_instantiation.js:6:46
6| declare component B(ref: React.RefSetter<Set<string>>, foo: string, bar: number) renders? A;
^^^^^^ [2]
<BUILTINS>/core.js:1942:19
1942| declare class Set<T> extends $ReadOnlySet<T> {
<BUILTINS>/core.js:2060:19
2060| declare class Set<T> extends $ReadOnlySet<T> {
^ [3]
<BUILTINS>/react.js:235:19
235| type RefSetter<-T> = React$RefSetter<T>;
Expand Down Expand Up @@ -929,8 +929,8 @@ References:
props_implicit_instantiation.js:7:46
7| declare component A(ref: React.RefSetter<Set<string>>, foo: string, bar: number);
^^^^^^ [2]
<BUILTINS>/core.js:1942:19
1942| declare class Set<T> extends $ReadOnlySet<T> {
<BUILTINS>/core.js:2060:19
2060| declare class Set<T> extends $ReadOnlySet<T> {
^ [3]
<BUILTINS>/react.js:235:19
235| type RefSetter<-T> = React$RefSetter<T>;
Expand Down
Loading

0 comments on commit 68da336

Please sign in to comment.