Skip to content

Instantly share code, notes, and snippets.

@jugglinmike
Last active August 29, 2015 14:15
Show Gist options
  • Save jugglinmike/a07c2f58c81393f7984e to your computer and use it in GitHub Desktop.
Save jugglinmike/a07c2f58c81393f7984e to your computer and use it in GitHub Desktop.
Question on ES6 Iterables

I'm trying to understand the behavior of iterables in Firefox Nightly and V8. Specifically, I am looking at how the runtime is supposed to behave when the iterable's Symbol.iterator method returns a non-object value. Here are the relevant parts of the specification (as I understand it):

13.6.4.13 Runtime Semantics: ForIn/OfBodyEvaluation

The abstract operation ForIn/OfBodyEvaluation is called with arguments lhs, stmt, iterator, lhsKind, and labelSet. The value of lhsKind is either assignment, varBinding or lexicalBinding.

  1. Let oldEnv be the running execution context’s LexicalEnvironment.
  2. Let V = undefined .
  3. Let destructuring be IsDestructuring of lhs.
  4. If destructuring is true and if lhsKind is assignment, then
    1. Assert: lhs is a LeftHandSideExpression.
    2. Let assignmentPattern be the parse of the source code corresponding to lhs using AssignmentPattern as the goal symbol.
  5. Repeat
    1. If lhsKind is either assignment or varBinding, then
      1. If destructuring is false, then
        1. Let lhsRef be the result of evaluating lhs ( it may be evaluated repeatedly).
        2. ReturnIfAbrupt(lhsRef).
    2. Else
      1. Assert: lhsKind is lexicalBinding.
      2. Assert: lhs is a ForDeclaration.
      3. Let iterationEnv be NewDeclarativeEnvironment(oldEnv).
      4. Set the running execution context’s LexicalEnvironment to iterationEnv.
      5. Perform BindingInstantiation for lhs passing iterationEnv as the argument.
      6. If destructuring is false, then
        1. Assert: lhs binds a single name.
        2. Let lhsName be the sole element of BoundNames of lhs.
        3. Let lhsRef be ResolveBinding(the sole element of lhs).
        4. Assert: lhsRef is not an abrupt completion.
    3. Let nextResult be IteratorStep(iterator).
    4. If nextResult is an abrupt completion, then
      1. Set the running execution context’s LexicalEnvironment to oldEnv.
      2. Return nextResult.
    5. If nextResult is false, then
      1. Set the running execution context’s LexicalEnvironment to oldEnv.
      2. Return NormalCompletion(V).
    6. Let nextValue be IteratorValue(nextResult).
    7. If nextValue is an abrupt completion, then
      1. Set the running execution context’s LexicalEnvironment to oldEnv.
      2. Return nextValue.
    8. If destructuring is false, then
      1. If lhsKind is lexicalBinding, then
        1. Let status be InitializeReferencedBinding(lhsRef, nextValue).
      2. Else,
        1. Let status be PutValue(lhsRef, nextValue).
    9. Else,
      1. If lhsKind is assignment, then
        1. Let status be the result of performing DestructuringAssignmentEvaluation of assignmentPattern using nextValue as the argument.
      2. Else if lhsKind is varBinding, then
        1. Assert: lhs is a ForBinding.
        2. Let status be the result of performing BindingInitialization for lhs passing nextValue and undefined as the arguments.
      3. Else,
        1. Assert: lhsKind is lexicalBinding.
        2. Assert: lhs is a ForDeclaration.
        3. Let status be the result of performing BindingInitialization for lhs passing nextValue and iterationEnv as arguments.
    10. If status is an abrupt completion, then
      1. Set the running execution context’s LexicalEnvironment to oldEnv.
      2. Return IteratorClose(iterator, status).
    11. Let status be the result of evaluating stmt.
    12. If status.[[type]] is normal and status.[[value]] is not empty, then
      1. Let V = status.[[value]].
    13. Set the running execution context’s LexicalEnvironment to oldEnv.
    14. If LoopContinues(status,labelSet) is false, then
      1. Return IteratorClose(iterator, status).

7.4.5 IteratorStep ( iterator )

The abstract operation IteratorStep with argument iterator requests the next value from iterator and returns either false indicating that the iterator has reached its end or the IteratorResult object if a next value is available. IteratorStep performs the following steps:

  1. Let result be IteratorNext(iterator).
  2. ReturnIfAbrupt(result).
  3. Let done be IteratorComplete(result).
  4. ReturnIfAbrupt(done).
  5. If done is true, return false.
  6. Return result.

7.4.2 IteratorNext ( iterator, value )

The abstract operation IteratorNext with argument iterator and optional argument value performs the following steps:

  1. If value was not passed, then
    1. Let result be Invoke(iterator, "next", «‍ »).
  2. Else,
    1. Let result be Invoke(iterator, "next", «‍value»).
  3. ReturnIfAbrupt(result).
  4. If Type(result) is not Object, throw a TypeError exception.
  5. Return result.

As I read it, if the iterable's Symbol.iterator method returns a non-object value (e.g. the number literal 23), then I think a TypeError should be raised according to 7.4.2 (step 4). Both Firefox Nightly and V8 treat this value as though it were an object--value is interpreted as undefined and done is interpreted as false, so the following test passes:

var iterable = {};
var i, firstIterResult;

iterable[Symbol.iterator] = function() {
  var finalIterResult = { value: null, done: true };
  var nextIterResult = firstIterResult;

  return {
    next: function() {
      var iterResult = nextIterResult;

      nextIterResult = finalIterResult;

      return iterResult;
    }
  };
};

firstIterResult = 23;
i = 0;
for (var x of iterable) {
  assert.sameValue(x, undefined);
  i++;
}
assert.sameValue(i, 1);

but that seems wrong to me. I think any non-object value returned from an iterable's Symbol.iterator method should result in a TypeError during for..of iteration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment