bazel packages: prep for AttributeContainer optimization

Details:
- RuleFactory:
  - don't plumb AttributeContainer through create{RuleUnchecked,AndAddRule}.
  - avoid overloading of createAndAddRule.
  - inline and simplify setRuleAttributeValue
    (only one of two callers wants 'visibility' logic)
  - inline and simplify getAttributeNoncomputedDefaultValue
    (check both name and type like we do for licenses)
- AttributeContainer:
  - inline setAttributeValueByName into sole use (for $implicit_test).
  - add TODO comments.
  - make isAttributeValueExplicitlySpecified private
PiperOrigin-RevId: 318841494
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
index 3a68926..5baf3db 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
@@ -48,7 +48,6 @@
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.Attribute.StarlarkComputedDefaultTemplate;
-import com.google.devtools.build.lib.packages.AttributeContainer;
 import com.google.devtools.build.lib.packages.AttributeMap;
 import com.google.devtools.build.lib.packages.AttributeValueSource;
 import com.google.devtools.build.lib.packages.BazelModuleContext;
@@ -679,12 +678,7 @@
                   + "Rules may be instantiated only in a BUILD thread.");
         }
         RuleFactory.createAndAddRule(
-            pkgContext,
-            ruleClass,
-            attributeValues,
-            thread.getSemantics(),
-            thread.getCallStack(),
-            new AttributeContainer(ruleClass));
+            pkgContext, ruleClass, attributeValues, thread.getSemantics(), thread.getCallStack());
       } catch (InvalidRuleException | NameConflictException e) {
         throw new EvalException(null, e.getMessage());
       }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AttributeContainer.java b/src/main/java/com/google/devtools/build/lib/packages/AttributeContainer.java
index 9aeddc8..f11a540 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AttributeContainer.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AttributeContainer.java
@@ -27,23 +27,27 @@
  * be a robust public interface, but rather just an input to {@link AttributeMap} instances. Use
  * those instances for all domain-level attribute access.
  */
