bazel syntax: enforce that ClassObject.getValue returns legal Starlark values
EvalUtils.getAttr now enforces that the value returned by
ClassObject.getValue is a legal value.
Previously, only the ternary SkylarkClassObject.getValue method
offered this postcondition, and it wasn't enforced.
This turned up BuildConfiguration.Fragment,
which was used as a value (e.g. in ctx.fragments.android)
but had forgotten to declare its relationship to StarlarkValue.
Also:
- remove obsolete 'instanceof Class' check.
- remove try/catch of IllegalArgumentException.
Instead do this check with narrower scope in the fragment code.
We will fix other cases as they appear.
PiperOrigin-RevId: 284644428
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index f167e8a..0810a03 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -475,18 +475,25 @@
}
@Nullable
- public Fragment getSkylarkFragment(String name, ConfigurationTransition transition) {
+ public Fragment getSkylarkFragment(String name, ConfigurationTransition transition)
+ throws EvalException {
Class<? extends Fragment> fragmentClass =
getConfiguration(transition).getSkylarkFragmentByName(name);
if (fragmentClass == null) {
return null;
}
- return getFragment(fragmentClass, name,
- String.format(
- " Please update the '%1$sfragments' argument of the rule definition "
- + "(for example: %1$sfragments = [\"%2$s\"])",
- (transition.isHostTransition()) ? "host_" : "", name),
- transition);
+ try {
+ return getFragment(
+ fragmentClass,
+ name,
+ String.format(
+ " Please update the '%1$sfragments' argument of the rule definition "
+ + "(for example: %1$sfragments = [\"%2$s\"])",
+ transition.isHostTransition() ? "host_" : "", name),
+ transition);
+ } catch (IllegalArgumentException ex) { // fishy
+ throw new EvalException(null, ex.getMessage());
+ }
}
public ImmutableCollection<String> getSkylarkFragmentNames(ConfigurationTransition transition) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
index 9b07ff9..5fbd7bd 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -48,6 +48,7 @@
import com.google.devtools.build.lib.skylarkbuildapi.BuildConfigurationApi;
import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
+import com.google.devtools.build.lib.syntax.StarlarkValue;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.util.RegexFilter;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -110,8 +111,10 @@
* <p>All implementations must be immutable and communicate this as clearly as possible (e.g.
* declare {@link ImmutableList} signatures on their interfaces vs. {@link List}). This is because
* fragment instances may be shared across configurations.
+ *
+ * <p>Fragments are Starlark values, as returned by {@code ctx.fragments.android}, for example.
*/
- public abstract static class Fragment {
+ public abstract static class Fragment implements StarlarkValue {
/**
* Validates the options for this Fragment. Issues warnings for the use of deprecated options,
* and warnings or errors for any option settings that conflict.
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/FragmentCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/config/FragmentCollection.java
index d396bcc..11f2ab7 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/FragmentCollection.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/FragmentCollection.java
@@ -19,6 +19,7 @@
import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.skylarkbuildapi.FragmentCollectionApi;
+import com.google.devtools.build.lib.syntax.EvalException;
import javax.annotation.Nullable;
/** Represents a collection of configuration fragments in Skylark. */
@@ -35,7 +36,7 @@
@Override
@Nullable
- public Object getValue(String name) {
+ public Object getValue(String name) throws EvalException {
return ruleContext.getSkylarkFragment(name, transition);
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
index 94c7aff..307e0fd 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
@@ -447,48 +447,37 @@
Eval.setDebugger(dbg);
}
- /** Returns the named field or method of the specified object. */
- static Object getAttr(StarlarkThread thread, Location loc, Object object, String name)
+ /** Returns the named field or method of value {@code x}, or null if not found. */
+ static Object getAttr(StarlarkThread thread, Location loc, Object x, String name)
throws EvalException, InterruptedException {
- MethodDescriptor method =
- object instanceof Class<?>
- ? CallUtils.getMethod(thread.getSemantics(), (Class<?>) object, name)
- : CallUtils.getMethod(thread.getSemantics(), object.getClass(), name);
+ // @SkylarkCallable-annotated field?
+ MethodDescriptor method = CallUtils.getMethod(thread.getSemantics(), x.getClass(), name);
if (method != null && method.isStructField()) {
return method.call(
- object,
+ x,
CallUtils.extraInterpreterArgs(method, /*ast=*/ null, loc, thread).toArray(),
loc,
thread);
}
- if (object instanceof SkylarkClassObject) {
- Object result = null;
- try {
- result = ((SkylarkClassObject) object).getValue(loc, thread.getSemantics(), name);
- } catch (IllegalArgumentException ex) { // TODO(adonovan): why necessary?
- throw new EvalException(loc, ex);
- }
- if (result != null) {
- return result;
- }
-
- } else if (object instanceof ClassObject) {
- Object result = null;
- try {
- result = ((ClassObject) object).getValue(name);
- } catch (IllegalArgumentException ex) {
- throw new EvalException(loc, ex);
- }
- // ClassObjects may have fields that are annotated with @SkylarkCallable.
- // Since getValue() does not know about those, we cannot expect that result is a valid object.
- if (result != null) {
- return Starlark.fromJava(result, thread.mutability());
+ // user-defined field?
+ if (x instanceof ClassObject) {
+ // TODO(adonovan): get rid of SkylarkClassObject once
+ // --incompatible_disable_target_provider_fields goes away,
+ // then remove the thread and loc parameters and publish
+ // this method as Starlark.getattr.
+ Object field =
+ x instanceof SkylarkClassObject
+ ? ((SkylarkClassObject) x).getValue(loc, thread.getSemantics(), name)
+ : ((ClassObject) x).getValue(name);
+ if (field != null) {
+ return Starlark.checkValid(field);
}
}
+ // @SkylarkCallable-annotated method?
if (method != null) {
- return new BuiltinCallable(object, name);
+ return new BuiltinCallable(x, name);
}
return null;
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 1532afe..c71b7a2 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
@@ -16,30 +16,17 @@
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.
- *
- * <p>An value is Skylark-friendly if its class (or a supertype) implements {@link
- * com.google.devtools.build.lib.syntax.StarlarkValue}, is annotated with {@link
- * com.google.devtools.build.lib.skylarkinterface.SkylarkModule}, or is a Skylark primitive like
- * {@link String}.
- *
- * <p>Ideally, this class should not be needed, and all {@link ClassObject}s should only expose
- * Skylark-friendly values.
- */
+/** A variant of ClassObject for implementations that require a StarlarkSemantics. */
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).
+ * @param loc the location of the field access operation
+ * @param semantics the Starlark semantics, which determine the available fields
+ * @param name the name of the field to retrieve
+ * @throws EvalException if the field exists could not be retrieved
*/
@Nullable
- Object getValue(Location loc, StarlarkSemantics starlarkSemantics, String name)
- throws EvalException;
+ Object getValue(Location loc, StarlarkSemantics semantics, String name) throws EvalException;
}