Add a flag that makes two Starlark functions equal if they are defined at the same location.

This flag, when set, mostly reverts https://github.com/bazelbuild/bazel/commit/af7fc3b75a62edc3b4b62de25b733b03c3ff925e (only mostly because it only applies to user-defined functions)

BUG=138789815
RELNOTES: None.
PiperOrigin-RevId: 261299731
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
index 2e384a7..061c986 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
@@ -243,7 +243,8 @@
       };
 
   @Override
-  public Provider provider(String doc, Object fields, Location location) throws EvalException {
+  public Provider provider(String doc, Object fields, Location location, Environment env)
+      throws EvalException {
     Iterable<String> fieldNames = null;
     if (fields instanceof SkylarkList<?>) {
       @SuppressWarnings("unchecked")
@@ -260,7 +261,8 @@
           "Expected list of strings or dictionary of string -> string for 'fields'");
       fieldNames = dict.keySet();
     }
-    return SkylarkProvider.createUnexportedSchemaful(fieldNames, location);
+    return SkylarkProvider.createUnexportedSchemaful(
+        fieldNames, location, env.getSemantics().experimentalFunctionEqualityByLocation());
   }
 
   // TODO(bazel-team): implement attribute copy and other rule properties
diff --git a/src/main/java/com/google/devtools/build/lib/packages/NativeProvider.java b/src/main/java/com/google/devtools/build/lib/packages/NativeProvider.java
index e7375ec..acc739e 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/NativeProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/NativeProvider.java
@@ -78,7 +78,7 @@
       Class<V> valueClass,
       String name,
       FunctionSignature.WithValues<Object, SkylarkType> signature) {
-    super(name, signature, Location.BUILTIN);
+    super(name, signature, Location.BUILTIN, /* equalityByLocation */ false);
     Class<? extends NativeProvider<?>> clazz = (Class<? extends NativeProvider<?>>) getClass();
     key = new NativeKey(name, clazz);
     this.valueClass = valueClass;
diff --git a/src/main/java/com/google/devtools/build/lib/packages/ProviderFromFunction.java b/src/main/java/com/google/devtools/build/lib/packages/ProviderFromFunction.java
index a293393..6edd60e 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/ProviderFromFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/ProviderFromFunction.java
@@ -42,8 +42,9 @@
   protected ProviderFromFunction(
       @Nullable String name,
       FunctionSignature.WithValues<Object, SkylarkType> signature,
-      Location location) {
-    super(name, signature, location);
+      Location location,
+      boolean equalityByLocation) {
+    super(name, signature, location, equalityByLocation);
   }
 
   public SkylarkProviderIdentifier id() {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkProvider.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkProvider.java
index 4d42b45..c3d9c33 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkProvider.java
@@ -75,8 +75,10 @@
    * @param location the location of the Skylark definition for this provider (tests may use {@link
    *     Location#BUILTIN})
    */
+  @VisibleForTesting
   public static SkylarkProvider createUnexportedSchemaless(Location location) {
-    return new SkylarkProvider(/*key=*/ null, /*fields=*/ null, location);
+    return new SkylarkProvider(
+        /*key=*/ null, /*fields=*/ null, location, /* equalityByLocation */ false);
   }
 
   /**
@@ -90,9 +92,12 @@
    *     Location#BUILTIN})
    */
   public static SkylarkProvider createUnexportedSchemaful(
-      Iterable<String> fields, Location location) {
+      Iterable<String> fields, Location location, boolean equalityByLocation) {
     return new SkylarkProvider(
-        /*key=*/ null, fields == null ? null : ImmutableList.copyOf(fields), location);
+        /*key=*/ null,
+        fields == null ? null : ImmutableList.copyOf(fields),
+        location,
+        equalityByLocation);
   }
 
   /**
@@ -102,8 +107,9 @@
    * @param location the location of the Skylark definition for this provider (tests may use {@link
    *     Location#BUILTIN})
    */
+  @VisibleForTesting
   public static SkylarkProvider createExportedSchemaless(SkylarkKey key, Location location) {
-    return new SkylarkProvider(key, /*fields=*/ null, location);
+    return new SkylarkProvider(key, /*fields=*/ null, location, /* equalityByLocation */ false);
   }
 
   /**
@@ -115,9 +121,14 @@
    * @param location the location of the Skylark definition for this provider (tests may use {@link
    *     Location#BUILTIN})
    */
+  @VisibleForTesting
   public static SkylarkProvider createExportedSchemaful(
       SkylarkKey key, Iterable<String> fields, Location location) {
-    return new SkylarkProvider(key, fields == null ? null : ImmutableList.copyOf(fields), location);
+    return new SkylarkProvider(
+        key,
+        fields == null ? null : ImmutableList.copyOf(fields),
+        location, /* equalityByLocation */
+        false);
   }
 
   /**
@@ -127,10 +138,13 @@
    * is schemaless.
    */
   private SkylarkProvider(
-      @Nullable SkylarkKey key, @Nullable ImmutableList<String> fields, Location location) {
+      @Nullable SkylarkKey key,
+      @Nullable ImmutableList<String> fields,
+      Location location,
+      boolean equalityByLocation) {
     // We override getName() in order to use the name that is assigned when export() is called.
     // Hence BaseFunction's constructor gets a null name.
-    super(/*name=*/ null, buildSignature(fields), location);
+    super(/*name=*/ null, buildSignature(fields), location, equalityByLocation);
     this.layout = fields == null ? null : new Layout(fields);
     this.key = key;  // possibly null
     this.errorMessageFormatForUnknownField =
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
index 3211834..0a484d1 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
@@ -704,6 +704,15 @@
       help = "If set to true, unknown string escapes like `\\a` become rejected.")
   public boolean incompatibleRestrictStringEscapes;
 
+  @Option(
+      name = "experimental_function_equality_by_location",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+      effectTags = OptionEffectTag.BUILD_FILE_SEMANTICS,
+      help =
+          "If set to true, two Starlark functions defined at the same place are considered equal.")
+  public boolean experimentalFunctionEqualityByLocation;
+
   /**
    * An interner to reduce the number of StarlarkSemantics instances. A single Blaze instance should
    * never accumulate a large number of these and being able to shortcut on object identity makes a
@@ -774,6 +783,7 @@
             .incompatibleAssignmentIdentifiersHaveLocalScope(
                 incompatibleAssignmentIdentifiersHaveLocalScope)
             .incompatibleDisallowHashingFrozenMutables(incompatibleDisallowHashingFrozenMutables)
+            .experimentalFunctionEqualityByLocation(experimentalFunctionEqualityByLocation)
             .build();
     return INTERNER.intern(semantics);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
index 8d85fc7..d5b5533 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
@@ -86,8 +86,10 @@
             positional = false,
             defaultValue = "None")
       },
-      useLocation = true)
-  public ProviderApi provider(String doc, Object fields, Location location) throws EvalException;
+      useLocation = true,
+      useEnvironment = true)
+  public ProviderApi provider(String doc, Object fields, Location location, Environment env)
+      throws EvalException;
 
   @SkylarkCallable(
       name = "rule",
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
index 0d3c49a..575458c 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
@@ -26,6 +26,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import javax.annotation.Nullable;
 
 /**
@@ -55,6 +56,7 @@
 // (that FuncallExpression must supply), optimizing for the all-positional and all-keyword cases.
 // Also, use better pure maps to minimize map O(n) re-creation events when processing keyword maps.
 public abstract class BaseFunction implements StarlarkFunction {
+  private final boolean equalityByLocation;
 
   /**
    * The name of the function.
@@ -130,7 +132,7 @@
    * {@link #getName}; otherwise it must be non-null.
    */
   public BaseFunction(@Nullable String name) {
-    this.name = name;
+    this(name, null, null, false);
   }
 
   /**
@@ -143,10 +145,12 @@
   public BaseFunction(
       @Nullable String name,
       @Nullable FunctionSignature.WithValues<Object, SkylarkType> signature,
-      @Nullable Location location) {
-    this(name);
+      @Nullable Location location,
+      boolean equalityByLocation) {
+    this.name = name;
     this.signature = signature;
     this.location = location;
+    this.equalityByLocation = equalityByLocation;
   }
 
   /**
@@ -158,7 +162,7 @@
   public BaseFunction(
       @Nullable String name,
       @Nullable FunctionSignature.WithValues<Object, SkylarkType> signature) {
-    this(name, signature, null);
+    this(name, signature, null, false);
   }
 
   /**
@@ -168,7 +172,7 @@
    * @param signature the signature, without default values or types
    */
   public BaseFunction(@Nullable String name, FunctionSignature signature) {
-    this(name, FunctionSignature.WithValues.create(signature), null);
+    this(name, FunctionSignature.WithValues.create(signature), null, false);
   }
 
   /**
@@ -178,8 +182,9 @@
    * @param defaultValues a list of default values for the optional arguments to be configured.
    */
   public BaseFunction(@Nullable String name, @Nullable Iterable<Object> defaultValues) {
-    this(name);
+    this.name = name;
     this.unconfiguredDefaultValues = defaultValues;
+    this.equalityByLocation = false;
   }
 
   /** Get parameter documentation as a list corresponding to each parameter */
@@ -574,6 +579,32 @@
     return builder.toString();
   }
 
+  @Override
+  public boolean equals(@Nullable Object other) {
+    if (equalityByLocation) {
+      if (other instanceof BaseFunction) {
+        BaseFunction that = (BaseFunction) other;
+        // In theory, the location alone unambiguously identifies a given function. However, in
+        // some test cases the location might not have a valid value, thus we also check the name.
+        return Objects.equals(this.getName(), that.getName())
+            && Objects.equals(this.location, that.location);
+      } else {
+        return false;
+      }
+    } else {
+      return super.equals(other);
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    if (equalityByLocation) {
+      return Objects.hash(getName(), location);
+    } else {
+      return super.hashCode();
+    }
+  }
+
   @Nullable
   public Location getLocation() {
     return location;
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 3199952..35d3350 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
@@ -126,6 +126,7 @@
         new UserDefinedFunction(
             node.getIdentifier().getName(),
             node.getIdentifier().getLocation(),
+            env.getSemantics().experimentalFunctionEqualityByLocation(),
             FunctionSignature.WithValues.create(sig, defaultValues, /*types=*/ null),
             node.getStatements(),
             env.getGlobals()));
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
index 9860f6f..7454580 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
@@ -222,6 +222,8 @@
 
   public abstract boolean incompatibleDisallowHashingFrozenMutables();
 
+  public abstract boolean experimentalFunctionEqualityByLocation();
+
   @Memoized
   @Override
   public abstract int hashCode();
@@ -304,6 +306,7 @@
           .incompatibleAllowTagsPropagation(false)
           .incompatibleAssignmentIdentifiersHaveLocalScope(false)
           .incompatibleDisallowHashingFrozenMutables(false)
+          .experimentalFunctionEqualityByLocation(false)
           .build();
 
   /** Builder for {@link StarlarkSemantics}. All fields are mandatory. */
@@ -410,6 +413,8 @@
 
     public abstract Builder incompatibleDisallowHashingFrozenMutables(boolean value);
 
+    public abstract Builder experimentalFunctionEqualityByLocation(boolean value);
+
     public abstract StarlarkSemantics build();
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
index 460bc35..c39bdd2 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
@@ -36,10 +36,11 @@
   public UserDefinedFunction(
       String name,
       Location location,
+      boolean equalityByLocation,
       FunctionSignature.WithValues<Object, SkylarkType> signature,
       ImmutableList<Statement> statements,
       Environment.GlobalFrame definitionGlobals) {
-    super(name, signature, location);
+    super(name, signature, location, equalityByLocation);
     this.statements = statements;
     this.definitionGlobals = definitionGlobals;
   }
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
index 32a5b56..121d233 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
@@ -84,7 +84,8 @@
   }
 
   @Override
-  public ProviderApi provider(String doc, Object fields, Location location) throws EvalException {
+  public ProviderApi provider(String doc, Object fields, Location location, Environment env)
+      throws EvalException {
     FakeProviderApi fakeProvider = new FakeProviderApi();
     // Field documentation will be output preserving the order in which the fields are listed.
     ImmutableList.Builder<ProviderFieldInfo> providerFieldInfos = ImmutableList.builder();
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkInfoTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkInfoTest.java
index 37e28b4..826b89c 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkInfoTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkInfoTest.java
@@ -81,7 +81,8 @@
   @Test
   public void givenLayoutTakesPrecedenceOverProviderLayout() throws Exception {
     SkylarkProvider provider =
-        SkylarkProvider.createUnexportedSchemaful(ImmutableList.of("f1", "f2"), Location.BUILTIN);
+        SkylarkProvider.createUnexportedSchemaful(
+            ImmutableList.of("f1", "f2"), Location.BUILTIN, /* equalityByLocation */ false);
     SkylarkInfo info =
         SkylarkInfo.createSchemaful(
             provider, invertedLayoutF2F1, new Object[]{5, 4}, Location.BUILTIN);
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkProviderTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkProviderTest.java
index 0c81e9b..cde858d 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkProviderTest.java
@@ -71,8 +71,9 @@
 
   @Test
   public void schemafulProvider_Instantiation() throws Exception {
-    SkylarkProvider provider = SkylarkProvider.createUnexportedSchemaful(
-        ImmutableList.of("a", "b", "c"), /*location=*/ null);
+    SkylarkProvider provider =
+        SkylarkProvider.createUnexportedSchemaful(
+            ImmutableList.of("a", "b", "c"), /*location=*/ null, false);
     SkylarkInfo info = instantiateWithA1B2C3(provider);
     assertThat(info.isCompact()).isTrue();
     assertHasExactlyValuesA1B2C3(info);
@@ -86,8 +87,9 @@
 
   @Test
   public void schemafulProvider_GetFields() throws Exception {
-    SkylarkProvider provider = SkylarkProvider.createUnexportedSchemaful(
-        ImmutableList.of("a", "b", "c"), /*location=*/ null);
+    SkylarkProvider provider =
+        SkylarkProvider.createUnexportedSchemaful(
+            ImmutableList.of("a", "b", "c"), /*location=*/ null, false);
     assertThat(provider.getFields()).containsExactly("a", "b", "c").inOrder();
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
index ce01c46..a61b471 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
@@ -172,7 +172,8 @@
         "--incompatible_disable_partition_default_parameter=" + rand.nextBoolean(),
         "--incompatible_assignment_identifiers_have_local_scope=" + rand.nextBoolean(),
         "--incompatible_disallow_hashing_frozen_mutables=" + rand.nextBoolean(),
-        "--internal_skylark_flag_test_canary=" + rand.nextBoolean());
+        "--internal_skylark_flag_test_canary=" + rand.nextBoolean(),
+        "--experimental_function_equality_by_location=" + rand.nextBoolean());
   }
 
   /**
@@ -233,6 +234,7 @@
         .incompatibleAssignmentIdentifiersHaveLocalScope(rand.nextBoolean())
         .incompatibleDisallowHashingFrozenMutables(rand.nextBoolean())
         .internalSkylarkFlagTestCanary(rand.nextBoolean())
+        .experimentalFunctionEqualityByLocation(rand.nextBoolean())
         .build();
   }