Repository navigation
TypeScript 2.0 change in IterableResult<T> makes implementing iterators harder #11375
Copy link
Copy link
Closed
Labels
CommittedThe team has roadmapped this issueThe team has roadmapped this issueFixedA PR has been merged for this issueA PR has been merged for this issueSuggestionAn idea for TypeScriptAn idea for TypeScript
Milestone
Description
TypeScript Version: 2.0.3
The definition in lib.es6.d.ts of IteratorResult (removing the ? after value) seems to have broken existing Iterator implementations, and is particularly troublesome with strict null checking enabled. See http://stackoverflow.com/questions/39813087/why-did-typescript-2-0-change-iteratorresultk for more details.
Code
A short version:
export interface JavaIterator<E> {
hasNext(): boolean;
next(): E;
remove(): void;
}
...
class IterableAdapter<T> implements Iterable<T>, IterableIterator<T> {
private _iterator: JavaIterator<T>
constructor(private collection: JavaCollection<T>){}
[Symbol.iterator]() { this._iterator = this.collection.iterator(); return this;}
next(): IteratorResult<T> {
if (!this._iterator.hasNext()) return { done: true }; // Error on this line
return {done: false, value: this._iterator.next()}
}
}
this version works
class IterableAdapter<T> implements Iterable<T>, IterableIterator<T> {
private _iterator: JavaIterator<T>
constructor(private collection: JavaCollection<T>){}
[Symbol.iterator]() { this._iterator = this.collection.iterator(); return this;}
next(): IteratorResult<T> {
if (!this._iterator.hasNext()) {
// A bit of a hack needed here needed for strict null checking
return { done: true, value: undefined } as any as IteratorResult<T>;
}
return {done: false, value: this._iterator.next()}
}
}
original bug
class HashMapKeyIterable<K,V> implements Iterator<K>, IterableIterator<K> {
private _bucket: HashMapEntry<K,V>[];
private _index: number;
constructor( private _buckets : Iterator<HashMapEntry<K,V>[]> ){
this._bucket = undefined;
this._index = undefined;
}
[Symbol.iterator]() { return this }
next(): IteratorResult<K> {
while (true) {
if (this._bucket) {
const i = this._index++;
if (i < this._bucket.length) {
let item = this._bucket[i];
return {done: false, value: item.key}
}
}
this._index = 0
let x = this._buckets.next();
if (x.done) return {done: true}; // Under TS 2.0 this needs to
this._bucket = x.value; // return {done: true: value: undefined};
}
}
}
Expected behavior:
Clean compile under TypeScript 2.0
Actual behavior:
Errors reported: TS2322: Type '{ done: true; }' is not assignable to type 'IteratorResult'. Property 'value' is missing in type '{ done: true; }'
Note: with strict null checking on, I seem to need to go further, resorting to
return { done: true, value: undefined } as any as IteratorResult<T>;`
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
CommittedThe team has roadmapped this issueThe team has roadmapped this issueFixedA PR has been merged for this issueA PR has been merged for this issueSuggestionAn idea for TypeScriptAn idea for TypeScript
Activity
yortus commentedon Oct 5, 2016
This does seem to be a bug and a divergence from IteratorResult in the ECMAScript spec. The spec states the following about the
valueproperty:aluanhaddad commentedon Oct 5, 2016
There is a long chain of issues around this. It is a little confusing. See #8357 for some the motivation.
yortus commentedon Oct 5, 2016
Both #8357 and #8938 mention that with boolean literal types,
IteratorResultcan be properly fixed.We now have boolean literal types, but it seems it's still difficult to fix - see #2983 (comment).
TL;DR the fix for this is now held up on default generic type variables (#2175). Unless of course someone can come up with a better alternative.
BurtHarris commentedon Oct 5, 2016
To me the fix seems easy: switch the definition in
lib.es6.d.tsback to what it was in 1.8:Entangling this with an enhancement that tries to enforce complex conditions for the property's optional status seems overreaching. I don't see why it should be held up on default generic type variables.
yortus commentedon Oct 5, 2016
Burt Harris (@BurtHarris):
IteratorResultalso isn't accurate, because it incorrectly conflates the yield and return types.Igorbek commentedon Oct 5, 2016
Troy Gerwien (@yortus) I've just wrote an alternative #2983 (comment)
BurtHarris commentedon Oct 5, 2016
Thanks Troy Gerwien (@yortus) .
It seems to me like it's not the previous definition of
IteratorResultthat is conflating yield and return types, that seems like it has to be something intschandling offor..of.If I understand the background on #8357, that would only only comes about when using strict null checking. The change in
lib.es6.d.tsimpacts any users who upgrade to TypeScript 2.0, even if they are not using the new features.yortus commentedon Oct 5, 2016
Burt Harris (@BurtHarris) the problem in both the current and the previous
IteratorResultdeclarations is thatvalueis typed asT, but really must support two different types. That's becausevalueis the yielded value whendoneis false, and the returned value whendoneis true. These values aren't necessarily (or even usually) of the same type. For example:So the type of
valueisnumberwhendone==false, andstringwhendone==true. The current/previous definition can't capture that because it only has one type argumentT. A union of the two types wouldn't be helpful either.BurtHarris commentedon Oct 5, 2016
Yes, but the latest issue you raise sounds like a problem with generators, not necessarily a problem with iterators. And it certainly makes sense to me that the type associated with a generator could perhaps need to account for the difference between yield and return. However, the code I'm working toward isn't (and probably won't be) dependent on generators.
Now I'll be the first to admit I'm not an expert on generators or iterators, and that if I were designing these features I would have specified the protocol differently. But based on my reading of the iterator protocol from Mozilla.org, I inferred that in the value returned from a
next()method, bothdoneandvalueare optional, suggesting only one or the other need be provided. Personally, I think that part of the protocol makes sense, though it might be hard to model completely in terms of TypeScript. Perhaps that's why you are saying its dependent default generic type variables, is that right?But in terms of a practicing programmer (using a released tool) rather than a language designer, I would prefer that TypeScript 2 accept the old interface until the details of proper enforcement the evolved protocol can be worked out, or until I opt-in to enforcement of a more restrictive one.
Work around microsoft/TypeScript#11375
winterbe commentedon Aug 11, 2017
I agree with Burt Harris (@BurtHarris) that the interface
IteratorResultis very confusing. The protocol clearly states that anundefinedvalue is valid ifdone: true, but it's currently not covered by the interface. This scenario is no corner case. I'd say pretty much every custom iterator will run into this problem whenstrictNullChecksis enabled (which should be the case in every project if you ask me).31 remaining items
fatcerberus commentedon May 31, 2019
Not sure if this has been suggested but could we fix this by just defining
IteratorResultas a discriminated union, i.e.LinusU commentedon Jun 1, 2019
Bruce Pascoe (@fatcerberus) I believe that whendoneistrue,valueshould always beundefined:edit: see my later comment
fatcerberus commentedon Jun 1, 2019
No, that’s not correct:
LinusU commentedon Jun 2, 2019
Ah, of course, hahaha☺️
It kind of has to be two types though, the return value doesn't have to be the same as the yielded value:
relevant: #2983
KSXGitHub commentedon Jun 2, 2019
For this particular use case, I think union type should suffice.
LinusU commentedon Jun 2, 2019
Using a type union wouldn't accurately let me work with the iterator though:
This will incorrectly tell me that
numcan be astring...rbuckton commentedon Jun 2, 2019
This should be resolved by #30790
fatcerberus commentedon Jun 2, 2019
It turns out #30790 does pretty much exactly what I suggested (plus more!). Nice. 😃
knocte commentedon Jul 2, 2019
Why close this? #30790 is a PR, not an issue, and it hasn't been merged yet.
KSXGitHub commentedon Jul 2, 2019
Agreed.
Burt Harris (@BurtHarris) can you reopen this?
DanielRosenwasser commentedon Jul 8, 2019
Should be merged now.
BurtHarris commentedon Jul 14, 2019
Thank you all.