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/Environment.java b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
index 6c1834e..cea5ea5 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
@@ -295,16 +295,18 @@
     this.importedExtensions = importedExtensions;
   }
 
-  public void importSymbol(PathFragment extension, String symbol)
+  public void importSymbol(PathFragment extension, Identifier symbol, String nameInLoadedFile)
       throws NoSuchVariableException, LoadFailedException {
     if (!importedExtensions.containsKey(extension)) {
       throw new LoadFailedException(extension.toString());
     }
-    Object value = importedExtensions.get(extension).lookup(symbol);
+
+    Object value = importedExtensions.get(extension).lookup(nameInLoadedFile);
     if (!isSkylarkEnabled()) {
       value = SkylarkType.convertFromSkylark(value);
     }
-    update(symbol, value);
+
+    update(symbol.getName(), value);
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java b/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
index 26c5f15..ce68d66 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
@@ -33,7 +33,7 @@
   public Identifier(String name) {
     this.name = name;
   }
-
+  
   /**
    *  Returns the name of the Identifier.
    */
@@ -51,15 +51,6 @@
   }
 
   @Override
-  Object eval(Environment env) throws EvalException {
-    try {
-      return env.lookup(name);
-    } catch (Environment.NoSuchVariableException e) {
-      throw createInvalidIdentifierException();
-    }
-  }
-
-  @Override
   public boolean equals(@Nullable Object object) {
     if (object instanceof Identifier) {
       Identifier that = (Identifier) object;
@@ -72,6 +63,15 @@
   public int hashCode() {
     return name.hashCode();
   }
+  
+  @Override
+  Object eval(Environment env) throws EvalException {
+    try {
+      return env.lookup(name);
+    } catch (Environment.NoSuchVariableException e) {
+      throw createInvalidIdentifierException();
+    }
+  }
 
   @Override
   public void accept(SyntaxTreeVisitor visitor) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/LoadStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/LoadStatement.java
index 0eae19b..8b9547a 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/LoadStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/LoadStatement.java
@@ -15,9 +15,10 @@
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.vfs.PathFragment;
 
-import java.util.List;
+import java.util.Map;
 
 /**
  * Syntax node for an import statement.
@@ -26,21 +27,28 @@
 
   public static final String PATH_ERROR_MSG = "Path '%s' is not valid. "
       + "It should either start with a slash or refer to a file in the current directory.";
-  private final ImmutableList<Identifier> symbols;
+  private final ImmutableMap<Identifier, String> symbols;
+  private final ImmutableList<Identifier> cachedSymbols; // to save time
   private final PathFragment importPath;
   private final String pathString;
 
   /**
    * Constructs an import statement.
+   *
+   * <p>Symbols maps a symbol to its original name under which it was defined in
+   * the bzl file that should be loaded.
+   * If aliasing is used, the value differs from it's key's symbol#getName().
+   * Otherwise, both values are identical.
    */
-  LoadStatement(String path, List<Identifier> symbols) {
-    this.symbols = ImmutableList.copyOf(symbols);
+  LoadStatement(String path, Map<Identifier, String> symbols) {
+    this.symbols = ImmutableMap.copyOf(symbols);
+    this.cachedSymbols = ImmutableList.copyOf(symbols.keySet());
     this.importPath = new PathFragment(path + ".bzl");
     this.pathString = path;
   }
 
   public ImmutableList<Identifier> getSymbols() {
-    return symbols;
+    return cachedSymbols;
   }
 
   public PathFragment getImportPath() {
@@ -49,18 +57,22 @@
 
   @Override
   public String toString() {
-    return String.format("load(\"%s\", %s)", importPath, Joiner.on(", ").join(symbols));
+    return String.format("load(\"%s\", %s)", importPath, Joiner.on(", ").join(cachedSymbols));
   }
 
   @Override
   void exec(Environment env) throws EvalException, InterruptedException {
-    for (Identifier i : symbols) {
+    for (Map.Entry<Identifier, String> entry : symbols.entrySet()) {
       try {
-        if (i.isPrivate()) {
-          throw new EvalException(getLocation(), "symbol '" + i + "' is private and cannot "
-              + "be imported");
+        Identifier current = entry.getKey();
+
+        if (current.isPrivate()) {
+          throw new EvalException(
+              getLocation(), "symbol '" + current + "' is private and cannot be imported");
         }
-        env.importSymbol(getImportPath(), i.getName());
+        // The key is the original name that was used to define the symbol
+        // in the loaded bzl file
+        env.importSymbol(getImportPath(), current, entry.getValue());
       } catch (Environment.NoSuchVariableException | Environment.LoadFailedException e) {
         throw new EvalException(getLocation(), e.getMessage());
       }
@@ -74,23 +86,21 @@
 
   @Override
   void validate(ValidationEnvironment env) throws EvalException {
-    validateLoadPath();
+    validatePath();
     
     if (!importPath.isAbsolute() && importPath.segmentCount() > 1) {
       throw new EvalException(getLocation(), String.format(PATH_ERROR_MSG, importPath));
     }
-    for (Identifier symbol : symbols) {
+    for (Identifier symbol : cachedSymbols) {
       env.declare(symbol.getName(), getLocation());
     }
   }
 
   /**
-   * Throws an exception if the path argument to load() does starts with more than one forward
+   * Throws an exception if the path argument to load() starts with more than one forward
    * slash ('/')
-   *
-   * @throws EvalException if the path is empty or starts with two forward slashes
    */
-  public void validateLoadPath() throws EvalException {
+  public void validatePath() throws EvalException {
     String error = null;
 
     if (pathString.isEmpty()) {
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.