+// TODO(adonovan): eliminate this class. 99% of all external calls to its methods are of the form
+// rule.getAttributeContainer().foo(), so it's just needless indirection for the caller, and
+// needless indirection in the representation. Instead, make Rule implement an interface with the
+// two methods getAttr and isAttributeValueExplicitlySpecified; it already has those methods.
+// The only time the AttributeContainer needs to be distinct from the Rule itself is in the
+// WorkspaceFactory.setParent hack. Perhaps we can eliminate that?
 public final class AttributeContainer {
 
   private final RuleClass ruleClass;
 
-  // Attribute values, keyed by attribute index:
+  // Attribute values, keyed by attribute index.
   private final Object[] attributeValues;
 
-  // Holds a list of attribute indices.
+  // Holds a list of attribute indices (+1, to make them nonzero).
   // The first byte gives the length of the list.
   // The list records which attributes were set explicitly in the BUILD file.
   // The list may be padded with zeros at the end.
   private byte[] state;
 
-  /**
-   * Create a container for a rule of the given rule class.
-   */
-  public AttributeContainer(RuleClass ruleClass) {
+  /** Creates a container for a rule of the given rule class. */
+  AttributeContainer(RuleClass ruleClass) {
     int n = ruleClass.getAttributeCount();
     if (n > 254) {
       // We reserve the zero byte as a hole/sentinel inside state[].
@@ -66,10 +70,8 @@
     return idx != null ? attributeValues[idx] : null;
   }
 
-  /**
-   * See {@link #isAttributeValueExplicitlySpecified(String)}.
-   */
-  public boolean isAttributeValueExplicitlySpecified(Attribute attribute) {
+  /** See {@link #isAttributeValueExplicitlySpecified(String)}. */
+  boolean isAttributeValueExplicitlySpecified(Attribute attribute) {
     return isAttributeValueExplicitlySpecified(attribute.getName());
   }
 
@@ -144,12 +146,4 @@
       setExplicit(index);
     }
   }
-
-  // This sets the attribute "explicitly" as if it came from the BUILD file.
-  // At present, the sole use of this is for the test_suite.$implicit_tests
-  // attribute, which is synthesized during package loading.  We do want to
-  // consider that "explicitly set" so that it appears in query output.
-  void setAttributeValueByName(String attrName, Object value) {
-    setAttributeValue(ruleClass.getAttributeByName(attrName), value, true);
-  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Package.java b/src/main/java/com/google/devtools/build/lib/packages/Package.java
index c52beb8..646d068 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Package.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Package.java
@@ -1232,15 +1232,15 @@
      * Creates a new {@link Rule} {@code r} where {@code r.getPackage()} is the {@link Package}
      * associated with this {@link Builder}.
      *
-     * <p>The created {@link Rule} will have no attribute values, no output files, and therefore
-     * will be in an invalid state.
+     * <p>The created {@link Rule} will have no output files and therefore will be in an invalid
+     * state.
      */
     Rule createRule(
         Label label,
         RuleClass ruleClass,
         Location location,
         List<StarlarkThread.CallStackEntry> callstack,
-        AttributeContainer attributeContainer) {
+        AttributeContainer attributeContainer) { // required by WorkspaceFactory.setParent hack
       return new Rule(
           pkg, label, ruleClass, location, callStackBuilder.of(callstack), attributeContainer);
     }
@@ -1255,7 +1255,6 @@
         RuleClass ruleClass,
         Location location,
         List<StarlarkThread.CallStackEntry> callstack,
-        AttributeContainer attributeContainer,
         ImplicitOutputsFunction implicitOutputsFunction) {
       return new Rule(
           pkg,
@@ -1263,7 +1262,7 @@
           ruleClass,
           location,
           callStackBuilder.of(callstack),
-          attributeContainer,
+          new AttributeContainer(ruleClass),
           implicitOutputsFunction);
     }
 
@@ -1514,7 +1513,8 @@
         }
 
         // "test_suite" rules have the idiosyncratic semantics of implicitly
-        // depending on all tests in the package, iff tests=[] and suites=[].
+        // depending on all tests in the package, iff tests=[] and suites=[],
+        // which is about 20% of >1M test_suite instances in Google's corpus.
         // Note, we implement this here when the Package is fully constructed,
         // since clearly this information isn't available at Rule construction
         // time, as forward references are permitted.
@@ -1536,7 +1536,13 @@
       if (!implicitTestSuiteRuleInstances.isEmpty()) {
         Collections.sort(labelsOfTestTargets);
         for (Rule rule : implicitTestSuiteRuleInstances) {
-          rule.setAttributeValueByName("$implicit_tests", labelsOfTestTargets);
+          // Pretend the test_suite.$implicit_tests attribute
+          // (which is synthesized during package loading)
+          // is explicitly set so that it appears in query output.
+          rule.setAttributeValue(
+              rule.getRuleClassObject().getAttributeByName("$implicit_tests"),
+              labelsOfTestTargets,
+              /*explicit=*/ true);
         }
       }
       return this;
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index 6d4df0f..2024fa8 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -386,8 +386,7 @@
             ruleClass,
             new BuildLangTypedAttributeValuesMap(kwargs),
             thread.getSemantics(),
-            thread.getCallStack(),
-            new AttributeContainer(ruleClass));
+            thread.getCallStack());
       } catch (RuleFactory.InvalidRuleException | Package.NameConflictException e) {
         throw new EvalException(null, e.getMessage());
       }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Rule.java b/src/main/java/com/google/devtools/build/lib/packages/Rule.java
index 406e28f..b89ffcf 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Rule.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Rule.java
@@ -127,10 +127,6 @@
     attributes.setAttributeValue(attribute, value, explicit);
   }
 
