Info-related cleanups
- Reorder Info methods for consistency with ClassObject
- "StructConstructor" -> "StructProvider"
- Added javadoc
RELNOTES: None
PiperOrigin-RevId: 181469643
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
index b398073..a40bacb 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
@@ -268,7 +268,7 @@
SkylarkRuleContext context, RuleConfiguredTargetBuilder builder, Object target, Location loc)
throws EvalException {
- Info oldStyleProviders = NativeProvider.STRUCT.create(loc);
+ Info oldStyleProviders = NativeProvider.STRUCT.createEmpty(loc);
ArrayList<Info> declaredProviders = new ArrayList<>();
if (target instanceof Info) {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Info.java b/src/main/java/com/google/devtools/build/lib/packages/Info.java
index b885d65..b3ed563 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Info.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Info.java
@@ -99,6 +99,8 @@
/**
* Preprocesses a map of field values to convert the field names and field values to
* Skylark-acceptable names and types.
+ *
+ * <p>This preserves the order of the map entries.
*/
protected static ImmutableMap<String, Object> copyValues(Map<String, Object> values) {
ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
@@ -111,23 +113,6 @@
}
/**
- * Returns whether the given field name exists.
- *
- * <p>This conceptually extends the API for {@link ClassObject}.
- */
- public abstract boolean hasField(String name);
-
- /** Returns a value and try to cast it into specified type */
- public <T> T getValue(String key, Class<T> type) throws EvalException {
- Object obj = getValue(key);
- if (obj == null) {
- return null;
- }
- SkylarkType.checkType(obj, type, key);
- return type.cast(obj);
- }
-
- /**
* Returns the Skylark location where this provider instance was created.
*
* <p>Builtin provider instances may return {@link Location#BUILTIN}.
@@ -141,25 +126,44 @@
}
/**
- * Returns the fields of this struct.
+ * Returns whether the given field name exists.
*
- * Overrides {@link ClassObject#getFieldNames()}, but does not allow {@link EvalException} to
- * be thrown.
+ * <p>This conceptually extends the API for {@link ClassObject}.
*/
- @Override
- public abstract ImmutableCollection<String> getFieldNames();
+ public abstract boolean hasField(String name);
/**
- * Returns the value associated with the name field in this struct,
- * or null if the field does not exist.
+ * {@inheritDoc}
*
- * Overrides {@link ClassObject#getValue(String)}, but does not allow {@link EvalException} to
+ * <p>Overrides {@link ClassObject#getValue(String)}, but does not allow {@link EvalException} to
* be thrown.
*/
@Nullable
@Override
public abstract Object getValue(String name);
+ /**
+ * Returns the result of {@link #getValue(String)}, cast as the given type, throwing {@link
+ * EvalException} if the cast fails.
+ */
+ public <T> T getValue(String key, Class<T> type) throws EvalException {
+ Object obj = getValue(key);
+ if (obj == null) {
+ return null;
+ }
+ SkylarkType.checkType(obj, type, key);
+ return type.cast(obj);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Overrides {@link ClassObject#getFieldNames()}, but does not allow {@link EvalException} to
+ * be thrown.
+ */
+ @Override
+ public abstract ImmutableCollection<String> getFieldNames();
+
@Override
public String getErrorMessageForUnknownField(String name) {
String suffix = "Available attributes: "
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 0f78e46..22a4eaa 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
@@ -45,7 +45,7 @@
private final String errorMessageFormatForUnknownField;
/** "struct" function. */
- public static final StructConstructor STRUCT = new StructConstructor();
+ public static final StructProvider STRUCT = new StructProvider();
private final Class<V> valueClass;
@@ -61,17 +61,17 @@
* Skylark code.
*/
@Deprecated
- public static interface WithLegacySkylarkName {
+ public interface WithLegacySkylarkName {
String getSkylarkName();
}
/**
- * A constructor for default {@code struct}s.
+ * The provider for the built-in type {@code struct}.
*
- * <p>Singleton, instance is {@link #STRUCT}.
+ * <p>Its singleton instance is {@link #STRUCT}.
*/
- public static final class StructConstructor extends NativeProvider<Info> {
- private StructConstructor() {
+ public static final class StructProvider extends NativeProvider<Info> {
+ private StructProvider() {
super(Info.class, "struct");
}
@@ -82,11 +82,19 @@
return SkylarkInfo.fromMap(this, kwargs, loc);
}
- public Info create(Map<String, Object> values, String message) {
- return new SkylarkInfo.MapBackedSkylarkInfo(this, values, message);
+ /**
+ * Creates a struct with the he given field values and message format for unknown fields.
+ *
+ * <p>The custom message is useful for objects that have fields but aren't exactly used as
+ * providers, such as the {@code native} object, and the struct fields of {@code ctx} like
+ * {@code ctx.attr}.
+ * */
+ public Info create(Map<String, Object> values, String errorMessageFormatForUnknownField) {
+ return new SkylarkInfo.MapBackedSkylarkInfo(this, values, errorMessageFormatForUnknownField);
}
- public Info create(Location loc) {
+ /** Creates an empty struct with the given location. */
+ public Info createEmpty(Location loc) {
return SkylarkInfo.fromMap(this, ImmutableMap.of(), loc);
}
}
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 cc5d3ea..15e5b5e 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
@@ -11,6 +11,7 @@
// 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.Joiner;
@@ -21,22 +22,28 @@
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.google.devtools.build.lib.events.Location;
-import com.google.devtools.build.lib.packages.NativeProvider.StructConstructor;
+import com.google.devtools.build.lib.packages.NativeProvider.StructProvider;
import com.google.devtools.build.lib.syntax.Concatable;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalUtils;
import java.util.Arrays;
import java.util.Map;
-/** Implementation of {@link Info} created from Skylark. */
+/**
+ * A provider instance of either 1) a Skylark-defined provider ({@link SkylarkInfo}), or 2) the
+ * built-in "struct" type ({@link NativeProvider#STRUCT}).
+ *
+ * <p>There are two concrete subclasses corresponding to two different implementation strategies.
+ * One is map-based and schemaless, the other has a fixed layout and is more memory-efficient.
+ */
public abstract class SkylarkInfo extends Info implements Concatable {
public SkylarkInfo(Provider provider, Location loc) {
super(provider, loc);
}
- public SkylarkInfo(StructConstructor provider, String message) {
- super(provider, message);
+ public SkylarkInfo(StructProvider provider, String errorMessageFormatForUnknownField) {
+ super(provider, errorMessageFormatForUnknownField);
}
@Override
@@ -46,7 +53,7 @@
@Override
public boolean isImmutable() {
- // If the provider is not yet exported the hash code of the object is subject to change
+ // If the provider is not yet exported, the hash code of the object is subject to change.
if (!getProvider().isExported()) {
return false;
}
@@ -58,15 +65,21 @@
return true;
}
- /** Return all the values stored in the object. */
+ /**
+ * Returns all the field values stored in the object, in the canonical order.
+ *
+ * <p>{@code protected} because this is only used for {@link #isImmutable}. It saves us having to
+ * get values one-by-one.
+ */
protected abstract Iterable<Object> getValues();
/**
- * {@link SkylarkInfo} implementation that stores its values in a map. This is mainly used for the
- * Skylark {@code struct()} constructor.
+ * A {@link SkylarkInfo} implementation that stores its values in a map. This is used for structs
+ * and for schemaless Skylark-defined providers.
*/
static final class MapBackedSkylarkInfo extends SkylarkInfo {
- protected final ImmutableMap<String, Object> values;
+
+ private final ImmutableMap<String, Object> values;
public MapBackedSkylarkInfo(Provider provider, Map<String, Object> kwargs, Location loc) {
super(provider, loc);
@@ -74,22 +87,24 @@
}
public MapBackedSkylarkInfo(
- StructConstructor provider, Map<String, Object> values, String message) {
- super(provider, message);
+ StructProvider provider,
+ Map<String, Object> values,
+ String errorMessageFormatForUnknownField) {
+ super(provider, errorMessageFormatForUnknownField);
this.values = copyValues(values);
}
@Override
- public Object getValue(String name) {
- return values.get(name);
- }
-
- @Override
public boolean hasField(String name) {
return values.containsKey(name);
}
@Override
+ public Object getValue(String name) {
+ return values.get(name);
+ }
+
+ @Override
public ImmutableCollection<String> getFieldNames() {
return values.keySet();
}
@@ -163,6 +178,7 @@
@Override
public Concatable concat(Concatable left, Concatable right, Location loc) throws EvalException {
+ // Casts are safe because SkylarkInfoConcatter is only used by SkylarkInfo.
SkylarkInfo lval = (SkylarkInfo) left;
SkylarkInfo rval = (SkylarkInfo) right;
Provider provider = lval.getProvider();
@@ -170,7 +186,7 @@
throw new EvalException(
loc,
String.format(
- "Cannot concat %s with %s",
+ "Cannot use '+' operator on instances of different providers (%s and %s)",
provider.getPrintableName(), rval.getProvider().getPrintableName()));
}
SetView<String> commonFields =
@@ -179,7 +195,8 @@
if (!commonFields.isEmpty()) {
throw new EvalException(
loc,
- "Cannot concat structs with common field(s): " + Joiner.on(",").join(commonFields));
+ "Cannot use '+' operator on provider instances with overlapping field(s): "
+ + Joiner.on(",").join(commonFields));
}
// Keep homogeneous compact concatenations compact.
if (lval instanceof CompactSkylarkInfo
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
index 4efa55a..bcd42e8 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
@@ -1059,7 +1059,7 @@
@Test
public void testStructConcatenationCommonFields() throws Exception {
- checkErrorContains("Cannot concat structs with common field(s): a",
+ checkErrorContains("Cannot use '+' operator on provider instances with overlapping field(s): a",
"x = struct(a = 1, b = 2)", "y = struct(c = 1, a = 2)", "z = x + y\n");
}
@@ -1247,7 +1247,7 @@
"data2 = provider()"
);
- checkEvalError("Cannot concat data1 with data2",
+ checkEvalError("Cannot use '+' operator on instances of different providers (data1 and data2)",
"d1 = data1(x = 1)",
"d2 = data2(y = 2)",
"d = d1 + d2"
@@ -1271,7 +1271,7 @@
public void declaredProvidersWithFieldsConcatError() throws Exception {
evalAndExport("data1 = provider(fields=['f1', 'f2'])", "data2 = provider(fields=['f3'])");
checkEvalError(
- "Cannot concat data1 with data2",
+ "Cannot use '+' operator on instances of different providers (data1 and data2)",
"d1 = data1(f1=1, f2=2)",
"d2 = data2(f3=3)",
"d = d1 + d2");
@@ -1281,7 +1281,7 @@
public void declaredProvidersWithOverlappingFieldsConcatError() throws Exception {
evalAndExport("data = provider(fields=['f1', 'f2'])");
checkEvalError(
- "Cannot concat structs with common field(s): f1",
+ "Cannot use '+' operator on provider instances with overlapping field(s): f1",
"d1 = data(f1 = 4)",
"d2 = data(f1 = 5)",
"d1 + d2");