Parser: Allow more complex expressions as for loop variables.

Also, use LValue in ForStatement.

--
MOS_MIGRATED_REVID=89122760
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java
index 76a46ee..2faf06c 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java
@@ -23,20 +23,20 @@
  */
 public final class ForStatement extends Statement {
 
-  private final Ident variable;
+  private final LValue variable;
   private final Expression collection;
   private final ImmutableList<Statement> block;
 
   /**
    * Constructs a for loop statement.
    */
-  ForStatement(Ident variable, Expression collection, List<Statement> block) {
-    this.variable = Preconditions.checkNotNull(variable);
+  ForStatement(Expression variable, Expression collection, List<Statement> block) {
+    this.variable = new LValue(Preconditions.checkNotNull(variable));
     this.collection = Preconditions.checkNotNull(collection);
     this.block = ImmutableList.copyOf(block);
   }
 
-  public Ident getVariable() {
+  public LValue getVariable() {
     return variable;
   }
 
@@ -62,7 +62,7 @@
 
     int i = 0;
     for (Object it : ImmutableList.copyOf(col)) {
-      env.update(variable.getName(), it);
+      variable.assign(env, getLocation(), it);
       for (Statement stmt : block) {
         stmt.exec(env);
       }
@@ -89,7 +89,7 @@
     // TODO(bazel-team): validate variable. Maybe make it temporarily readonly.
     SkylarkType type = collection.validate(env);
     env.checkIterable(type, getLocation());
-    env.update(variable.getName(), SkylarkType.UNKNOWN, getLocation());
+    variable.validate(env, getLocation(), SkylarkType.UNKNOWN);
     for (Statement stmt : block) {
       stmt.validate(env);
     }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
index d736a27..187f11b 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
@@ -705,37 +705,28 @@
                                  start, token.right);
   }
 
-  // loop_variables ::= '(' variables ')'
-  //                  | variables
-  // variables ::= ident (',' ident)*
-  private Ident parseForLoopVariables() {
+  // Equivalent to 'exprlist' rule in Python grammar.
+  // loop_variables ::= primary_with_suffix ( ',' primary_with_suffix )* ','?
+  private Expression parseForLoopVariables() {
+    // We cannot reuse parseExpression because it would parse the 'in' operator.
+    // e.g.  "for i in e: pass"  -> we want to parse only "i" here.
     int start = token.left;
-    boolean hasParen = false;
-    if (token.kind == TokenKind.LPAREN) {
-      hasParen = true;
-      nextToken();
+    Expression e1 = parsePrimaryWithSuffix();
+    if (token.kind != TokenKind.COMMA) {
+      return e1;
     }
 
-    // TODO(bazel-team): allow multiple variables in the core Blaze language too.
-    Ident firstIdent = parseIdent();
-    boolean multipleVariables = false;
-
+    // It's a tuple
+    List<Expression> tuple = new ArrayList<>();
+    tuple.add(e1);
     while (token.kind == TokenKind.COMMA) {
-      multipleVariables = true;
-      nextToken();
-      parseIdent();
+      expect(TokenKind.COMMA);
+      if (EXPR_LIST_TERMINATOR_SET.contains(token.kind)) {
+        break;
+      }
+      tuple.add(parsePrimaryWithSuffix());
     }
-
-    if (hasParen) {
-      expect(TokenKind.RPAREN);
-    }
-
-    int end = token.right;
-    if (multipleVariables && !parsePython) {
-      reportError(lexer.createLocation(start, end),
-          "For loops with multiple variables are not yet supported");
-    }
-    return multipleVariables ? makeErrorExpression(start, end) : firstIdent;
+    return setLocation(ListLiteral.makeTuple(tuple), start, token.right);
   }
 
   // list_maker ::= '[' ']'
@@ -766,11 +757,11 @@
           new ListComprehension(expression);
         do {
           nextToken();
-          Ident ident = parseForLoopVariables();
+          Expression loopVar = parseForLoopVariables();
           if (token.kind == TokenKind.IN) {
             nextToken();
             Expression listExpression = parseExpression();
-            listComprehension.add(ident, listExpression);
+            listComprehension.add(loopVar, listExpression);
           } else {
             break;
           }
@@ -824,7 +815,7 @@
     if (token.kind == TokenKind.FOR) {
       // Dict comprehension
       nextToken();
-      Ident loopVar = parseForLoopVariables();
+      Expression loopVar = parseForLoopVariables();
       expect(TokenKind.IN);
       Expression listExpression = parseExpression();
       expect(TokenKind.RBRACE);
@@ -1144,12 +1135,12 @@
   private void parseForStatement(List<Statement> list) {
     int start = token.left;
     expect(TokenKind.FOR);
-    Ident ident = parseIdent();
+    Expression loopVar = parseForLoopVariables();
     expect(TokenKind.IN);
     Expression collection = parseExpression();
     expect(TokenKind.COLON);
     List<Statement> block = parseSuite();
-    Statement stmt = new ForStatement(ident, collection, block);
+    Statement stmt = new ForStatement(loopVar, collection, block);
     list.add(setLocation(stmt, start, token.right));
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
index 9698474..07501da 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
@@ -844,50 +844,28 @@
   }
 
   @Test
-  public void testForLoopMultipleVariablesFail() throws Exception {
-    // For loops with multiple variables are not allowed, when parsePython is not set
-    syntaxEvents.setFailFast(false);
-    List<Statement> stmts = parseFile(
-        "[ i for i, j, k in [(1, 2, 3)] ]\n",
-        false /* no parsePython */);
-    assertThat(stmts).hasSize(1);
-    syntaxEvents.assertContainsEvent("For loops with multiple variables are not yet supported");
-  }
-
-  @Test
   public void testForLoopMultipleVariables() throws Exception {
-    // For loops with multiple variables is ok, when parsePython is set
-    List<Statement> stmts1 = parseFile(
-        "[ i for i, j, k in [(1, 2, 3)] ]\n",
-        true /* parsePython */);
+    List<Statement> stmts1 = parseFile("[ i for i, j, k in [(1, 2, 3)] ]\n");
     assertThat(stmts1).hasSize(1);
 
-    List<Statement> stmts2 = parseFile(
-        "[ i for i, j in [(1, 2, 3)] ]\n",
-        true /* parsePython */);
+    List<Statement> stmts2 = parseFile("[ i for i, j in [(1, 2, 3)] ]\n");
     assertThat(stmts2).hasSize(1);
 
-    List<Statement> stmts3 = parseFile(
-        "[ i for (i, j, k) in [(1, 2, 3)] ]\n",
-        true /* parsePython */);
+    List<Statement> stmts3 = parseFile("[ i for (i, j, k) in [(1, 2, 3)] ]\n");
     assertThat(stmts3).hasSize(1);
   }
 
   @Test
   public void testForLoopBadSyntax() throws Exception {
     syntaxEvents.setFailFast(false);
-    parseFile(
-        "[1 for (a, b, c in var]\n",
-        false /* no parsePython */);
+    parseFile("[1 for (a, b, c in var]\n");
     syntaxEvents.assertContainsEvent("syntax error");
   }
 
   @Test
   public void testForLoopBadSyntax2() throws Exception {
     syntaxEvents.setFailFast(false);
-    parseFile(
-        "[1 for () in var]\n",
-        false /* no parsePython */);
+    parseFile("[1 for in var]\n");
     syntaxEvents.assertContainsEvent("syntax error");
   }