-  void setAttributeValueByName(String attrName, Object value) {
-    attributes.setAttributeValueByName(attrName, value);
-  }
-
   void setContainsErrors() {
     this.containsErrors = true;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index e83d4c4..aad0239 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -1784,11 +1784,11 @@
 
   /**
    * Returns the default function for determining the set of implicit outputs generated by a given
-   * rule. If not otherwise specified, this will be the implementation used by {@link Rule}s
-   * created with this {@link RuleClass}.
+   * rule. If not otherwise specified, this will be the implementation used by {@link Rule}s created
+   * with this {@link RuleClass}.
    *
-   * <p>Do not use this value to calculate implicit outputs for a rule, instead use
-   * {@link Rule#getImplicitOutputsFunction()}.
+   * <p>Do not use this value to calculate implicit outputs for a rule, instead use {@link
+   * Rule#getImplicitOutputsFunction()}.
    *
    * <p>An implicit output is an OutputFile that automatically comes into existence when a rule of
    * this class is declared, and whose name is derived from the name of the rule.
@@ -1796,7 +1796,7 @@
    * <p>Implicit outputs are a widely-relied upon. All ".so", and "_deploy.jar" targets referenced
    * in BUILD files are examples.
    */
-  @VisibleForTesting
+  // (public for serialization)
   public ImplicitOutputsFunction getDefaultImplicitOutputsFunction() {
     return implicitOutputsFunction;
   }
@@ -1984,10 +1984,10 @@
       EventHandler eventHandler,
       Location location,
       List<StarlarkThread.CallStackEntry> callstack,
-      AttributeContainer attributeContainer,
       boolean checkThirdPartyRulesHaveLicenses)
       throws LabelSyntaxException, InterruptedException, CannotPrecomputeDefaultsException {
-    Rule rule = pkgBuilder.createRule(ruleLabel, this, location, callstack, attributeContainer);
+    Rule rule =
+        pkgBuilder.createRule(ruleLabel, this, location, callstack, new AttributeContainer(this));
     populateRuleAttributeValues(rule, pkgBuilder, attributeValues, eventHandler);
     checkAspectAllowedValues(rule, eventHandler);
     rule.populateOutputFiles(eventHandler, pkgBuilder);
@@ -2022,12 +2022,10 @@
       AttributeValues<T> attributeValues,
       Location location,
       List<StarlarkThread.CallStackEntry> callstack,
-      AttributeContainer attributeContainer,
       ImplicitOutputsFunction implicitOutputsFunction)
       throws InterruptedException, CannotPrecomputeDefaultsException {
     Rule rule =
-        pkgBuilder.createRule(
-            ruleLabel, this, location, callstack, attributeContainer, implicitOutputsFunction);
+        pkgBuilder.createRule(ruleLabel, this, location, callstack, implicitOutputsFunction);
     populateRuleAttributeValues(rule, pkgBuilder, attributeValues, NullEventHandler.INSTANCE);
     rule.populateOutputFilesUnchecked(NullEventHandler.INSTANCE, pkgBuilder);
     return rule;
@@ -2046,6 +2044,37 @@
       AttributeValues<T> attributeValues,
       EventHandler eventHandler)
       throws InterruptedException, CannotPrecomputeDefaultsException {
+
+    // TODO(b/157751486): opt: optimize attribute representation.
+    //
+    // Conceptually, the attribute values of a rule instance are a mapping
+    // from each attribute name to its value plus a boolean indicating
+    // whether it was set explicitly, equivalent to a table of triples:
+    //
+    //    (Attribute attr, Object value, bool explicit)
+    //
+    // (Attributes may be represented by name, Attribute reference, or by
+    // a small integer, because the RuleClass provides constant-time
+    // conversions between all three.)
+    //
+    // Empirically, about half of all rule attribute values are equal to
+    // the default value trivially provided by the RuleClass.
+    // To save space, don't record these values; instead, record only the
+    // explicitly provided ones, plus those whose defaults were non-trivially
+    // computed. (Because of the latter category, we must record the
+    // 'explicit' boolean.)
+    //
+    // One possible representation would be an Object[] array of length
+    // n, for the values sorted by ascending index, and a byte[] of length
+    // n bytes + n bits, the bytes being the indices and the bits being
+    // the 'explicit' flags. Lookup would require binary search over
+    // the bytes.
+    //
+    // Currently, the attributes are queried even as they are under
+    // construction (see checkAllowedValues), but that check could be moved after table
+    // construction, like
+    // checkForDuplicateLabels.
+
     BitSet definedAttrIndices =
         populateDefinedRuleAttributeValues(
             rule,
@@ -2096,7 +2125,7 @@
       Attribute attr = getAttribute(attrIndex);
 
       if (attributeName.equals("licenses") && ignoreLicenses) {
-        setRuleAttributeValue(rule, eventHandler, attr, License.NO_LICENSE, /*explicit=*/ false);
+        rule.setAttributeValue(attr, License.NO_LICENSE, /*explicit=*/ false);
         definedAttrIndices.set(attrIndex);
         continue;
       }
@@ -2115,8 +2144,25 @@
         nativeAttributeValue = attributeValue;
       }
 
+      // visibility is additionally recorded by rule.setVisibility.
+      if (attr.getName().equals("visibility")) {
+        @SuppressWarnings("unchecked")
+        List<Label> vis = (List<Label>) nativeAttributeValue;
+        if (!vis.isEmpty() && vis.get(0).equals(ConstantRuleVisibility.LEGACY_PUBLIC_LABEL)) {
+          rule.reportError(
+              rule.getLabel() + ": //visibility:legacy_public only allowed in package declaration",
+              eventHandler);
+        }
+        try {
+          rule.setVisibility(PackageUtils.getVisibility(rule.getLabel(), vis));
+        } catch (EvalException e) {
+          rule.reportError(rule.getLabel() + " " + e.getMessage(), eventHandler);
+        }
+      }
+
       boolean explicit = attributeValues.isExplicitlySpecified(attributeAccessor);
-      setRuleAttributeValue(rule, eventHandler, attr, nativeAttributeValue, explicit);
+      rule.setAttributeValue(attr, nativeAttributeValue, explicit);
+      checkAllowedValues(rule, attr, eventHandler);
       definedAttrIndices.set(attrIndex);
     }
     return definedAttrIndices;
