Skylark: load() allows the loading of symbols via an alias.

E.g. load("/foo/bla", my_rule = "old_name") will introduce the symbol "my_rule" as an alias for "old_name".

--
MOS_MIGRATED_REVID=98933635
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 46e7ca7..21eb2a5 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
@@ -36,6 +36,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -1069,7 +1070,7 @@
     return list;
   }
 
-  // load '(' STRING (COMMA STRING)* COMMA? ')'
+  // load '(' STRING (COMMA [IDENTIFIER EQUALS] STRING)* COMMA? ')'
   private void parseLoad(List<Statement> list) {
     int start = token.left;
     if (token.kind != TokenKind.STRING) {
@@ -1083,20 +1084,16 @@
     nextToken();
     expect(TokenKind.COMMA);
 
-    List<Identifier> symbols = new ArrayList<>();
-    if (token.kind == TokenKind.STRING) {
-      symbols.add(new Identifier((String) token.value));
-    }
-    expect(TokenKind.STRING);
+    Map<Identifier, String> symbols = new HashMap<>();
+    parseLoadSymbol(symbols); // At least one symbol is required
+
     while (token.kind != TokenKind.RPAREN && token.kind != TokenKind.EOF) {
       expect(TokenKind.COMMA);
       if (token.kind == TokenKind.RPAREN) {
         break;
       }
-      if (token.kind == TokenKind.STRING) {
-        symbols.add(new Identifier((String) token.value));
-      }
-      expect(TokenKind.STRING);
+
+      parseLoadSymbol(symbols);
     }
     expect(TokenKind.RPAREN);
     
@@ -1106,7 +1103,7 @@
     // this only happens in Skylark. Consequently, we invoke it here to discover
     // invalid load paths in BUILD mode, too.
     try {
-      stmt.validateLoadPath();
+      stmt.validatePath();
     } catch (EvalException e) {
       syntaxError(pathToken, e.getMessage());
     }
@@ -1114,6 +1111,51 @@
     list.add(setLocation(stmt, start, token.left));
   }
 
+  /**
+   * Parses the next symbol argument of a load statement and puts it into the output map.
+   *
+   * <p> The symbol is either "name" (STRING) or name = "declared" (IDENTIFIER EQUALS STRING).
+   * "Declared" refers to the original name in the bazel file that should be loaded.
+   * Moreover, it will be the key of the entry in the map.
+   * If no alias is used, "name" and "declared" will be identical.
+   */
+  private void parseLoadSymbol(Map<Identifier, String> symbols) {
+    Token nameToken, declaredToken;
+
+    if (token.kind == TokenKind.STRING) {
+      nameToken = token;
+      declaredToken = nameToken;
+    } else {
+      if (token.kind != TokenKind.IDENTIFIER) {
+        syntaxError(token, "Expected either a literal string or an identifier");
+      }
+
+      nameToken = token;
+
+      expect(TokenKind.IDENTIFIER);
+      expect(TokenKind.EQUALS);
+
+      declaredToken = token;
+    }
+
+    expect(TokenKind.STRING);
+
+    try {
+      Identifier identifier = new Identifier(nameToken.value.toString());
+
+      if (symbols.containsKey(identifier)) {
+        syntaxError(
+            nameToken, String.format("Symbol '%s' has already been loaded", identifier.getName()));
+      } else {
+        symbols.put(
+            setLocation(identifier, nameToken.left, token.left), declaredToken.value.toString());
+      }
+    } catch (NullPointerException npe) {
+      // This means that the value of at least one token is null. In this case, the previous
+      // expect() call has already logged an error.
+    }
+  }
+
   private void parseTopLevelStatement(List<Statement> list) {
     // In Python grammar, there is no "top-level statement" and imports are
     // considered as "small statements". We are a bit stricter than Python here.