--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/AbstractIterator.java Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/test/script/basic/es6.js Wed Mar 23 21:45:59 2016 -0700
+++ b/nashorn/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/nashorn/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);
+