8151700: Add support for ES6 for-of

Reviewed-by: attila, sundar
This commit is contained in:
Hannes Wallnöfer 2016-03-24 11:43:48 +01:00
parent 666f0e49f6
commit d50a34f94f
12 changed files with 390 additions and 37 deletions

@ -803,7 +803,7 @@ final class AssignSymbols extends SimpleNodeVisitor implements Loggable {
@Override
public Node leaveForNode(final ForNode forNode) {
if (forNode.isForIn()) {
if (forNode.isForInOrOf()) {
return forNode.setIterator(lc, newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73
}

@ -1753,7 +1753,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
return false;
}
enterStatement(forNode);
if (forNode.isForIn()) {
if (forNode.isForInOrOf()) {
enterForIn(forNode);
} else {
final Expression init = forNode.getInit();
@ -1768,7 +1768,15 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
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 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
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);
}

@ -595,7 +595,7 @@ final class LocalVariableTypesCalculator extends SimpleNodeVisitor {
}
final Expression init = forNode.getInit();
if(forNode.isForIn()) {
if(forNode.isForInOrOf()) {
final JoinPredecessorExpression iterable = forNode.getModify();
visitExpression(iterable);
enterTestFirstLoop(forNode, null, init,

@ -254,12 +254,12 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
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);

@ -130,7 +130,7 @@ final class OptimisticTypesCalculator extends SimpleNodeVisitor {
@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 {

@ -51,8 +51,11 @@ public final class ForNode extends LoopNode {
/** 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 @@ public final class ForNode extends LoopNode {
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 @@ public final class ForNode extends LoopNode {
@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 final class ForNode extends LoopNode {
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 @@ public final class ForNode extends LoopNode {
* @return true if the containing block's scope object creator is required in codegen
*/
public boolean needsScopeCreator() {
return isForIn() && hasPerIterationScope();
return isForInOrOf() && hasPerIterationScope();
}
}

@ -110,6 +110,41 @@ public abstract class AbstractIterator extends ScriptObject {
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 @@ public abstract class AbstractIterator extends ScriptObject {
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 @@ public abstract class AbstractIterator extends ScriptObject {
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 {

@ -892,7 +892,7 @@ loop:
block();
break;
case VAR:
variableStatement(type, true);
variableStatement(type);
break;
case SEMICOLON:
emptyStatement();
@ -946,11 +946,11 @@ loop:
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 @@ loop:
}
}
/**
/*
* VariableStatement :
* var VariableDeclarationList ;
*
@ -1066,8 +1066,8 @@ loop:
* 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 @@ loop:
*
* Parse a FOR statement.
*/
@SuppressWarnings("fallthrough")
private void forStatement() {
final long forToken = token;
final int forLine = line;
@ -1235,6 +1236,7 @@ loop:
JoinPredecessorExpression modify = null;
int flags = 0;
boolean isForOf = false;
try {
// FOR tested in caller.
@ -1292,8 +1294,17 @@ loop:
}
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 @@ loop:
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:

@ -52,10 +52,12 @@ import jdk.nashorn.internal.codegen.ApplySpecialization;
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.
@ -102,6 +104,11 @@ public final class ScriptRuntime {
*/
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
@ -365,6 +372,77 @@ public final class ScriptRuntime {
return Collections.emptyIterator();
}
/**
* 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.

@ -52,8 +52,9 @@ parser.error.no.func.decl.here.warn=Function declarations should only occur at p
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}

@ -64,3 +64,8 @@ expectError('`text`', 'template literal', 'SyntaxError');
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');

@ -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);