8167289: Backport ES6 updates from Graal.js
authorhannesw
Fri, 07 Oct 2016 10:30:14 +0200
changeset 41425 86226c782b1a
parent 41424 1c78811193d1
child 41426 aa60c8d89a92
8167289: Backport ES6 updates from Graal.js Reviewed-by: lagergren, sundar Contributed-by: andreas.woess@oracle.com
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LiteralNode.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java
--- 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);
     }
 
     /**