@@ -2152,19 +2198,38 @@
             eventHandler);
       }
 
-      if (attr.getName().equals("licenses") && ignoreLicenses) {
-        rule.setAttributeValue(attr, License.NO_LICENSE, /*explicit=*/ false);
-      } else if (attr.hasComputedDefault()) {
+      // We must check both the name and the type of each attribute below in case a Starlark rule
+      // defines a licenses or distributions attribute of another type.
+
+      if (attr.hasComputedDefault()) {
         // Note that it is necessary to set all non-computed default values before calling
         // Attribute#getDefaultValue for computed default attributes. Computed default attributes
         // may have a condition predicate (i.e. the predicate returned by Attribute#getCondition)
         // that depends on non-computed default attribute values, and that condition predicate is
         // evaluated by the call to Attribute#getDefaultValue.
         attrsWithComputedDefaults.add(attr);
+
       } else if (attr.isLateBound()) {
         rule.setAttributeValue(attr, attr.getLateBoundDefault(), /*explicit=*/ false);
+
+      } else if (attr.getName().equals("applicable_licenses")
+          && attr.getType() == BuildType.LICENSE) {
+        // TODO(b/149505729): Determine the right semantics for someone trying to define their own
+        // attribute named applicable_licenses.
+        rule.setAttributeValue(
+            attr, pkgBuilder.getDefaultApplicableLicenses(), /*explicit=*/ false);
+
+      } else if (attr.getName().equals("licenses") && attr.getType() == BuildType.LICENSE) {
+        rule.setAttributeValue(
+            attr,
+            ignoreLicenses ? License.NO_LICENSE : pkgBuilder.getDefaultLicense(),
+            /*explicit=*/ false);
+
+      } else if (attr.getName().equals("distribs") && attr.getType() == BuildType.DISTRIBUTIONS) {
+        rule.setAttributeValue(attr, pkgBuilder.getDefaultDistribs(), /*explicit=*/ false);
+
       } else {
-        Object defaultValue = getAttributeNoncomputedDefaultValue(attr, pkgBuilder);
+        Object defaultValue = attr.getDefaultValue(/*rule=*/ null);
         rule.setAttributeValue(attr, defaultValue, /*explicit=*/ false);
         checkAllowedValues(rule, attr, eventHandler);
       }
@@ -2339,69 +2404,6 @@
   }
 
   /**
-   * Returns the default value for the specified rule attribute.
-   *
-   * <p>For most rule attributes, the default value is either explicitly specified
-   * in the attribute, or implicitly based on the type of the attribute, except
-   * for some special cases (e.g. "licenses", "distribs") where it comes from
-   * some other source, such as state in the package.
-   *
-   * <p>Precondition: {@code !attr.hasComputedDefault()}.  (Computed defaults are
-   * evaluated in second pass.)
-   */
-  private static Object getAttributeNoncomputedDefaultValue(Attribute attr,
-      Package.Builder pkgBuilder) {
-    // TODO(b/149505729): Determine the right semantics for someone trying to define their own
-    // attribute named applicable_licenses.
-    if (attr.getName().equals("applicable_licenses")) {
-      return pkgBuilder.getDefaultApplicableLicenses();
-    }
-    // Starlark rules may define their own "licenses" attributes with different types -
-    // we shouldn't trigger the special "licenses" on those cases.
-    if (attr.getName().equals("licenses") && attr.getType() == BuildType.LICENSE) {
-      return pkgBuilder.getDefaultLicense();
-    }
-    if (attr.getName().equals("distribs")) {
-      return pkgBuilder.getDefaultDistribs();
-    }
-    return attr.getDefaultValue(null);
-  }
-
-  /**
-   * Sets the value of attribute {@code attr} in {@code rule} to the native value {@code
-   * nativeAttrVal}, and sets the value's explicitness to {@code explicit}.
-   *
-   * <p>Handles the special case of the "visibility" attribute by also setting the rule's
-   * visibility with {@link Rule#setVisibility}.
-   *
-   * <p>Checks that {@code nativeAttrVal} is an allowed value via {@link #checkAllowedValues}.
-   */
-  private static void setRuleAttributeValue(
-      Rule rule,
-      EventHandler eventHandler,
-      Attribute attr,
-      Object nativeAttrVal,
-      boolean explicit) {
-    if (attr.getName().equals("visibility")) {
-      @SuppressWarnings("unchecked")
-      List<Label> attrList = (List<Label>) nativeAttrVal;
-      if (!attrList.isEmpty()
-          && ConstantRuleVisibility.LEGACY_PUBLIC_LABEL.equals(attrList.get(0))) {
-        rule.reportError(
-            rule.getLabel() + ": //visibility:legacy_public only allowed in package declaration",
-            eventHandler);
-      }
-      try {
-        rule.setVisibility(PackageUtils.getVisibility(rule.getLabel(), attrList));
-      } catch (EvalException e) {
-         rule.reportError(rule.getLabel() + " " + e.getMessage(), eventHandler);
-      }
-    }
-    rule.setAttributeValue(attr, nativeAttrVal, explicit);
-    checkAllowedValues(rule, attr, eventHandler);
-  }
-
-  /**
    * Converts the build-language-typed {@code buildLangValue} to a native value via {@link
    * BuildType#selectableConvert}. Canonicalizes the value's order if it is a {@link List} type and
    * {@code attr.isOrderIndependent()} returns {@code true}.
@@ -2465,7 +2467,6 @@
     }
   }
 
-
   /**
    * Verifies that the rule has a valid value for the attribute according to its allowed values.
    *
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java b/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java
index b579056..0668a78 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java
@@ -78,8 +78,7 @@
       BuildLangTypedAttributeValuesMap attributeValues,
       EventHandler eventHandler,
       StarlarkSemantics semantics,
-      ImmutableList<StarlarkThread.CallStackEntry> callstack,
-      AttributeContainer attributeContainer)
+      ImmutableList<StarlarkThread.CallStackEntry> callstack)
       throws InvalidRuleException, InterruptedException {
     Preconditions.checkNotNull(ruleClass);
     String ruleClassName = ruleClass.getName();
@@ -133,7 +132,6 @@
           eventHandler,
           generator.location, // see b/23974287 for rationale
           callstack,
-          attributeContainer,
           checkThirdPartyLicenses);
     } catch (LabelSyntaxException | CannotPrecomputeDefaultsException e) {
       throw new RuleFactory.InvalidRuleException(ruleClass + " " + e.getMessage());
@@ -154,31 +152,22 @@
    *     creation
    * @param semantics the Starlark semantics
    * @param callstack the stack of active calls in the Starlark thread
-   * @param attributeContainer the {@link AttributeContainer} the rule will contain
    * @throws InvalidRuleException if the rule could not be constructed for any reason (e.g. no
    *     {@code name} attribute is defined)
    * @throws NameConflictException if the rule's name or output files conflict with others in this
    *     package
    * @throws InterruptedException if interrupted
    */
