Disallow field syntax to access a target's providers

The provider-key syntax should be used instead.

This is an incompatible change attached to new flag --incompatible_disable_target_provider_fields.

See https://github.com/bazelbuild/bazel/issues/9014 for details.

RELNOTES: New incompatible flag --incompatible_disable_target_provider_fields removes the ability (in Starlark) to access a target's providers via the field syntax (for example, `ctx.attr.dep.my_provider`). The provider-key syntax should be used instead (for example, `ctx.attr.dep[MyProvider]`). See https://github.com/bazelbuild/bazel/issues/9014 for details.
PiperOrigin-RevId: 260920114
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/AbstractConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/AbstractConfiguredTarget.java
index 3027a99..799b6d1 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/AbstractConfiguredTarget.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/AbstractConfiguredTarget.java
@@ -16,6 +16,7 @@
 
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.analysis.AnalysisUtils;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.DefaultInfo;
@@ -36,20 +37,23 @@
 import com.google.devtools.build.lib.skyframe.BuildConfigurationValue;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
 import com.google.devtools.build.lib.skylarkinterface.StarlarkContext;
-import com.google.devtools.build.lib.syntax.ClassObject;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalUtils;
+import com.google.devtools.build.lib.syntax.Mutability;
 import com.google.devtools.build.lib.syntax.Printer;
+import com.google.devtools.build.lib.syntax.SkylarkClassObject;
+import com.google.devtools.build.lib.syntax.SkylarkType;
+import com.google.devtools.build.lib.syntax.StarlarkSemantics;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import javax.annotation.Nullable;
 
 /**
- * An abstract implementation of ConfiguredTarget in which all properties are
- * assigned trivial default values.
+ * An abstract implementation of ConfiguredTarget in which all properties are assigned trivial
+ * default values.
  */
 public abstract class AbstractConfiguredTarget
