archives

« Bugzilla Issues Index

#4334 — Abrupt completion discarded in native collection constructors


The constructors for Map, Set, WeakMap, and WeakSet all accept an optional
`iterable` parameter. This is used to create an iterator, and each of the
iterator's values is inserted into the collection's internal data structure
using the appropriate instance method (`add` in the case of Set and WeakSet,
`set` in the case of Map and WeakMap), aliased as `adder`.

All implementations have a provision for behavior when the `adder` returns an
abrupt completion: they close the iterator and return the result of that
operation. For example, from Set [1]:

> 23.2.1.1 Set ( [ iterable ] )
> ...
> 6. If iterable is either undefined or null, let iter be undefined.
> 7. Else,
> a. Let adder be Get(set, "add").
> b. ReturnIfAbrupt(adder).
> c. If IsCallable(adder) is false, throw a TypeError exception.
> d. Let iter be GetIterator(iterable).
> e. ReturnIfAbrupt(iter).
> 8. If iter is undefined, return set.
> 9. Repeat
> ...
> f. Let status be Call(adder, set, «nextValue.[[value]]»).
> g. If status is an abrupt completion, return IteratorClose(iter, status).

This sequence means if `Set.prototype.add` throws an error, the constructor
invocation may execute without throwing. In code:

var s;
Set.prototype.add = function() { throw new Error(); };
s = new Set([1]); // No error thrown here
s.size === 0; // true

It seems wise to close the iterator regardless of "status", but I think that if
status is an abrupt completion, this should be forwarded.

> 9. Repeat
> ...
> f. Let status be Call(adder, set, «nextValue.[[value]]»).
> g. If status is an abrupt completion, then
> i. IteratorClose(iter, status).
> ii. return status

This pattern demonstrates the same behavior (an abrupt completion from
IteratorClose is ignored), but it only does so in cases where an abrupt
completion is already positioned to be returned.


(In reply to mike from comment #0)
...
> 9. Repeat
> ...
> f. Let status be Call(adder, set, «nextValue.[[value]]»).
> g. If status is an abrupt completion, return IteratorClose(iter, status).
...
>
> var s;
> Set.prototype.add = function() { throw new Error(); };
> s = new Set([1]); // No error thrown here
> s.size === 0; // true
>
> It seems wise to close the iterator regardless of "status", but I think that
> if
> status is an abrupt completion, this should be forwarded.

No, that's not the iterator protocol WRT 'return'. 'Return' is only called if a iterator driven loop terminates early. It is not called on iterator exhaustion. See http://people.mozilla.org/~jorendorff/es6-draft.html#table-54 and also the for-of evaluation semantics.

If status is an abrupt completion, it will indeed normally the the value that is returned in 9.g, because:

>
> > 9. Repeat
> > ...
> > f. Let status be Call(adder, set, «nextValue.[[value]]»).
> > g. If status is an abrupt completion, then
> > i. IteratorClose(iter, status).
> > ii. return status

return IteratorClose(iter, status)

essentially does the same as your steps g.i-ii above. See http://people.mozilla.org/~jorendorff/es6-draft.html#sec-iteratorclose

If iter does not have a 'return' method, IteratorClose returns status. It also returns status if the 'return' method is called, regardless of whether 'return' has a normal or abrupt completion. The only cases where it doesn't return status is if an internal protocol violation is detected.

>
> This pattern demonstrates the same behavior (an abrupt completion from
> IteratorClose is ignored), but it only does so in cases where an abrupt
> completion is already positioned to be returned.


(In reply to Allen Wirfs-Brock from comment #1)
> (In reply to mike from comment #0)
> > > 9. Repeat
> > > ...
> > > f. Let status be Call(adder, set, «nextValue.[[value]]»).
> > > g. If status is an abrupt completion, then
> > > i. IteratorClose(iter, status).
> > > ii. return status
>
> return IteratorClose(iter, status)
>
> essentially does the same as your steps g.i-ii above. See
> http://people.mozilla.org/~jorendorff/es6-draft.html#sec-iteratorclose
>
> If iter does not have a 'return' method, IteratorClose returns status. It
> also returns status if the 'return' method is called, regardless of whether
> 'return' has a normal or abrupt completion. The only cases where it doesn't
> return status is if an internal protocol violation is detected.

It's not the behavior of the `return` method that I am concerned about. It is
when the "adder" function (Set.prototype.add in the running example) itself
throws an error. Under these conditions, the abrupt completion is referenced
to decide whether IteratorClose should be called. But after that, it is
discarded--the constructor returns the completion of IteratorClose instead.

This means that it is possible for a normal completion to be returned from the
constructor even when Call(adder, set, «nextValue.[[value]]») returns an
abrupt completion.


(In reply to mike from comment #2)

>
> It's not the behavior of the `return` method that I am concerned about. It is
> when the "adder" function (Set.prototype.add in the running example) itself
> throws an error. Under these conditions, the abrupt completion is referenced
> to decide whether IteratorClose should be called. But after that, it is
> discarded--the constructor returns the completion of IteratorClose instead.

Nope, the abnormal completion from the add call ('status') is passed as the second argument into IteratorClose and that completion record is what is returned usually returned as the conpletion value of IteratorClose and then returned as the completion value of the Set function.

>
> This means that it is possible for a normal completion to be returned from
> the
> constructor even when Call(adder, set, «nextValue.[[value]]») returns an
> abrupt completion.

Nope, see http://people.mozilla.org/~jorendorff/es6-draft.html#sec-iteratorclose .

If the value passed ;as the second argument to IteratorClose is an abrupt completion (and it always is) there is no path through IteratorClose that returns a normal completion.

What about the expression of IteratorClose or the Set/etc. algorithms mades this difficult for you to see? What would make it clearer for you? Would it be clearer if step g said:
g. If status is an abrupt completion, return Completion(IteratorClose(iter, status)).


(In reply to Allen Wirfs-Brock from comment #3)
> Nope, the abnormal completion from the add call ('status') is passed as the
> second argument into IteratorClose and that completion record is what is
> returned usually returned as the conpletion value of IteratorClose and then
> returned as the completion value of the Set function.

Ah, now I understand.

> What about the expression of IteratorClose or the Set/etc. algorithms mades
> this difficult for you to see? What would make it clearer for you? Would it
> be clearer if step g said:
> g. If status is an abrupt completion, return
> Completion(IteratorClose(iter, status)).

Thanks for offering an alternative, but I don't think that version would
have helped me in this case. It was/is very clear to me that the completion
from IteratorClose is being returned in 9.g. The piece I was missing was how
IteratorClose accepts status and returns it again. I don't have any
suggestions on how to make this detail clearer; in this case, the problem
comes down to a careless reading. I appreciate your patience (as always) and
apologize for the noise!