Remove unknownFieldError from StarlarkInfo

The change saves additional 0.05% retained heap on some builds.

PiperOrigin-RevId: 501481920
Change-Id: I1de431385b8065d79f847b2eceafb393d6f6ae41
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkInfo.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkInfo.java
index 01ae01b..ac0ed5a 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkInfo.java
@@ -15,7 +15,6 @@
 package com.google.devtools.build.lib.packages;
 
 import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -30,22 +29,13 @@
 import net.starlark.java.syntax.TokenKind;
 
 /** An struct-like Info (provider instance) for providers defined in Starlark. */
-public final class StarlarkInfo extends StructImpl implements HasBinary {
+public class StarlarkInfo extends StructImpl implements HasBinary {
   private final Provider provider;
 
   // For a n-element info, the table contains n key strings, sorted,
   // followed by the n corresponding legal Starlark values.
   private final Object[] table;
 
-  // A format string with one %s placeholder for the missing field name.
-  // If null, uses the default format specified by the provider.
-  // TODO(adonovan): make the provider determine the error message
-  // (but: this has implications for struct+struct, the equivalence
-  // relation, and other observable behaviors).
-  // Perhaps it should be a property of the StarlarkInfo instance, but
-  // defined by a subclass?
-  @Nullable private final String unknownFieldError;
-
   // TODO(adonovan): restrict type of provider to StarlarkProvider?
   // Do we ever need StarlarkInfos of BuiltinProviders? Such BuiltinProviders could
   // be  moved to Starlark using bzl builtins injection.
@@ -55,15 +45,10 @@
   // The efficient table algorithms would be a nice addition to the Starlark
   // interpreter, to allow other clients to define their own fast structs
   // (or to define a standard one). See also comments at Info about upcoming clean-ups.
-  private StarlarkInfo(
-      Provider provider,
-      Object[] table,
-      @Nullable Location loc,
-      @Nullable String unknownFieldError) {
+  StarlarkInfo(Provider provider, Object[] table, @Nullable Location loc) {
     super(loc);
     this.provider = provider;
     this.table = table;
-    this.unknownFieldError = unknownFieldError;
   }
 
   @Override
@@ -72,7 +57,7 @@
   }
 
   // Converts a map to a table of sorted keys followed by corresponding values.
-  private static Object[] toTable(Map<String, Object> values) {
+  static Object[] toTable(Map<String, Object> values) {
     int n = values.size();
     Object[] table = new Object[n + n];
     int i = 0;
@@ -128,7 +113,7 @@
       }
     }
 
-    return new StarlarkInfo(provider, table, loc, /*unknownFieldError=*/ null);
+    return new StarlarkInfo(provider, table, loc);
   }
 
   // Permutes array elements from alternating keys/values form,
@@ -248,14 +233,6 @@
     return ImmutableList.copyOf(keys);
   }
 
-  /** Returns the per-instance error message, if specified, or the provider's message otherwise. */
-  @Override
-  public String getErrorMessageForUnknownField(String name) {
-    return unknownFieldError != null
-        ? String.format(unknownFieldError, name) + allAttributesSuffix()
-        : super.getErrorMessageForUnknownField(name);
-  }
-
   @Override
   public boolean isImmutable() {
     // If the provider is not yet exported, the hash code of the object is subject to change.
@@ -289,50 +266,7 @@
    */
   public static StarlarkInfo create(
       Provider provider, Map<String, Object> values, @Nullable Location loc) {
-    return new StarlarkInfo(provider, toTable(values), loc, /*unknownFieldError=*/ null);
-  }
-
-  /**
-   * Creates a schemaless provider instance with the given provider type, field values, and
-   * unknown-field error message.
-   *
-   * <p>This is used to create structs for special purposes, such as {@code ctx.attr} and the {@code
-   * native} module. The creation location will be {@link Location#BUILTIN}.
-   *
-   * <p>{@code unknownFieldError} is a string format, as for {@link
-   * Provider#getErrorMessageFormatForUnknownField}.
-   *
-   * @deprecated Do not use this method. Instead, create a new subclass of {@link BuiltinProvider}
-   *     with the desired error message format, and create a corresponding {@link NativeInfo}
-   *     subclass.
-   */
-  // TODO(bazel-team): Make the special structs that need a custom error message use a different
-  // provider (subclassing BuiltinProvider) and a different StructImpl implementation. Then remove
-  // this functionality, thereby saving a string pointer field for the majority of providers that
-  // don't need it. However, this is tricky: if the error message is a property of the provider,
-  // then each flavor of struct must have a distinct provider of a unique class, and this would be
-  // observable to Starlark code. What would be their names: "struct", or something else? Should
-  // struct+struct fail when different flavors are mixed (as happens today when adding info
-  // instances of different providers)? Or should it return a new struct picking the provider of one
-  // operand arbitrarily (as it does today for custom error strings)? Or ignore providers and return
-  // a plain old struct, always? Or only if they differ? Or should we abolish struct+struct
-  // altogether? In other words, the advice in the @deprecated tag above is not compatible.
-  //
-  // brandjon notes: nearly all the uses of custom errors are for objects that properly should be
-  // Structures but not structs. They only leveraged the struct machinery for historical reasons and
-  // convenience.
-  // For instance, ctx.attr should have a custom error message, but should not support concatenation
-  // (it fails today but only because you can't produce two ctx.attr's that don't have common
-  // fields). It also should not support to_json().
-  // It's possible someone was crazy enough to take ctx.attr.to_json(), but we can probably break
-  // that case without causing too much trouble.
-  // If we migrate all these cases of non-providers away, whatever is left should be happy to use a
-  // default error message, and we can eliminate this extra detail.
-  @Deprecated
-  public static StarlarkInfo createWithCustomMessage(
-      Provider provider, Map<String, Object> values, String unknownFieldError) {
-    Preconditions.checkNotNull(unknownFieldError);
-    return new StarlarkInfo(provider, toTable(values), Location.BUILTIN, unknownFieldError);
+    return new StarlarkInfo(provider, toTable(values), loc);
   }
 
   @Nullable
@@ -393,6 +327,6 @@
       zi++;
     }
 
