--- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java 2016-03-18 16:54:28.949727387 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java 2016-03-18 16:54:28.813727392 +0100 @@ -803,7 +803,7 @@ @Override public Node leaveForNode(final ForNode forNode) { - if (forNode.isForIn()) { + if (forNode.isForIn() || forNode.isForOf()) { return forNode.setIterator(lc, newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73 } --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java 2016-03-18 16:54:29.545727364 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java 2016-03-18 16:54:29.405727370 +0100 @@ -1761,7 +1761,7 @@ return false; } enterStatement(forNode); - if (forNode.isForIn()) { + if (forNode.isForIn() || forNode.isForOf()) { enterForIn(forNode); } else { final Expression init = forNode.getInit(); @@ -1776,7 +1776,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); @@ -3326,7 +3334,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); } --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java 2016-03-18 16:54:30.057727345 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java 2016-03-18 16:54:29.933727350 +0100 @@ -595,7 +595,7 @@ } final Expression init = forNode.getInit(); - if(forNode.isForIn()) { + if(forNode.isForIn() || forNode.isForOf()) { final JoinPredecessorExpression iterable = forNode.getModify(); visitExpression(iterable); enterTestFirstLoop(forNode, null, init, --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java 2016-03-18 16:54:30.581727325 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java 2016-03-18 16:54:30.445727330 +0100 @@ -254,12 +254,12 @@ ForNode newForNode = forNode; final Expression test = forNode.getTest(); - if (!forNode.isForIn() && isAlwaysTrue(test)) { + if (!forNode.isForIn() && !forNode.isForOf() && isAlwaysTrue(test)) { newForNode = forNode.setTest(lc, null); } newForNode = checkEscape(newForNode); - if(!es6 && newForNode.isForIn()) { + if(!es6 && (newForNode.isForIn() || newForNode.isForOf())) { // 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); --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java 2016-03-18 16:54:30.969727311 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java 2016-03-18 16:54:30.857727315 +0100 @@ -130,7 +130,7 @@ @Override public boolean enterForNode(final ForNode forNode) { - if(forNode.isForIn()) { + if(forNode.isForIn() || forNode.isForOf()) { // for..in has the iterable in its "modify" tagNeverOptimistic(forNode.getModify()); } else { --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java 2016-03-18 16:54:31.525727290 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java 2016-03-18 16:54:31.381727295 +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 !isForIn() && !isForOf() && test == null; } @Override public boolean mustEnter() { - if (isForIn()) { + if (isForIn() || isForOf()) { return false; //may be an empty set to iterate over, then we skip the loop } return test == null; @@ -185,6 +192,15 @@ 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 each construct, known from e.g. Rhino. This will be a for of construct * in ECMAScript 6 @@ -282,6 +298,6 @@ * @return true if the containing block's scope object creator is required in codegen */ public boolean needsScopeCreator() { - return isForIn() && hasPerIterationScope(); + return (isForIn() || isForOf()) && hasPerIterationScope(); } } --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/AbstractIterator.java 2016-03-18 16:54:31.993727272 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/AbstractIterator.java 2016-03-18 16:54:31.865727277 +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 { --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java 2016-03-18 16:54:32.545727251 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java 2016-03-18 16:54:32.397727257 +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 variableStatement(final TokenType varType, final boolean isStatement) { - return variableStatement(varType, isStatement, -1); + private List variableStatement(final TokenType varType) { + return variableStatement(varType, true, -1); } private List 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: --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java 2016-03-18 16:54:33.065727231 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java 2016-03-18 16:54:32.949727236 +0100 @@ -52,10 +52,13 @@ 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; +import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; /** * Utilities to be called by JavaScript runtime API and generated classes. @@ -103,6 +106,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 +374,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() { + + 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 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 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 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. * --- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties 2016-03-18 16:54:33.661727209 +0100 +++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties 2016-03-18 16:54:33.529727214 +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} --- old/test/script/basic/es6.js 2016-03-18 16:54:34.233727187 +0100 +++ new/test/script/basic/es6.js 2016-03-18 16:54:34.093727193 +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 2016-03-15 08:23:41.510769431 +0100 +++ new/test/script/basic/es6/for-of.js 2016-03-18 16:54:34.609727173 +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); +