-    implements ConfiguredTarget, VisibilityProvider, ClassObject {
+    implements ConfiguredTarget, VisibilityProvider, SkylarkClassObject {
   private final Label label;
   private final BuildConfigurationValue.Key configurationKey;
 
@@ -62,6 +66,18 @@
   private static final String DATA_RUNFILES_FIELD = "data_runfiles";
   private static final String DEFAULT_RUNFILES_FIELD = "default_runfiles";
 
+  // A set containing all field names which may be specially handled (and thus may not be
+  // attributed to normal user-specified providers).
+  private static final ImmutableSet<String> SPECIAL_FIELD_NAMES =
+      ImmutableSet.of(
+          LABEL_FIELD,
+          FILES_FIELD,
+          DEFAULT_RUNFILES_FIELD,
+          DATA_RUNFILES_FIELD,
+          FilesToRunProvider.SKYLARK_NAME,
+          OutputGroupInfo.SKYLARK_NAME,
+          RuleConfiguredTarget.ACTIONS_FIELD_NAME);
+
   public AbstractConfiguredTarget(Label label, BuildConfigurationValue.Key configurationKey) {
     this(label, configurationKey, NestedSetBuilder.emptySet(Order.STABLE_ORDER));
   }
@@ -106,10 +122,33 @@
   }
 
   @Override
+  public Object getValue(Location loc, StarlarkSemantics semantics, String name)
+      throws EvalException {
+    if (semantics.incompatibleDisableTargetProviderFields()
+        && !SPECIAL_FIELD_NAMES.contains(name)) {
+      throw new EvalException(
+          loc,
+          "Accessing providers via the field syntax on structs is "
+              + "deprecated and will be removed soon. It may be temporarily re-enabled by setting "
+              + "--incompatible_disable_target_provider_fields=false. See "
+              + "https://github.com/bazelbuild/bazel/issues/9014 for details.");
+    }
+    return getValue(name);
+  }
+
+  @Override
   public Object getValue(String name) {
     switch (name) {
       case LABEL_FIELD:
         return getLabel();
+      case RuleConfiguredTarget.ACTIONS_FIELD_NAME:
+        // Depending on subclass, the 'actions' field will either be unsupported or of type
+        // java.util.List, which needs to be converted to SkylarkList before being returned.
+        Object result = get(name);
+        if (result != null) {
+          result = SkylarkType.convertToSkylark(result, (Mutability) null);
+        }
+        return result;
       default:
         return get(name);
     }
@@ -207,9 +246,6 @@
    */
   @Override
   public final Object get(String providerKey) {
-    if (OutputGroupInfo.SKYLARK_NAME.equals(providerKey)) {
-      return get(OutputGroupInfo.SKYLARK_CONSTRUCTOR);
-    }
     switch (providerKey) {
       case FILES_FIELD:
         return getDefaultProvider().getFiles();
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkInfo.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkInfo.java
index 0837b1f..c18438f 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkInfo.java
@@ -32,6 +32,7 @@
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.SkylarkType;
+import com.google.devtools.build.lib.syntax.StarlarkSemantics;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -97,6 +98,13 @@
     return getLayout() != null;
   }
 
+  @Override
+  public Object getValue(Location loc, StarlarkSemantics starlarkSemantics, String name)
+      throws EvalException {
+    // By default, a SkylarkInfo's field values are not affected by the Starlark semantics.
+    return getValue(name);
+  }
+
   /**
    * Creates a schemaless (map-based) provider instance with the given provider type and field
    * values.
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 65c65e4..f588a4e 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
@@ -233,6 +233,23 @@
   public boolean incompatibleDepsetIsNotIterable;
 
   @Option(
+      name = "incompatible_disable_target_provider_fields",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
+      effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS},
+      metadataTags = {
+        OptionMetadataTag.INCOMPATIBLE_CHANGE,
+        OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
+      },
+      help =
+          "If set to true, disable the ability to access providers on 'target' objects via field "
+              + "syntax. Use provider-key syntax instead. For example, instead of using "
+              + "`ctx.attr.dep.my_info` to access `my_info` from inside a rule implementation "
+              + "function, use `ctx.attr.dep[MyInfo]`. See "
+              + "https://github.com/bazelbuild/bazel/issues/9014 for details.")
+  public boolean incompatibleDisableTargetProviderFields;
+
+  @Option(
       name = "incompatible_disable_deprecated_attr_params",
       defaultValue = "true",
       documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
@@ -702,6 +719,7 @@
             .incompatibleBzlDisallowLoadAfterStatement(incompatibleBzlDisallowLoadAfterStatement)
             .incompatibleDepsetIsNotIterable(incompatibleDepsetIsNotIterable)
             .incompatibleDepsetUnion(incompatibleDepsetUnion)
+            .incompatibleDisableTargetProviderFields(incompatibleDisableTargetProviderFields)
             .incompatibleDisableThirdPartyLicenseChecking(
                 incompatibleDisableThirdPartyLicenseChecking)
             .incompatibleDisableDeprecatedAttrParams(incompatibleDisableDeprecatedAttrParams)
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
index 07c00f1..64701b5 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
@@ -133,7 +133,7 @@
 
     if (objValue instanceof SkylarkClassObject) {
       try {
-        return ((SkylarkClassObject) objValue).getValue(name);
+        return ((SkylarkClassObject) objValue).getValue(loc, env.getSemantics(), name);
       } catch (IllegalArgumentException ex) {
         throw new EvalException(loc, ex);
       }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkClassObject.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkClassObject.java
index 03c9f88..64e6fc6 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkClassObject.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkClassObject.java
@@ -13,6 +13,9 @@
 // limitations under the License.
 package com.google.devtools.build.lib.syntax;
 
+import com.google.devtools.build.lib.events.Location;
+import javax.annotation.Nullable;
+
 /**
  * A marker interface for a {@link ClassObject} whose {@link #getValue} always returns a
  * Skylark-friendly value, with no defensive conversion required.
@@ -26,4 +29,17 @@
  * Skylark-friendly values.
  */
 public interface SkylarkClassObject extends ClassObject {
+
+  /**
+   * Returns the value of the field with the given name, or null if the field does not exist.
+   *
+   * @param loc the location in the current Starlark evaluation context
+   * @param starlarkSemantics the current starlark semantics, which may affect which fields are
+   *     available, or the semantics of the available fields
+   * @param name the name of the field to return the value of
+   * @throws EvalException if a user-visible error occurs (other than non-existent field).
+   */
+  @Nullable
+  Object getValue(Location loc, StarlarkSemantics starlarkSemantics, String name)
+      throws EvalException;
 }
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 7527c6c..c3e1143 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
@@ -148,6 +148,8 @@
 
   public abstract boolean incompatibleDepsetUnion();
 
+  public abstract boolean incompatibleDisableTargetProviderFields();
+
   public abstract boolean incompatibleDisableThirdPartyLicenseChecking();
 
   public abstract boolean incompatibleDisableDeprecatedAttrParams();
@@ -263,6 +265,7 @@
           .incompatibleBzlDisallowLoadAfterStatement(true)
           .incompatibleDepsetIsNotIterable(true)
           .incompatibleDepsetUnion(true)
+          .incompatibleDisableTargetProviderFields(false)
           .incompatibleDisableThirdPartyLicenseChecking(true)
           .incompatibleDisableDeprecatedAttrParams(true)
           .incompatibleDisableObjcProviderResources(true)
@@ -331,6 +334,8 @@
 
     public abstract Builder incompatibleDepsetUnion(boolean value);
 
+    public abstract Builder incompatibleDisableTargetProviderFields(boolean value);
+
     public abstract Builder incompatibleDisableThirdPartyLicenseChecking(boolean value);
 
     public abstract Builder incompatibleDisableDeprecatedAttrParams(boolean value);
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 e77ed2d..a154354 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
@@ -138,6 +138,7 @@
         "--incompatible_depset_for_libraries_to_link_getter=" + rand.nextBoolean(),
         "--incompatible_depset_is_not_iterable=" + rand.nextBoolean(),
         "--incompatible_depset_union=" + rand.nextBoolean(),
+        "--incompatible_disable_target_provider_fields=" + rand.nextBoolean(),
         "--incompatible_disable_deprecated_attr_params=" + rand.nextBoolean(),
         "--incompatible_disable_objc_provider_resources=" + rand.nextBoolean(),
         "--incompatible_disable_third_party_license_checking=" + rand.nextBoolean(),
@@ -196,6 +197,7 @@
         .incompatibleDepsetForLibrariesToLinkGetter(rand.nextBoolean())
         .incompatibleDepsetIsNotIterable(rand.nextBoolean())
         .incompatibleDepsetUnion(rand.nextBoolean())
+        .incompatibleDisableTargetProviderFields(rand.nextBoolean())
         .incompatibleDisableDeprecatedAttrParams(rand.nextBoolean())
         .incompatibleDisableObjcProviderResources(rand.nextBoolean())
         .incompatibleDisableThirdPartyLicenseChecking(rand.nextBoolean())
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
index 38f7a3a..11b0f10 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
@@ -2909,6 +2909,98 @@
   }
 
   @Test
+  public void testDisableTargetProviderFields() throws Exception {
+    setSkylarkSemanticsOptions("--incompatible_disable_target_provider_fields=true");
+    scratch.file(
+        "test/skylark/rule.bzl",
+        "MyProvider = provider()",
+        "",
+        "def _my_rule_impl(ctx): ",
+        "  print(ctx.attr.dep.my_info)",
+        "def _dep_rule_impl(ctx): ",
+        "  my_info = MyProvider(foo = 'bar')",
+        "  return struct(my_info = my_info, providers = [my_info])",
+        "my_rule = rule(",
+        "  implementation = _my_rule_impl,",
+        "  attrs = {",
+        "    'dep':  attr.label(),",
+        "  })",
+        "dep_rule = rule(implementation = _dep_rule_impl)");
+    scratch.file(
+        "test/skylark/BUILD",
+        "load(':rule.bzl', 'my_rule', 'dep_rule')",
+        "",
+        "my_rule(name = 'r', dep = ':d')",
+        "dep_rule(name = 'd')");
+
+    reporter.removeHandler(failFastHandler);
+    getConfiguredTarget("//test/skylark:r");
+    assertContainsEvent(
+        "Accessing providers via the field syntax on structs is deprecated and will be removed "
+            + "soon. It may be temporarily re-enabled by setting "
+            + "--incompatible_disable_target_provider_fields=false. "
+            + "See https://github.com/bazelbuild/bazel/issues/9014 for details.");
+  }
+
+  // Verifies that non-provider fields on the 'target' type are still available even with
+  // --incompatible_disable_target_provider_fields.
+  @Test
+  public void testDisableTargetProviderFields_actionsField() throws Exception {
+    setSkylarkSemanticsOptions("--incompatible_disable_target_provider_fields=true");
+    scratch.file(
+        "test/skylark/rule.bzl",
+        "MyProvider = provider()",
+        "",
+        "def _my_rule_impl(ctx): ",
+        "  print(ctx.attr.dep.actions)",
+        "def _dep_rule_impl(ctx): ",
+        "  my_info = MyProvider(foo = 'bar')",
+        "  return struct(my_info = my_info, providers = [my_info])",
+        "my_rule = rule(",
+        "  implementation = _my_rule_impl,",
+        "  attrs = {",
+        "    'dep':  attr.label(),",
+        "  })",
+        "dep_rule = rule(implementation = _dep_rule_impl)");
+    scratch.file(
+        "test/skylark/BUILD",
+        "load(':rule.bzl', 'my_rule', 'dep_rule')",
+        "",
+        "my_rule(name = 'r', dep = ':d')",
+        "dep_rule(name = 'd')");
+
+    assertThat(getConfiguredTarget("//test/skylark:r")).isNotNull();
+  }
+
+  @Test
+  public void testDisableTargetProviderFields_disabled() throws Exception {
+    setSkylarkSemanticsOptions("--incompatible_disable_target_provider_fields=false");
+    scratch.file(
+        "test/skylark/rule.bzl",
+        "MyProvider = provider()",
+        "",
+        "def _my_rule_impl(ctx): ",
+        "  print(ctx.attr.dep.my_info)",
+        "def _dep_rule_impl(ctx): ",
+        "  my_info = MyProvider(foo = 'bar')",
+        "  return struct(my_info = my_info, providers = [my_info])",
+        "my_rule = rule(",
+        "  implementation = _my_rule_impl,",
+        "  attrs = {",
+        "    'dep':  attr.label(),",
+        "  })",
+        "dep_rule = rule(implementation = _dep_rule_impl)");
+    scratch.file(
+        "test/skylark/BUILD",
+        "load(':rule.bzl', 'my_rule', 'dep_rule')",
+        "",
+        "my_rule(name = 'r', dep = ':d')",
+        "dep_rule(name = 'd')");
+
+    assertThat(getConfiguredTarget("//test/skylark:r")).isNotNull();
+  }
+
+  @Test
   public void testNoRuleOutputsParam() throws Exception {
     setSkylarkSemanticsOptions("--incompatible_no_rule_outputs_param=true");
     scratch.file(