-    return new StarlarkInfo(xprov, ztable, Location.BUILTIN, x.unknownFieldError);
+    return new StarlarkInfo(xprov, ztable, Location.BUILTIN);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkInfoWithMessage.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkInfoWithMessage.java
new file mode 100644
index 0000000..c63e9c9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkInfoWithMessage.java
@@ -0,0 +1,87 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.packages;
+
+import com.google.common.base.Preconditions;
+import java.util.Map;
+import javax.annotation.Nullable;
+import net.starlark.java.syntax.Location;
+
+/**
+ * A {@link StarlarkInfo} that supports custom no-such-field error messages.
+ *
+ * <p>This is used for certain special structs like `ctx.attr`.
+ */
+// TODO(bazel-team): It's questionable whether the use cases for this class should be part of the
+// class hierarchy of StructImpl at all. They really only need to have fields and custom error
+// messages (which are features of the simpler Structure class), not `+` concatenation,
+// proto/json encoding, or provider functionality.
+public final class StarlarkInfoWithMessage extends StarlarkInfo {
+  // A format string with one %s placeholder for the missing field name.
+  // TODO(adonovan): make the provider determine the error message
+  // (but: this has implications for struct+struct, the equivalence
+  // relation, and other observable behaviors).
+  // Perhaps it should be a property of the StarlarkInfo instance, but
+  // defined by a subclass?
+  private final String unknownFieldError;
+
+  private StarlarkInfoWithMessage(
+      Provider provider, Object[] table, @Nullable Location loc, String unknownFieldError) {
+    super(provider, table, loc);
+    this.unknownFieldError = unknownFieldError;
+  }
+
+  /** Returns the per-instance error message, if specified, or the provider's message otherwise. */
+  @Override
+  public String getErrorMessageForUnknownField(String name) {
+    return String.format(unknownFieldError, name) + allAttributesSuffix();
+  }
+
+  /**
+   * Creates a schemaless provider instance with the given provider type, field values, and
+   * unknown-field error message.
+   *
+   * <p>This is used to create structs for special purposes, such as {@code ctx.attr} and the {@code
+   * native} module. The creation location will be {@link Location#BUILTIN}.
+   *
+   * <p>{@code unknownFieldError} is a string format, as for {@link
+   * Provider#getErrorMessageFormatForUnknownField}.
+   *
+   * @deprecated Do not use this method. Instead, create a new subclass of {@link BuiltinProvider}
+   *     with the desired error message format, and create a corresponding {@link NativeInfo}
+   *     subclass.
+   */
+  // TODO(bazel-team): Eliminate the need for this class by migrating the special structs that need
+  // a custom error message to inherit from Structure rather than from the provider machinery. If
+  // there are any use cases where the object is an actual native provider, migrate them to their
+  // own subclasses of BuiltinProvider.
+  //
+  // However, either of these migrations could cause obscure user-visible changes in:
+  //   - the type name ("struct" vs something else)
+  //   - equality (`==`) semantics
+  //   - the availability of the ".to_proto" and ".to_json" methods
+  //   - the availability of the struct concatenation operator (`+`) and the type of its result.
+  //     (Today, concatenation of structs with different error messages is allowed, and the result
+  //     uses the error message of the left-hand side. But maybe this should be disallowed, or maybe
+  //     it should always return a plain struct, or maybe the operator should be abolished
+  //     altogether.)
+  @Deprecated
+  public static StarlarkInfo createWithCustomMessage(
+      Provider provider, Map<String, Object> values, String unknownFieldError) {
+    Preconditions.checkNotNull(unknownFieldError);
+    return new StarlarkInfoWithMessage(
+        provider, toTable(values), Location.BUILTIN, unknownFieldError);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StructProvider.java b/src/main/java/com/google/devtools/build/lib/packages/StructProvider.java
index be24edd..04a02fb 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StructProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StructProvider.java
@@ -57,6 +57,7 @@
    * ctx.attr}.
    */
   public StarlarkInfo create(Map<String, Object> fields, String errorMessageFormatForUnknownField) {
-    return StarlarkInfo.createWithCustomMessage(this, fields, errorMessageFormatForUnknownField);
+    return StarlarkInfoWithMessage.createWithCustomMessage(
+        this, fields, errorMessageFormatForUnknownField);
   }
 }