-  static Rule createAndAddRule(
+  static Rule createAndAddRuleImpl(
       Package.Builder pkgBuilder,
       RuleClass ruleClass,
       BuildLangTypedAttributeValuesMap attributeValues,
       EventHandler eventHandler,
       StarlarkSemantics semantics,
-      ImmutableList<StarlarkThread.CallStackEntry> callstack,
-      AttributeContainer attributeContainer)
+      ImmutableList<StarlarkThread.CallStackEntry> callstack)
       throws InvalidRuleException, NameConflictException, InterruptedException {
     Rule rule =
-        createRule(
-            pkgBuilder,
-            ruleClass,
-            attributeValues,
-            eventHandler,
-            semantics,
-            callstack,
-            attributeContainer);
+        createRule(pkgBuilder, ruleClass, attributeValues, eventHandler, semantics, callstack);
     pkgBuilder.addRule(rule);
     return rule;
   }
@@ -195,7 +184,6 @@
    *     a map entry for each non-optional attribute of this class of rule.
    * @param loc the location of the rule expression
    * @param thread the lexical environment of the function call which declared this rule (optional)
-   * @param attributeContainer the {@link AttributeContainer} the rule will contain
    * @throws InvalidRuleException if the rule could not be constructed for any reason (e.g. no
    *     {@code name} attribute is defined)
    * @throws NameConflictException if the rule's name or output files conflict with others in this
