changeset 1655:3ac5d360070e

8151700: Add support for ES6 for-of Reviewed-by: attila, sundar
author hannesw
date Thu, 24 Mar 2016 11:43:48 +0100
parents cdacfe806770
children c261f8440c55
files src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/AbstractIterator.java src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties test/script/basic/es6.js test/script/basic/es6/for-of.js
diffstat 12 files changed, 390 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java	Wed Mar 23 21:45:59 2016 -0700
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java	Thu Mar 24 11:43:48 2016 +0100
@@ -803,7 +803,7 @@
 
     @Override
     public Node leaveForNode(final ForNode forNode) {
-        if (forNode.isForIn()) {
+        if (forNode.isForInOrOf()) {
             return forNode.setIterator(lc, newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73
         }
 
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java	Wed Mar 23 21:45:59 2016 -0700
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java	Thu Mar 24 11:43:48 2016 +0100
@@ -1753,7 +1753,7 @@
             return false;
         }
         enterStatement(forNode);
-        if (forNode.isForIn()) {
+        if (forNode.isForInOrOf()) {
             enterForIn(forNode);
         } else {
             final Expression init = forNode.getInit();
@@ -1768,7 +1768,15 @@
 
     private void enterForIn(final ForNode forNode) {
         loadExpression(forNode.getModify(), TypeBounds.OBJECT);
-        method.invoke(forNode.isForEach() ? ScriptRuntime.TO_VALUE_ITERATOR : ScriptRuntime.TO_PROPERTY_ITERATOR);
+        if (forNode.isForEach()) {
+            method.invoke(ScriptRuntime.TO_VALUE_ITERATOR);
+        } else if (forNode.isForIn()) {
+            method.invoke(ScriptRuntime.TO_PROPERTY_ITERATOR);
+        } else if (forNode.isForOf()) {
+            method.invoke(ScriptRuntime.TO_ES6_ITERATOR);
+        } else {
+            throw new IllegalArgumentException("Unexpected for node");
+        }
         final Symbol iterSymbol = forNode.getIterator();
         final int iterSlot = iterSymbol.getSlot(Type.OBJECT);
         method.store(iterSymbol, ITERATOR_TYPE);
@@ -3318,7 +3326,7 @@
             if (needsScope && varNode.isLet()) {
                 method.loadCompilerConstant(SCOPE);
                 method.loadUndefined(Type.OBJECT);
-                final int flags = getScopeCallSiteFlags(identSymbol) | (varNode.isBlockScoped() ? CALLSITE_DECLARE : 0);
+                final int flags = getScopeCallSiteFlags(identSymbol) | CALLSITE_DECLARE;
                 assert isFastScope(identSymbol);
                 storeFastScopeVar(identSymbol, flags);
             }
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Wed Mar 23 21:45:59 2016 -0700
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Thu Mar 24 11:43:48 2016 +0100
@@ -595,7 +595,7 @@
         }
 
         final Expression init = forNode.getInit();
-        if(forNode.isForIn()) {
+        if(forNode.isForInOrOf()) {
             final JoinPredecessorExpression iterable = forNode.getModify();
             visitExpression(iterable);
             enterTestFirstLoop(forNode, null, init,
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java	Wed Mar 23 21:45:59 2016 -0700
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java	Thu Mar 24 11:43:48 2016 +0100
@@ -254,12 +254,12 @@
         ForNode newForNode = forNode;
 
         final Expression test = forNode.getTest();
-        if (!forNode.isForIn() && isAlwaysTrue(test)) {
+        if (!forNode.isForInOrOf() && isAlwaysTrue(test)) {
             newForNode = forNode.setTest(lc, null);
         }
 
         newForNode = checkEscape(newForNode);
-        if(!es6 && newForNode.isForIn()) {
+        if(!es6 && newForNode.isForInOrOf()) {
             // Wrap it in a block so its internally created iterator is restricted in scope, unless we are running
             // in ES6 mode, in which case the parser already created a block to capture let/const declarations.
             addStatementEnclosedInBlock(newForNode);
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java	Wed Mar 23 21:45:59 2016 -0700
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java	Thu Mar 24 11:43:48 2016 +0100
@@ -130,7 +130,7 @@
 
     @Override
     public boolean enterForNode(final ForNode forNode) {
-        if(forNode.isForIn()) {
+        if(forNode.isForInOrOf()) {
             // for..in has the iterable in its "modify"
             tagNeverOptimistic(forNode.getModify());
         } else {
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java	Wed Mar 23 21:45:59 2016 -0700
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java	Thu Mar 24 11:43:48 2016 +0100
@@ -51,8 +51,11 @@
     /** Is this a normal for each in loop? */
     public static final int IS_FOR_EACH         = 1 << 1;
 
+    /** Is this a ES6 for-of loop? */
+    public static final int IS_FOR_OF           = 1 << 2;
+
     /** Does this loop need a per-iteration scope because its init contain a LET declaration? */
-    public static final int PER_ITERATION_SCOPE = 1 << 2;
+    public static final int PER_ITERATION_SCOPE = 1 << 3;
 
     private final int flags;
 
@@ -127,6 +130,10 @@
             init.toString(sb, printTypes);
             sb.append(" in ");
             modify.toString(sb, printTypes);
+        } else if (isForOf()) {
+            init.toString(sb, printTypes);
+            sb.append(" of ");
+            modify.toString(sb, printTypes);
         } else {
             if (init != null) {
                 init.toString(sb, printTypes);
@@ -146,12 +153,12 @@
 
     @Override
     public boolean hasGoto() {
-        return !isForIn() && test == null;
+        return !isForInOrOf() && test == null;
     }
 
     @Override
     public boolean mustEnter() {
-        if (isForIn()) {
+        if (isForInOrOf()) {
             return false; //may be an empty set to iterate over, then we skip the loop
         }
         return test == null;
@@ -185,6 +192,23 @@
     public boolean isForIn() {
         return (flags & IS_FOR_IN) != 0;
     }
+
+    /**
+     * Is this a for-of loop?
+     * @return true if this is a for-of loop
+     */
+    public boolean isForOf() {
+        return (flags & IS_FOR_OF) != 0;
+    }
+
+    /**
+     * Is this a for-in or for-of statement?
+     * @return true if this is a for-in or for-of loop
+     */
+    public boolean isForInOrOf() {
+        return isForIn() || isForOf();
+    }
+
     /**
      * Is this a for each construct, known from e.g. Rhino. This will be a for of construct
      * in ECMAScript 6
@@ -283,6 +307,6 @@
      * @return true if the containing block's scope object creator is required in codegen
      */
     public boolean needsScopeCreator() {
-        return isForIn() && hasPerIterationScope();
+        return isForInOrOf() && hasPerIterationScope();
     }
 }
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/AbstractIterator.java	Wed Mar 23 21:45:59 2016 -0700
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/AbstractIterator.java	Thu Mar 24 11:43:48 2016 +0100
@@ -110,6 +110,41 @@
         return new IteratorResult(value, done, global);
     }
 
+    static MethodHandle getIteratorInvoker(final Global global) {
+        return global.getDynamicInvoker(ITERATOR_INVOKER_KEY,
+                () -> Bootstrap.createDynamicCallInvoker(Object.class, Object.class, Object.class));
+    }
+
+    /**
+     * Get the invoker for the ES6 iterator {@code next} method.
+     * @param global the global object
+     * @return the next invoker
+     */
+    public static InvokeByName getNextInvoker(final Global global) {
+        return global.getInvokeByName(AbstractIterator.NEXT_INVOKER_KEY,
+                () -> new InvokeByName("next", Object.class, Object.class, Object.class));
+    }
+
+    /**
+     * Get the invoker for the ES6 iterator result {@code done} property.
+     * @param global the global object
+     * @return the done invoker
+     */
+    public static MethodHandle getDoneInvoker(final Global global) {
+        return global.getDynamicInvoker(AbstractIterator.DONE_INVOKER_KEY,
+                () -> Bootstrap.createDynamicInvoker("done", NashornCallSiteDescriptor.GET_PROPERTY, Object.class, Object.class));
+    }
+
+    /**
+     * Get the invoker for the ES6 iterator result {@code value} property.
+     * @param global the global object
+     * @return the value invoker
+     */
+    public static MethodHandle getValueInvoker(final Global global) {
+        return global.getDynamicInvoker(AbstractIterator.VALUE_INVOKER_KEY,
+                () -> Bootstrap.createDynamicInvoker("value", NashornCallSiteDescriptor.GET_PROPERTY, Object.class, Object.class));
+    }
+
     /**
      * ES6 7.4.1 GetIterator abstract operation
      *
@@ -126,8 +161,7 @@
 
             if (Bootstrap.isCallable(getter)) {
                 try {
-                    final MethodHandle invoker = global.getDynamicInvoker(ITERATOR_INVOKER_KEY,
-                            () -> Bootstrap.createDynamicCallInvoker(Object.class, Object.class, Object.class));
+                    final MethodHandle invoker = getIteratorInvoker(global);
 
                     final Object value = invoker.invokeExact(getter, iterable);
                     if (JSType.isPrimitive(value)) {
@@ -156,12 +190,9 @@
 
         final Object iterator = AbstractIterator.getIterator(Global.toObject(iterable), global);
 
-        final InvokeByName nextInvoker = global.getInvokeByName(AbstractIterator.NEXT_INVOKER_KEY,
-                () -> new InvokeByName("next", Object.class, Object.class, Object.class));
-        final MethodHandle doneInvoker = global.getDynamicInvoker(AbstractIterator.DONE_INVOKER_KEY,
-                () -> Bootstrap.createDynamicInvoker("done", NashornCallSiteDescriptor.GET_PROPERTY, Object.class, Object.class));
-        final MethodHandle valueInvoker = global.getDynamicInvoker(AbstractIterator.VALUE_INVOKER_KEY,
-                () -> Bootstrap.createDynamicInvoker("value", NashornCallSiteDescriptor.GET_PROPERTY, Object.class, Object.class));
+        final InvokeByName nextInvoker = getNextInvoker(global);
+        final MethodHandle doneInvoker = getDoneInvoker(global);
+        final MethodHandle valueInvoker = getValueInvoker(global);
 
         try {
             do {
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java	Wed Mar 23 21:45:59 2016 -0700
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java	Thu Mar 24 11:43:48 2016 +0100
@@ -892,7 +892,7 @@
             block();
             break;
         case VAR:
-            variableStatement(type, true);
+            variableStatement(type);
             break;
         case SEMICOLON:
             emptyStatement();
@@ -946,11 +946,11 @@
                 if (singleStatement) {
                     throw error(AbstractParser.message("expected.stmt", type.getName() + " declaration"), token);
                 }
-                variableStatement(type, true);
+                variableStatement(type);
                 break;
             }
             if (env._const_as_var && type == CONST) {
-                variableStatement(TokenType.VAR, true);
+                variableStatement(TokenType.VAR);
                 break;
             }
 
@@ -1047,7 +1047,7 @@
         }
     }
 
-    /**
+    /*
      * VariableStatement :
      *      var VariableDeclarationList ;
      *
@@ -1066,8 +1066,8 @@
      * Parse a VAR statement.
      * @param isStatement True if a statement (not used in a FOR.)
      */
-    private List<VarNode> variableStatement(final TokenType varType, final boolean isStatement) {
-        return variableStatement(varType, isStatement, -1);
+    private List<VarNode> variableStatement(final TokenType varType) {
+        return variableStatement(varType, true, -1);
     }
 
     private List<VarNode> variableStatement(final TokenType varType, final boolean isStatement, final int sourceOrder) {
@@ -1215,6 +1215,7 @@
      *
      * Parse a FOR statement.
      */
+    @SuppressWarnings("fallthrough")
     private void forStatement() {
         final long forToken = token;
         final int forLine = line;
@@ -1235,6 +1236,7 @@
         JoinPredecessorExpression modify = null;
 
         int flags = 0;
+        boolean isForOf = false;
 
         try {
             // FOR tested in caller.
@@ -1292,8 +1294,17 @@
                 }
                 break;
 
+            case IDENT:
+                if (env._es6 && "of".equals(getValue())) {
+                    isForOf = true;
+                    // fall through
+                } else {
+                    expect(SEMICOLON); // fail with expected message
+                    break;
+                }
             case IN:
-                flags |= ForNode.IS_FOR_IN;
+
+                flags |= isForOf ? ForNode.IS_FOR_OF : ForNode.IS_FOR_IN;
                 test = new JoinPredecessorExpression();
                 if (vars != null) {
                     // for (var i in obj)
@@ -1301,32 +1312,31 @@
                         init = new IdentNode(vars.get(0).getName());
                     } else {
                         // for (var i, j in obj) is invalid
-                        throw error(AbstractParser.message("many.vars.in.for.in.loop"), vars.get(1).getToken());
+                        throw error(AbstractParser.message("many.vars.in.for.in.loop", isForOf ? "of" : "in"), vars.get(1).getToken());
                     }
-
                 } else {
                     // for (expr in obj)
-                    assert init != null : "for..in init expression can not be null here";
+                    assert init != null : "for..in/of init expression can not be null here";
 
                     // check if initial expression is a valid L-value
                     if (!(init instanceof AccessNode ||
                           init instanceof IndexNode ||
                           init instanceof IdentNode)) {
-                        throw error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken());
+                        throw error(AbstractParser.message("not.lvalue.for.in.loop", isForOf ? "of" : "in"), init.getToken());
                     }
 
                     if (init instanceof IdentNode) {
                         if (!checkIdentLValue((IdentNode)init)) {
-                            throw error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken());
+                            throw error(AbstractParser.message("not.lvalue.for.in.loop", isForOf ? "of" : "in"), init.getToken());
                         }
-                        verifyStrictIdent((IdentNode)init, "for-in iterator");
+                        verifyStrictIdent((IdentNode)init, isForOf ? "for-of iterator" : "for-in iterator");
                     }
                 }
 
                 next();
 
-                // Get the collection expression.
-                modify = joinPredecessorExpression();
+                // For-of only allows AssignmentExpression.
+                modify = isForOf ? new JoinPredecessorExpression(assignmentExpression(false)) : joinPredecessorExpression();
                 break;
 
             default:
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java	Wed Mar 23 21:45:59 2016 -0700
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java	Thu Mar 24 11:43:48 2016 +0100
@@ -52,10 +52,12 @@
 import jdk.nashorn.internal.codegen.CompilerConstants;
 import jdk.nashorn.internal.codegen.CompilerConstants.Call;
 import jdk.nashorn.internal.ir.debug.JSONWriter;
+import jdk.nashorn.internal.objects.AbstractIterator;
 import jdk.nashorn.internal.objects.Global;
 import jdk.nashorn.internal.objects.NativeObject;
 import jdk.nashorn.internal.parser.Lexer;
 import jdk.nashorn.internal.runtime.linker.Bootstrap;
+import jdk.nashorn.internal.runtime.linker.InvokeByName;
 
 /**
  * Utilities to be called by JavaScript runtime API and generated classes.
@@ -103,6 +105,11 @@
     public static final Call TO_VALUE_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toValueIterator", Iterator.class, Object.class);
 
     /**
+     * Return an appropriate iterator for the elements in a ES6 for-of loop
+     */
+    public static final Call TO_ES6_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toES6Iterator", Iterator.class, Object.class);
+
+    /**
       * Method handle for apply. Used from {@link ScriptFunction} for looking up calls to
       * call sites that are known to be megamorphic. Using an invoke dynamic here would
       * lead to the JVM deoptimizing itself to death
@@ -366,6 +373,77 @@
     }
 
     /**
+     * Returns an iterator over property values used in the {@code for ... of} statement. The iterator uses the
+     * Iterator interface defined in version 6 of the ECMAScript specification.
+     *
+     * @param obj object to iterate on.
+     * @return iterator based on the ECMA 6 Iterator interface.
+     */
+    public static Iterator<?> toES6Iterator(final Object obj) {
+        final Global global = Global.instance();
+        final Object iterator = AbstractIterator.getIterator(Global.toObject(obj), global);
+
+        final InvokeByName nextInvoker = AbstractIterator.getNextInvoker(global);
+        final MethodHandle doneInvoker = AbstractIterator.getDoneInvoker(global);
+        final MethodHandle valueInvoker = AbstractIterator.getValueInvoker(global);
+
+        return new Iterator<Object>() {
+
+            private Object nextResult = nextResult();
+
+            private Object nextResult() {
+                try {
+                    final Object next = nextInvoker.getGetter().invokeExact(iterator);
+                    if (Bootstrap.isCallable(next)) {
+                        return nextInvoker.getInvoker().invokeExact(next, iterator, (Object) null);
+                    }
+                } catch (final RuntimeException|Error r) {
+                    throw r;
+                } catch (final Throwable t) {
+                    throw new RuntimeException(t);
+                }
+                return null;
+            }
+
+            @Override
+            public boolean hasNext() {
+                if (nextResult == null) {
+                    return false;
+                }
+                try {
+                    final Object done = doneInvoker.invokeExact(nextResult);
+                    return !JSType.toBoolean(done);
+                } catch (final RuntimeException|Error r) {
+                    throw r;
+                } catch (final Throwable t) {
+                    throw new RuntimeException(t);
+                }
+            }
+
+            @Override
+            public Object next() {
+                if (nextResult == null) {
+                    return Undefined.getUndefined();
+                }
+                try {
+                    final Object result = nextResult;
+                    nextResult = nextResult();
+                    return valueInvoker.invokeExact(result);
+                } catch (final RuntimeException|Error r) {
+                    throw r;
+                } catch (final Throwable t) {
+                    throw new RuntimeException(t);
+                }
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException("remove");
+            }
+        };
+    }
+
+    /**
      * Merge a scope into its prototype's map.
      * Merge a scope into its prototype.
      *
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties	Wed Mar 23 21:45:59 2016 -0700
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties	Thu Mar 24 11:43:48 2016 +0100
@@ -52,8 +52,9 @@
 parser.error.property.redefinition=Property "{0}" already defined
 parser.error.unexpected.token=Unexpected token: {0}
 parser.error.for.each.without.in=for each can only be used with for..in
-parser.error.many.vars.in.for.in.loop=Only one variable allowed in for..in loop
-parser.error.not.lvalue.for.in.loop=Invalid left side value of for..in loop
+parser.error.many.vars.in.for.in.loop=Only one variable allowed in for..{0} loop
+parser.error.not.lvalue.for.in.loop=Invalid left side value of for..{0} loop
+parser.error.for.in.loop.initializer=for..{0] loop declaration must not have an initializer
 parser.error.missing.catch.or.finally=Missing catch or finally after try
 parser.error.regex.unsupported.flag=Unsupported RegExp flag: {0}
 parser.error.regex.repeated.flag=Repeated RegExp flag: {0}
--- a/test/script/basic/es6.js	Wed Mar 23 21:45:59 2016 -0700
+++ b/test/script/basic/es6.js	Thu Mar 24 11:43:48 2016 +0100
@@ -64,3 +64,8 @@
 expectError('`${ x }`', 'template literal', 'SyntaxError');
 expectError('`text ${ x } text`', 'template literal', 'SyntaxError');
 expectError('f`text`', 'template literal', 'SyntaxError');
+expectError('for (a of [1, 2, 3]) print(a)', 'for-of', 'SyntaxError');
+expectError('for (var a of [1, 2, 3]) print(a)', 'for-of', 'SyntaxError');
+expectError('for (let a of [1, 2, 3]) print(a)', 'for-of', 'SyntaxError');
+expectError('for (const a of [1, 2, 3]) print(a)', 'for-of', 'SyntaxError');
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script/basic/es6/for-of.js	Thu Mar 24 11:43:48 2016 +0100
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * 
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ * 
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ * 
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8151700: Add support for ES6 for-of
+ *
+ * @test
+ * @run
+ * @option --language=es6
+ */
+
+let result = "";
+for (let a of [1, 2, "foo"]) {
+    result += a;
+}
+
+if (result !== "12foo") {
+    throw new Error("unexpcected result: " + result);
+}
+
+let sum = 0;
+let numbers = [1, 2, 3, 4];
+numbers.ten = 10; // not iterated over
+
+for (let n of numbers) {
+    sum += n;
+}
+
+if (sum !== 10) {
+    throw new Error("unexpected sum: " + sum);;
+}
+
+if (typeof n !== "undefined") {
+    throw new Error("n is visible outside of for-of");
+}
+
+let message = "Hello";
+result = "";
+
+for(const c of message) {
+    result += c;
+}
+
+if (result !== "Hello") {
+    throw new Error("unexpected result: " + result);
+}
+
+if (typeof c !== "undefined") {
+    throw new Error("c is visible outside of for-of")
+}
+
+// Callbacks with per-iteration scope
+
+result = "";
+let funcs = [];
+
+for (let a of [1, 2, "foo"]) {
+    funcs.push(function() { result += a; });
+}
+
+funcs.forEach(function(f) { f(); });
+if (result !== "12foo") {
+    throw new Error("unexpcected result: " + result);
+}
+
+result = "";
+funcs = [];
+
+for (const a of [1, 2, "foo"]) {
+    funcs.push(function() { result += a; });
+}
+
+funcs.forEach(function(f) { f(); });
+if (result !== "12foo") {
+    throw new Error("unexpcected result: " + result);
+}
+
+// Set
+var set = new Set(["foo", "bar", "foo"]);
+result = "";
+
+for (var w of set) {
+    result += w;
+}
+
+if (result !== "foobar") {
+    throw new Error("unexpected result: " + result);
+}
+
+// Maps
+var map = new Map([["a", 1], ["b", 2]]);
+result = "";
+
+for (var entry of map) {
+    result += entry;
+}
+
+if (result !== "a,1b,2") {
+    throw new Error("unexpected result: " + result);
+}
+
+// per-iteration scope
+
+let array = ["a", "b", "c"];
+funcs = [];
+
+for (let i of array) {
+    for (let j of array) {
+        for (let k of array) {
+            funcs.push(function () {
+                return i + j + k;
+            });
+        }
+    }
+}
+
+Assert.assertEquals(funcs.length, 3 * 3 * 3);
+let count = 0;
+
+for (let i = 0; i < 3; i++) {
+    for (let j = 0; j < 3; j++) {
+        for (let k = 0; k < 3; k++) {
+            Assert.assertEquals(funcs[count++](), array[i] + array[j] + array[k]);
+        }
+    }
+}
+
+// per-iteration scope with const declaration
+
+funcs = [];
+
+for (const i of array) {
+    for (const j of array) {
+        for (const k of array) {
+            funcs.push(function () {
+                return i + j + k;
+            });
+        }
+    }
+}
+
+Assert.assertEquals(funcs.length, 3 * 3 * 3);
+count = 0;
+
+for (let i = 0; i < 3; i++) {
+    for (let j = 0; j < 3; j++) {
+        for (let k = 0; k < 3; k++) {
+            Assert.assertEquals(funcs[count++](), array[i] + array[j] + array[k]);
+        }
+    }
+}
+
+// fibonacci iterator
+
+let fibonacci = {};
+
+fibonacci[Symbol.iterator] = function() {
+    let previous = 0, current = 1;
+    return {
+        next: function() {
+            let tmp = current;
+            current = previous + current;
+            previous = tmp;
+            return { done: false, value: current };
+        }
+    }
+};
+
+for (f of fibonacci) {
+    if (f > 100000) {
+        break;
+    }
+}
+
+Assert.assertTrue(f === 121393);
+