Support labels referencing external targets (with '@') in load with --incompatible_load_argument_is_label enabled (fixes #3560)

Closes #3562.

PiperOrigin-RevId: 167745885
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
index 2fc54df..b7b91d4 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
@@ -131,10 +131,10 @@
   void execLoad(LoadStatement node) throws EvalException, InterruptedException {
     if (env.getSemantics().incompatibleLoadArgumentIsLabel) {
       String s = node.getImport().getValue();
-      if (!s.startsWith("//") && !s.startsWith(":")) {
+      if (!s.startsWith("//") && !s.startsWith(":") && !s.startsWith("@")) {
         throw new EvalException(
             node.getLocation(),
-            "First argument of 'load' must be a label and start with either '//' or ':'. "
+            "First argument of 'load' must be a label and start with either '//', ':', or '@'. "
                 + "Use --incompatible_load_argument_is_label=false to temporarily disable this "
                 + "check.");
       }
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index f3d2b09..e27cb63 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -1568,7 +1568,15 @@
   public void testLoadStatementWithAbsolutePath() throws Exception {
     env = newEnvironmentWithSkylarkOptions("--incompatible_load_argument_is_label");
     checkEvalErrorContains(
-        "First argument of 'load' must be a label and start with either '//' or ':'",
+        "First argument of 'load' must be a label and start with either '//', ':', or '@'.",
+        "load('/tmp/foo', 'arg')");
+  }
+
+  @Test
+  public void testAllowLoadStatementWithAbsolutePath() throws Exception {
+    env = newEnvironmentWithSkylarkOptions("--incompatible_load_argument_is_label=false");
+    checkEvalErrorDoesNotContain(
+        "First argument of 'load' must be a label and start with either '//', ':', or '@'.",
         "load('/tmp/foo', 'arg')");
   }
 
@@ -1576,7 +1584,39 @@
   public void testLoadStatementWithRelativePath() throws Exception {
     env = newEnvironmentWithSkylarkOptions("--incompatible_load_argument_is_label");
     checkEvalErrorContains(
-        "First argument of 'load' must be a label and start with either '//' or ':'",
+        "First argument of 'load' must be a label and start with either '//', ':', or '@'.",
         "load('foo', 'arg')");
   }
+
+  @Test
+  public void testAllowLoadStatementWithRelativePath() throws Exception {
+    env = newEnvironmentWithSkylarkOptions("--incompatible_load_argument_is_label=false");
+    checkEvalErrorDoesNotContain(
+        "First argument of 'load' must be a label and start with either '//', ':', or '@'.",
+        "load('foo', 'arg')");
+  }
+
+  @Test
+  public void testLoadStatementWithExternalLabel() throws Exception {
+    env = newEnvironmentWithSkylarkOptions("--incompatible_load_argument_is_label");
+    checkEvalErrorDoesNotContain(
+        "First argument of 'load' must be a label and start with either '//', ':', or '@'.",
+        "load('@other//foo.bzl', 'arg')");
+  }
+
+  @Test
+  public void testLoadStatementWithAbsoluteLabel() throws Exception {
+    env = newEnvironmentWithSkylarkOptions("--incompatible_load_argument_is_label");
+    checkEvalErrorDoesNotContain(
+        "First argument of 'load' must be a label and start with either '//', ':', or '@'.",
+        "load('//foo.bzl', 'arg')");
+  }
+
+  @Test
+  public void testLoadStatementWithRelativeLabel() throws Exception {
+    env = newEnvironmentWithSkylarkOptions("--incompatible_load_argument_is_label");
+    checkEvalErrorDoesNotContain(
+        "First argument of 'load' must be a label and start with either '//', ':', or '@'.",
+        "load(':foo.bzl', 'arg')");
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
index 8ff4bf1..428aefe 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
@@ -201,6 +201,14 @@
     }
   }
 
+  public void checkEvalErrorDoesNotContain(String msg, String... input) throws Exception {
+    try {
+      eval(input);
+    } catch (EvalException | FailFastException e) {
+      assertThat(e).hasMessageThat().doesNotContain(msg);
+    }
+  }
+
   // Forward relevant methods to the EventCollectionApparatus
   public EvaluationTestCase setFailFast(boolean failFast) {
     eventCollectionApparatus.setFailFast(failFast);