@@ -207,17 +195,10 @@
       RuleClass ruleClass,
       BuildLangTypedAttributeValuesMap attributeValues,
       StarlarkSemantics semantics,
-      ImmutableList<StarlarkThread.CallStackEntry> callstack,
-      AttributeContainer attributeContainer)
+      ImmutableList<StarlarkThread.CallStackEntry> callstack)
       throws InvalidRuleException, NameConflictException, InterruptedException {
-    return createAndAddRule(
-        context.pkgBuilder,
-        ruleClass,
-        attributeValues,
-        context.eventHandler,
-        semantics,
-        callstack,
-        attributeContainer);
+    return createAndAddRuleImpl(
+        context.pkgBuilder, ruleClass, attributeValues, context.eventHandler, semantics, callstack);
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactoryHelper.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactoryHelper.java
index 4c51fb4..f0682e7 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactoryHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactoryHelper.java
@@ -47,28 +47,14 @@
     StoredEventHandler eventHandler = new StoredEventHandler();
     BuildLangTypedAttributeValuesMap attributeValues = new BuildLangTypedAttributeValuesMap(kwargs);
     Rule rule =
-        RuleFactory.createRule(
-            pkg,
-            ruleClass,
-            attributeValues,
-            eventHandler,
-            semantics,
-            callstack,
-            new AttributeContainer(ruleClass));
+        RuleFactory.createRule(pkg, ruleClass, attributeValues, eventHandler, semantics, callstack);
     pkg.addEvents(eventHandler.getEvents());
     pkg.addPosts(eventHandler.getPosts());
     overwriteRule(pkg, rule);
     for (Map.Entry<String, Label> entry :
         ruleClass.getExternalBindingsFunction().apply(rule).entrySet()) {
       Label nameLabel = Label.parseAbsolute("//external:" + entry.getKey(), ImmutableMap.of());
-      addBindRule(
-          pkg,
-          bindRuleClass,
-          nameLabel,
-          entry.getValue(),
-          semantics,
-          callstack,
-          new AttributeContainer(bindRuleClass));
+      addBindRule(pkg, bindRuleClass, nameLabel, entry.getValue(), semantics, callstack);
     }
     return rule;
   }
