--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LiteralNode.java Thu Oct 06 18:05:55 2016 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LiteralNode.java Fri Oct 07 10:30:14 2016 +0200
@@ -635,6 +635,12 @@
/** Ranges for splitting up large literals in code generation */
private final List<Splittable.SplitRange> splitRanges;
+ /** Does this array literal have a spread element? */
+ private final boolean hasSpread;
+
+ /** Does this array literal have a trailing comma?*/
+ private final boolean hasTrailingComma;
+
@Override
public boolean isArray() {
return true;
@@ -791,11 +797,26 @@
* @param value array literal value, a Node array
*/
protected ArrayLiteralNode(final long token, final int finish, final Expression[] value) {
+ this(token, finish, value, false, false);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param token token
+ * @param finish finish
+ * @param value array literal value, a Node array
+ * @param hasSpread true if the array has a spread element
+ * @param hasTrailingComma true if the array literal has a comma after the last element
+ */
+ protected ArrayLiteralNode(final long token, final int finish, final Expression[] value, final boolean hasSpread, final boolean hasTrailingComma) {
super(Token.recast(token, TokenType.ARRAY), finish, value);
this.elementType = Type.UNKNOWN;
this.presets = null;
this.postsets = null;
this.splitRanges = null;
+ this.hasSpread = hasSpread;
+ this.hasTrailingComma = hasTrailingComma;
}
/**
@@ -808,6 +829,24 @@
this.postsets = postsets;
this.presets = presets;
this.splitRanges = splitRanges;
+ this.hasSpread = node.hasSpread;
+ this.hasTrailingComma = node.hasTrailingComma;
+ }
+
+ /**
+ * Returns {@code true} if this array literal has a spread element.
+ * @return true if this literal has a spread element
+ */
+ public boolean hasSpread() {
+ return hasSpread;
+ }
+
+ /**
+ * Returns {@code true} if this array literal has a trailing comma.
+ * @return true if this literal has a trailing comma
+ */
+ public boolean hasTrailingComma() {
+ return hasTrailingComma;
}
/**
@@ -989,6 +1028,23 @@
return new ArrayLiteralNode(parent.getToken(), parent.getFinish(), valueToArray(value));
}
+ /*
+ * Create a new array literal of Nodes from a list of Node values
+ *
+ * @param token token
+ * @param finish finish
+ * @param value literal value list
+ * @param hasSpread true if the array has a spread element
+ * @param hasTrailingComma true if the array literal has a comma after the last element
+ *
+ * @return the new literal node
+ */
+ public static LiteralNode<Expression[]> newInstance(final long token, final int finish, final List<Expression> value,
+ final boolean hasSpread, final boolean hasTrailingComma) {
+ return new ArrayLiteralNode(token, finish, valueToArray(value), hasSpread, hasTrailingComma);
+ }
+
+
/**
* Create a new array literal of Nodes
*
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java Thu Oct 06 18:05:55 2016 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java Fri Oct 07 10:30:14 2016 +0200
@@ -755,44 +755,11 @@
private void verifyDestructuringAssignmentPattern(final Expression pattern, final String contextString) {
assert pattern instanceof ObjectNode || pattern instanceof LiteralNode.ArrayLiteralNode;
- pattern.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
+ pattern.accept(new VerifyDestructuringPatternNodeVisitor(new LexicalContext()) {
@Override
- public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
- if (literalNode.isArray()) {
- boolean restElement = false;
- for (final Expression element : literalNode.getElementExpressions()) {
- if (element != null) {
- if (restElement) {
- throw error(String.format("Unexpected element after rest element"), element.getToken());
- }
- if (element.isTokenType(SPREAD_ARRAY)) {
- restElement = true;
- final Expression lvalue = ((UnaryNode) element).getExpression();
- if (!checkValidLValue(lvalue, contextString)) {
- throw error(AbstractParser.message("invalid.lvalue"), lvalue.getToken());
- }
- }
- element.accept(this);
- }
- }
- return false;
- } else {
- return enterDefault(literalNode);
- }
- }
-
- @Override
- public boolean enterObjectNode(final ObjectNode objectNode) {
- return true;
- }
-
- @Override
- public boolean enterPropertyNode(final PropertyNode propertyNode) {
- if (propertyNode.getValue() != null) {
- propertyNode.getValue().accept(this);
- return false;
- } else {
- return enterDefault(propertyNode);
+ protected void verifySpreadElement(final Expression lvalue) {
+ if (!checkValidLValue(lvalue, contextString)) {
+ throw error(AbstractParser.message("invalid.lvalue"), lvalue.getToken());
}
}
@@ -817,27 +784,6 @@
}
@Override
- public boolean enterBinaryNode(final BinaryNode binaryNode) {
- if (binaryNode.isTokenType(ASSIGN)) {
- binaryNode.lhs().accept(this);
- // Initializer(rhs) can be any AssignmentExpression
- return false;
- } else {
- return enterDefault(binaryNode);
- }
- }
-
- @Override
- public boolean enterUnaryNode(final UnaryNode unaryNode) {
- if (unaryNode.isTokenType(SPREAD_ARRAY)) {
- // rest element
- return true;
- } else {
- return enterDefault(unaryNode);
- }
- }
-
- @Override
protected boolean enterDefault(final Node node) {
throw error(String.format("unexpected node in AssignmentPattern: %s", node));
}
@@ -1579,7 +1525,50 @@
variableDeclarationList(varType, true, -1);
}
- private List<Expression> variableDeclarationList(final TokenType varType, final boolean isStatement, final int sourceOrder) {
+ private static final class ForVariableDeclarationListResult {
+ /** First missing const or binding pattern initializer. */
+ Expression missingAssignment;
+ /** First declaration with an initializer. */
+ long declarationWithInitializerToken;
+ /** Destructuring assignments. */
+ Expression init;
+ Expression firstBinding;
+ Expression secondBinding;
+
+ void recordMissingAssignment(final Expression binding) {
+ if (missingAssignment == null) {
+ missingAssignment = binding;
+ }
+ }
+
+ void recordDeclarationWithInitializer(final long token) {
+ if (declarationWithInitializerToken == 0L) {
+ declarationWithInitializerToken = token;
+ }
+ }
+
+ void addBinding(final Expression binding) {
+ if (firstBinding == null) {
+ firstBinding = binding;
+ } else if (secondBinding == null) {
+ secondBinding = binding;
+ }
+ // ignore the rest
+ }
+
+ void addAssignment(final Expression assignment) {
+ if (init == null) {
+ init = assignment;
+ } else {
+ init = new BinaryNode(Token.recast(init.getToken(), COMMARIGHT), init, assignment);
+ }
+ }
+ }
+
+ /**
+ * @param isStatement {@code true} if a VariableStatement, {@code false} if a {@code for} loop VariableDeclarationList
+ */
+ private ForVariableDeclarationListResult variableDeclarationList(final TokenType varType, final boolean isStatement, final int sourceOrder) {
// VAR tested in caller.
assert varType == VAR || varType == LET || varType == CONST;
final int varLine = line;
@@ -1587,7 +1576,6 @@
next();
- final List<Expression> bindings = new ArrayList<>();
int varFlags = 0;
if (varType == LET) {
varFlags |= VarNode.IS_LET;
@@ -1595,7 +1583,7 @@
varFlags |= VarNode.IS_CONST;
}
- Expression missingAssignment = null;
+ final ForVariableDeclarationListResult forResult = isStatement ? null : new ForVariableDeclarationListResult();
while (true) {
// Get name of var.
if (type == YIELD && inGeneratorFunction()) {
@@ -1603,7 +1591,7 @@
}
final String contextString = "variable name";
- Expression binding = bindingIdentifierOrPattern(contextString);
+ final Expression binding = bindingIdentifierOrPattern(contextString);
final boolean isDestructuring = !(binding instanceof IdentNode);
if (isDestructuring) {
final int finalVarFlags = varFlags;
@@ -1625,6 +1613,9 @@
// Look for initializer assignment.
if (type == ASSIGN) {
+ if (!isStatement) {
+ forResult.recordDeclarationWithInitializer(varToken);
+ }
next();
// Get initializer expression. Suppress IN if not statement.
@@ -1655,26 +1646,29 @@
}
// Only set declaration flag on lexically scoped let/const as it adds runtime overhead.
final IdentNode name = varType == LET || varType == CONST ? ident.setIsDeclaredHere() : ident;
- binding = name;
+ if (!isStatement) {
+ if (init == null && varType == CONST) {
+ forResult.recordMissingAssignment(name);
+ }
+ forResult.addBinding(new IdentNode(name));
+ }
final VarNode var = new VarNode(varLine, varToken, sourceOrder, finish, name, init, varFlags);
appendStatement(var);
- if (init == null && varType == CONST) {
- if (missingAssignment == null) {
- missingAssignment = binding;
- }
- }
} else {
assert init != null || !isStatement;
- binding = init == null ? binding : verifyAssignment(Token.recast(varToken, ASSIGN), binding, init);
- if (isStatement) {
- appendStatement(new ExpressionStatement(varLine, binding.getToken(), finish, binding, varType));
- } else if (init == null) {
- if (missingAssignment == null) {
- missingAssignment = binding;
+ if (init != null) {
+ final Expression assignment = verifyAssignment(Token.recast(varToken, ASSIGN), binding, init);
+ if (isStatement) {
+ appendStatement(new ExpressionStatement(varLine, assignment.getToken(), finish, assignment, varType));
+ } else {
+ forResult.addAssignment(assignment);
+ forResult.addBinding(assignment);
}
+ } else if (!isStatement) {
+ forResult.recordMissingAssignment(binding);
+ forResult.addBinding(binding);
}
}
- bindings.add(binding);
if (type != COMMARIGHT) {
break;
@@ -1685,20 +1679,9 @@
// If is a statement then handle end of line.
if (isStatement) {
endOfLine();
- } else {
- if (type == SEMICOLON) {
- // late check for missing assignment, now we know it's a for (init; test; modify) loop
- if (missingAssignment != null) {
- if (missingAssignment instanceof IdentNode) {
- throw error(AbstractParser.message("missing.const.assignment", ((IdentNode)missingAssignment).getName()));
- } else {
- throw error(AbstractParser.message("missing.destructuring.assignment"), missingAssignment.getToken());
- }
- }
- }
}
- return bindings;
+ return forResult;
}
private boolean isBindingIdentifier() {
@@ -1729,50 +1712,91 @@
}
}
+ private abstract class VerifyDestructuringPatternNodeVisitor extends NodeVisitor<LexicalContext> {
+ VerifyDestructuringPatternNodeVisitor(final LexicalContext lc) {
+ super(lc);
+ }
+
+ @Override
+ public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
+ if (literalNode.isArray()) {
+ if (((LiteralNode.ArrayLiteralNode)literalNode).hasSpread() && ((LiteralNode.ArrayLiteralNode)literalNode).hasTrailingComma()) {
+ throw error("Rest element must be last", literalNode.getElementExpressions().get(literalNode.getElementExpressions().size() - 1).getToken());
+ }
+ boolean restElement = false;
+ for (final Expression element : literalNode.getElementExpressions()) {
+ if (element != null) {
+ if (restElement) {
+ throw error("Unexpected element after rest element", element.getToken());
+ }
+ if (element.isTokenType(SPREAD_ARRAY)) {
+ restElement = true;
+ final Expression lvalue = ((UnaryNode) element).getExpression();
+ verifySpreadElement(lvalue);
+ }
+ element.accept(this);
+ }
+ }
+ return false;
+ } else {
+ return enterDefault(literalNode);
+ }
+ }
+
+ protected abstract void verifySpreadElement(Expression lvalue);
+
+ @Override
+ public boolean enterObjectNode(final ObjectNode objectNode) {
+ return true;
+ }
+
+ @Override
+ public boolean enterPropertyNode(final PropertyNode propertyNode) {
+ if (propertyNode.getValue() != null) {
+ propertyNode.getValue().accept(this);
+ return false;
+ } else {
+ return enterDefault(propertyNode);
+ }
+ }
+
+ @Override
+ public boolean enterBinaryNode(final BinaryNode binaryNode) {
+ if (binaryNode.isTokenType(ASSIGN)) {
+ binaryNode.lhs().accept(this);
+ // Initializer(rhs) can be any AssignmentExpression
+ return false;
+ } else {
+ return enterDefault(binaryNode);
+ }
+ }
+
+ @Override
+ public boolean enterUnaryNode(final UnaryNode unaryNode) {
+ if (unaryNode.isTokenType(SPREAD_ARRAY)) {
+ // rest element
+ return true;
+ } else {
+ return enterDefault(unaryNode);
+ }
+ }
+ }
+
/**
* Verify destructuring variable declaration binding pattern and extract bound variable declarations.
*/
private void verifyDestructuringBindingPattern(final Expression pattern, final Consumer<IdentNode> identifierCallback) {
- assert (pattern instanceof BinaryNode && ((BinaryNode)pattern).isTokenType(ASSIGN)) ||
+ assert (pattern instanceof BinaryNode && pattern.isTokenType(ASSIGN)) ||
pattern instanceof ObjectNode || pattern instanceof LiteralNode.ArrayLiteralNode;
- pattern.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
+ pattern.accept(new VerifyDestructuringPatternNodeVisitor(new LexicalContext()) {
@Override
- public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
- if (literalNode.isArray()) {
- boolean restElement = false;
- for (final Expression element : literalNode.getElementExpressions()) {
- if (restElement) {
- throw error(String.format("Unexpected element after rest element"), element.getToken());
- }
- if (element != null) {
- if (element.isTokenType(SPREAD_ARRAY)) {
- restElement = true;
- if (!(((UnaryNode) element).getExpression() instanceof IdentNode)) {
- throw error(String.format("Expected a valid binding identifier"), element.getToken());
-
- }
- }
- element.accept(this);
- }
- }
- return false;
+ protected void verifySpreadElement(final Expression lvalue) {
+ if (lvalue instanceof IdentNode) {
+ // checked in identifierCallback
+ } else if (isDestructuringLhs(lvalue)) {
+ verifyDestructuringBindingPattern(lvalue, identifierCallback);
} else {
- return enterDefault(literalNode);
- }
- }
-
- @Override
- public boolean enterObjectNode(final ObjectNode objectNode) {
- return true;
- }
-
- @Override
- public boolean enterPropertyNode(final PropertyNode propertyNode) {
- if (propertyNode.getValue() != null) {
- propertyNode.getValue().accept(this);
- return false;
- } else {
- return enterDefault(propertyNode);
+ throw error("Expected a valid binding identifier", lvalue.getToken());
}
}
@@ -1783,27 +1807,6 @@
}
@Override
- public boolean enterBinaryNode(final BinaryNode binaryNode) {
- if (binaryNode.isTokenType(ASSIGN)) {
- binaryNode.lhs().accept(this);
- // Initializer(rhs) can be any AssignmentExpression
- return false;
- } else {
- return enterDefault(binaryNode);
- }
- }
-
- @Override
- public boolean enterUnaryNode(final UnaryNode unaryNode) {
- if (unaryNode.isTokenType(SPREAD_ARRAY)) {
- // rest element
- return true;
- } else {
- return enterDefault(unaryNode);
- }
- }
-
- @Override
protected boolean enterDefault(final Node node) {
throw error(String.format("unexpected node in BindingPattern: %s", node));
}
@@ -1910,10 +1913,10 @@
final ParserContextLoopNode forNode = new ParserContextLoopNode();
lc.push(forNode);
Block body = null;
- List<Expression> vars = null;
Expression init = null;
JoinPredecessorExpression test = null;
JoinPredecessorExpression modify = null;
+ ForVariableDeclarationListResult varDeclList = null;
int flags = 0;
boolean isForOf = false;
@@ -1931,10 +1934,11 @@
expect(LPAREN);
+ TokenType varType = null;
switch (type) {
case VAR:
// Var declaration captured in for outer block.
- vars = variableDeclarationList(type, false, forStart);
+ varDeclList = variableDeclarationList(varType = type, false, forStart);
break;
case SEMICOLON:
break;
@@ -1942,12 +1946,12 @@
if (useBlockScope() && (type == LET && lookaheadIsLetDeclaration(true) || type == CONST)) {
flags |= ForNode.PER_ITERATION_SCOPE;
// LET/CONST declaration captured in container block created above.
- vars = variableDeclarationList(type, false, forStart);
+ varDeclList = variableDeclarationList(varType = type, false, forStart);
break;
}
if (env._const_as_var && type == CONST) {
// Var declaration captured in for outer block.
- vars = variableDeclarationList(TokenType.VAR, false, forStart);
+ varDeclList = variableDeclarationList(varType = TokenType.VAR, false, forStart);
break;
}
@@ -1983,38 +1987,30 @@
break;
}
case IN:
-
flags |= isForOf ? ForNode.IS_FOR_OF : ForNode.IS_FOR_IN;
test = new JoinPredecessorExpression();
- if (vars != null) {
- // for (var i in obj)
- if (vars.size() == 1) {
- init = new IdentNode((IdentNode)vars.get(0));
- if (init.isTokenType(ASSIGN)) {
- throw error(AbstractParser.message("for.in.loop.initializer"), init.getToken());
- }
- assert init instanceof IdentNode || isDestructuringLhs(init);
- } else {
+ if (varDeclList != null) {
+ // for (var|let|const ForBinding in|of expression)
+ if (varDeclList.secondBinding != null) {
// for (var i, j in obj) is invalid
- throw error(AbstractParser.message("many.vars.in.for.in.loop", isForOf ? "of" : "in"), vars.get(1).getToken());
+ throw error(AbstractParser.message("many.vars.in.for.in.loop", isForOf ? "of" : "in"), varDeclList.secondBinding.getToken());
}
+ if (varDeclList.declarationWithInitializerToken != 0 && (isStrictMode || type != TokenType.IN || varType != VAR || varDeclList.init != null)) {
+ // ES5 legacy: for (var i = AssignmentExpressionNoIn in Expression)
+ // Invalid in ES6, but allow it in non-strict mode if no ES6 features used,
+ // i.e., error if strict, for-of, let/const, or destructuring
+ throw error(AbstractParser.message("for.in.loop.initializer", isForOf ? "of" : "in"), varDeclList.declarationWithInitializerToken);
+ }
+ init = varDeclList.firstBinding;
+ assert init instanceof IdentNode || isDestructuringLhs(init);
} else {
// for (expr in obj)
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)) {
+ if (!checkValidLValue(init, isForOf ? "for-of iterator" : "for-in iterator")) {
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", isForOf ? "of" : "in"), init.getToken());
- }
- verifyStrictIdent((IdentNode)init, isForOf ? "for-of iterator" : "for-in iterator");
- }
}
next();
@@ -2856,6 +2852,7 @@
final List<Expression> elements = new ArrayList<>();
// Track elisions.
boolean elision = true;
+ boolean hasSpread = false;
loop:
while (true) {
long spreadToken = 0;
@@ -2879,6 +2876,7 @@
case ELLIPSIS:
if (isES6()) {
+ hasSpread = true;
spreadToken = token;
next();
}
@@ -2905,7 +2903,7 @@
}
}
- return LiteralNode.newInstance(arrayToken, finish, elements);
+ return LiteralNode.newInstance(arrayToken, finish, elements, hasSpread, elision);
}
/**