I'm trying to create a subclass of Set, and since I cannot just simply extend from it, I'm wrapping its functionality.
I'm trying to implement the Symbol.iterator method, but Flow is having none of it.
This is the code that I have:
/* @flow */
class CSet<T> {
_set: Set<T>;
[Symbol.iterator](): Iterator<T> {
return this._set[Symbol.iterator];
}
}
var a: CSet = new CSet();
for(var b of a){
}
core.js:309:5,29: property @@iterator
Property not found in
test.js:2:7,10: CSet
test.js:4:2,6:2:
computed property keys not supported
The second error isn't as huge of a deal since I can easily suppress it. I'm wondering if I'm just doing something wrong all-together though.
Because Flow doesn't currently have general support for symbols, the way it represents Symbol.iterator
is hacky and basically prevents the ability to define iterators in userspace for now (its support only works in library definitions) :(
Specifically Flow expects that an iterable has a @@iterator
property on them (which is certainly not a valid property name -- but this was a temporary hack to get support in library definitions).
So until proper symbol support lands, your best bet for a workaround here would be to create a library definition for this module that uses this @@iterator
property for Flow's understanding:
// RealModule.js
export class CSet {
[Symbol.iterator]() {
return this._set[Symbol.iterator];
}
}
.
// RealModule.FlowLibDef.js
declare module RealModule {
declare class CSet<T> {
_set: Set<T>;
@@iterator(): Iterator<T>;
}
}
// @flow
class MyCollection<T> {
/*:: @@iterator(): Iterator<T> { return ({}: any); } */
// $FlowFixMe: computed property
[Symbol.iterator](): Iterator<T> {
// NOTE: this could just as easily return a different implementation
return new MyIterator(this);
}
}
class MyIterator<+T> {
/*:: @@iterator(): Iterator<T> { return (this: any); } */
// $FlowFixMe: computed property
[Symbol.iterator](): Iterator<T> {
return this;
}
next(): IteratorResult<T, void> {
return { done: false, value: someT };
// or return { done: true, value: undefined };
}
}
for (const value of new MyCollection()) {
console.log(value);
}
The reason this works is that flow interprets /*:: code */
as though it were flow code in the source, except that it is commented out at runtime therefore does not actually affect the code.
Flow intrinsically knows about the @@iterator
method, despite it not being valid JavaScript, thus we define it as existing, returning an Iterator<T>
, and returning a value that works for it (i.e. an empty object cast as any
).
The computed property method is then ignored completely by flow, as if it were not defined at all. It is crucial that you actually return a valid Iterator<T>
from the method, otherwise things will break at runtime.
I've found a trick that lets you mark a user-defined class as iterable, without needing to write and maintain a parallel libdef for your entire class.
The key is to write a dedicated superclass that implements [Symbol.iterator]()
, and provide a libdef just for that superclass:
// IterableBase.js
export default class IterableBase {
[Symbol.iterator]() {
return this._Symbol_iterator();
}
}
// IterableBase.js.flow
// @flow
declare class IterableBase<T> {
@@iterator(): Iterator<T>;
}
export default IterableBase;
Now you can have your custom class extend IterableBase
and implement the substitute method name:
// RealModule.js
// @flow
import IterableBase from './IterableBase';
class CSet<T> extends IterableBase<T> {
_Symbol_iterator(): Iterator<T> {
...
}
}
This is obviously still a hack, but it should be easier and safer than the alternative.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With