@@ -138,8 +124,7 @@
       Label virtual,
       Label actual,
       StarlarkSemantics semantics,
-      ImmutableList<StarlarkThread.CallStackEntry> callstack,
-      AttributeContainer attributeContainer)
+      ImmutableList<StarlarkThread.CallStackEntry> callstack)
       throws RuleFactory.InvalidRuleException, Package.NameConflictException, InterruptedException {
 
     Map<String, Object> attributes = Maps.newHashMap();
@@ -153,8 +138,7 @@
     BuildLangTypedAttributeValuesMap attributeValues =
         new BuildLangTypedAttributeValuesMap(attributes);
     Rule rule =
-        RuleFactory.createRule(
-            pkg, bindRuleClass, attributeValues, handler, semantics, callstack, attributeContainer);
+        RuleFactory.createRule(pkg, bindRuleClass, attributeValues, handler, semantics, callstack);
     overwriteRule(pkg, rule);
     rule.setVisibility(ConstantRuleVisibility.PUBLIC);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceGlobals.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceGlobals.java
index f76dd8a..47e344d 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceGlobals.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceGlobals.java
@@ -284,8 +284,7 @@
           nameLabel,
           actual == NONE ? null : Label.parseAbsolute((String) actual, ImmutableMap.of()),
           thread.getSemantics(),
-          thread.getCallStack(),
-          new AttributeContainer(ruleClass));
+          thread.getCallStack());
     } catch (InvalidRuleException | Package.NameConflictException | LabelSyntaxException e) {
       throw Starlark.errorf("%s", e.getMessage());
     }
diff --git a/src/test/java/com/google/devtools/build/lib/packages/AttributeContainerTest.java b/src/test/java/com/google/devtools/build/lib/packages/AttributeContainerTest.java
index a632f10..57b12e0 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/AttributeContainerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/AttributeContainerTest.java
@@ -45,25 +45,17 @@
   }
 
   @Test
-  public void testAttributeSettingAndRetrievalByName() throws Exception {
+  public void testAttributeSettingAndRetrieval() throws Exception {
     Object someValue1 = new Object();
     Object someValue2 = new Object();
-    container.setAttributeValueByName(attribute1.getName(), someValue1);
-    container.setAttributeValueByName(attribute2.getName(), someValue2);
+    container.setAttributeValue(attribute1, someValue1, /*explicit=*/ true);
+    container.setAttributeValue(attribute2, someValue2, /*explicit=*/ true);
     assertThat(container.getAttr(attribute1.getName())).isEqualTo(someValue1);
     assertThat(container.getAttr(attribute2.getName())).isEqualTo(someValue2);
     assertThat(container.getAttr("nomatch")).isNull();
   }
 
   @Test
-  public void testExplicitSpecificationsByName() throws Exception {
-    // Name-based setters are automatically considered explicit.
-    container.setAttributeValueByName(attribute1.getName(), new Object());
-    assertThat(container.isAttributeValueExplicitlySpecified(attribute1)).isTrue();
-    assertThat(container.isAttributeValueExplicitlySpecified("nomatch")).isFalse();
-  }
-
-  @Test
   public void testExplicitSpecificationsByInstance() throws Exception {
     Object someValue = new Object();
     container.setAttributeValue(attribute1, someValue, true);
diff --git a/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java b/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java
index fc9477f..b50181e 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java
@@ -800,7 +800,6 @@
         reporter,
         location,
         callstack,
-        new AttributeContainer(ruleClass),
         /*checkThirdPartyRulesHaveLicenses=*/ true);
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/packages/RuleFactoryTest.java b/src/test/java/com/google/devtools/build/lib/packages/RuleFactoryTest.java
index f89a2df..403b402 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/RuleFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/RuleFactoryTest.java
@@ -70,14 +70,13 @@
 
     RuleClass ruleClass = provider.getRuleClassMap().get("cc_library");
     Rule rule =
-        RuleFactory.createAndAddRule(
+        RuleFactory.createAndAddRuleImpl(
             pkgBuilder,
             ruleClass,
             new BuildLangTypedAttributeValuesMap(attributeValues),
             new Reporter(new EventBus()),
             StarlarkSemantics.DEFAULT,
-            DUMMY_STACK,
-            new AttributeContainer(ruleClass));
+            DUMMY_STACK);
 
     assertThat(rule.getAssociatedRule()).isSameInstanceAs(rule);
 
@@ -128,14 +127,13 @@
 
     RuleClass ruleClass = provider.getRuleClassMap().get("bind");
     Rule rule =
-        RuleFactory.createAndAddRule(
+        RuleFactory.createAndAddRuleImpl(
             pkgBuilder,
             ruleClass,
             new BuildLangTypedAttributeValuesMap(attributeValues),
             new Reporter(new EventBus()),
             StarlarkSemantics.DEFAULT,
-            DUMMY_STACK,
-            new AttributeContainer(ruleClass));
+            DUMMY_STACK);
     assertThat(rule.containsErrors()).isFalse();
   }
 
@@ -157,14 +155,13 @@
         assertThrows(
             RuleFactory.InvalidRuleException.class,
             () ->
-                RuleFactory.createAndAddRule(
+                RuleFactory.createAndAddRuleImpl(
                     pkgBuilder,
                     ruleClass,
                     new BuildLangTypedAttributeValuesMap(attributeValues),
                     new Reporter(new EventBus()),
                     StarlarkSemantics.DEFAULT,
-                    DUMMY_STACK,
-                    new AttributeContainer(ruleClass)));
+                    DUMMY_STACK));
     assertThat(e).hasMessageThat().contains("must be in the WORKSPACE file");
   }
 
@@ -186,14 +183,13 @@
         assertThrows(
             RuleFactory.InvalidRuleException.class,
             () ->
-                RuleFactory.createAndAddRule(
+                RuleFactory.createAndAddRuleImpl(
                     pkgBuilder,
                     ruleClass,
                     new BuildLangTypedAttributeValuesMap(attributeValues),
                     new Reporter(new EventBus()),
                     StarlarkSemantics.DEFAULT,
-                    DUMMY_STACK,
-                    new AttributeContainer(ruleClass)));
+                    DUMMY_STACK));
     assertThat(e).hasMessageThat().contains("cannot be in the WORKSPACE file");
   }
 
@@ -227,14 +223,13 @@
         assertThrows(
             RuleFactory.InvalidRuleException.class,
             () ->
-                RuleFactory.createAndAddRule(
+                RuleFactory.createAndAddRuleImpl(
                     pkgBuilder,
                     ruleClass,
                     new BuildLangTypedAttributeValuesMap(attributeValues),
                     new Reporter(new EventBus()),
                     StarlarkSemantics.DEFAULT,
-                    DUMMY_STACK,
-                    new AttributeContainer(ruleClass)));
+                    DUMMY_STACK));
     assertWithMessage(e.getMessage())
         .that(e.getMessage().contains("output file name can't be equal '.'"))
         .isTrue();