aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools
diff options
context:
space:
mode:
authorGravatar cparsons <cparsons@google.com>2018-02-05 02:01:28 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2018-02-05 02:02:56 -0800
commit6c1c0660dd51420160eb1c0f5eac7e67917a231c (patch)
tree756d35b5aad7d6f136d44df2450df63e1884733b /src/main/java/com/google/devtools
parent152b22dd9d817e7f01f88cd8b62928052f0a32df (diff)
Expose structField callable methods of skylark objects to dir() and str() calls
RELNOTES: None. PiperOrigin-RevId: 184498836
Diffstat (limited to 'src/main/java/com/google/devtools')
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/Info.java24
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/NativeInfo.java28
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java86
3 files changed, 118 insertions, 20 deletions
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 50fcb156c3..33b8c95d13 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
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.packages;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
@@ -108,14 +109,17 @@ public abstract class Info implements ClassObject, SkylarkValue, Serializable {
public abstract boolean hasField(String name);
/**
- * {@inheritDoc}
- *
- * <p>Overrides {@link ClassObject#getValue(String)}, but does not allow {@link EvalException} to
- * be thrown.
+ * <p>Wraps {@link ClassObject#getValue(String)}, returning null in cases where
+ * {@link EvalException} would have been thrown.
*/
- @Nullable
- @Override
- public abstract Object getValue(String name);
+ @VisibleForTesting
+ public Object getValueOrNull(String name) {
+ try {
+ return getValue(name);
+ } catch (EvalException e) {
+ return null;
+ }
+ }
/**
* Returns the result of {@link #getValue(String)}, cast as the given type, throwing {@link
@@ -172,7 +176,7 @@ public abstract class Info implements ClassObject, SkylarkValue, Serializable {
return false;
}
for (String field : getFieldNames()) {
- if (!this.getValue(field).equals(other.getValue(field))) {
+ if (!Objects.equal(this.getValueOrNull(field), other.getValueOrNull(field))) {
return false;
}
}
@@ -187,7 +191,7 @@ public abstract class Info implements ClassObject, SkylarkValue, Serializable {
objectsToHash.add(provider);
for (String field : fields) {
objectsToHash.add(field);
- objectsToHash.add(getValue(field));
+ objectsToHash.add(getValueOrNull(field));
}
return Objects.hashCode(objectsToHash.toArray());
}
@@ -208,7 +212,7 @@ public abstract class Info implements ClassObject, SkylarkValue, Serializable {
first = false;
printer.append(fieldName);
printer.append(" = ");
- printer.repr(getValue(fieldName));
+ printer.repr(getValueOrNull(fieldName));
}
printer.append(")");
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/NativeInfo.java b/src/main/java/com/google/devtools/build/lib/packages/NativeInfo.java
index e1035c86e9..58ea8d3ea0 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/NativeInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/NativeInfo.java
@@ -15,7 +15,11 @@ package com.google.devtools.build.lib.packages;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.FuncallExpression;
+import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor;
import java.util.Map;
/** Base class for native implementations of {@link Info}. */
@@ -23,19 +27,35 @@ import java.util.Map;
public class NativeInfo extends Info {
protected final ImmutableMap<String, Object> values;
+ // Initialized lazily.
+ private ImmutableSet<String> fieldNames;
+
@Override
- public Object getValue(String name) {
- return values.get(name);
+ public Object getValue(String name) throws EvalException {
+ if (values.containsKey(name)) {
+ return values.get(name);
+ } else if (hasField(name)) {
+ MethodDescriptor methodDescriptor = FuncallExpression.getStructField(this.getClass(), name);
+ return FuncallExpression.invokeStructField(methodDescriptor, name, this);
+ } else {
+ return null;
+ }
}
@Override
public boolean hasField(String name) {
- return values.containsKey(name);
+ return getFieldNames().contains(name);
}
@Override
public ImmutableCollection<String> getFieldNames() {
- return values.keySet();
+ if (fieldNames == null) {
+ fieldNames = ImmutableSet.<String>builder()
+ .addAll(values.keySet())
+ .addAll(FuncallExpression.getStructFieldNames(this.getClass()))
+ .build();
+ }
+ return fieldNames;
}
public NativeInfo(NativeProvider<?> provider) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
index 833b20a7fb..241789d268 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
@@ -42,12 +42,14 @@ import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
/**
@@ -134,6 +136,48 @@ public final class FuncallExpression extends Expression {
}
});
+ private static final LoadingCache<Class<?>, Map<String, MethodDescriptor>> fieldCache =
+ CacheBuilder.newBuilder()
+ .initialCapacity(10)
+ .maximumSize(100)
+ .build(
+ new CacheLoader<Class<?>, Map<String, MethodDescriptor>>() {
+
+ @Override
+ public Map<String, MethodDescriptor> load(Class<?> key) throws Exception {
+ ImmutableMap.Builder<String, MethodDescriptor> fieldMap = ImmutableMap.builder();
+ HashSet<String> fieldNamesForCollisions = new HashSet<>();
+ List<MethodDescriptor> fieldMethods =
+ methodCache
+ .get(key)
+ .values()
+ .stream()
+ .flatMap(List::stream)
+ .filter(
+ methodDescriptor -> methodDescriptor.getAnnotation().structField())
+ .collect(Collectors.toList());
+
+ for (MethodDescriptor fieldMethod : fieldMethods) {
+ SkylarkCallable callable = fieldMethod.getAnnotation();
+ String name = callable.name();
+ if (name.isEmpty()) {
+ name =
+ StringUtilities.toPythonStyleFunctionName(
+ fieldMethod.getMethod().getName());
+ }
+ // TODO(b/72113542): Validate with annotation processor instead of at runtime.
+ if (!fieldNamesForCollisions.add(name)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Class %s has two structField methods named %s defined",
+ key.getName(), name));
+ }
+ fieldMap.put(name, fieldMethod);
+ }
+ return fieldMap.build();
+ }
+ });
+
/**
* Returns a map of methods and corresponding SkylarkCallable annotations of the methods of the
* classObj class reachable from Skylark.
@@ -260,15 +304,30 @@ public final class FuncallExpression extends Expression {
return printer.toString();
}
- /**
- * Returns the list of Skylark callable Methods of objClass with the given name and argument
- * number.
- */
+ /** Returns the Skylark callable Method of objClass with structField=true and the given name. */
+ public static MethodDescriptor getStructField(Class<?> objClass, String methodName) {
+ try {
+ return fieldCache.get(objClass).get(methodName);
+ } catch (ExecutionException e) {
+ throw new IllegalStateException("Method loading failed: " + e);
+ }
+ }
+
+ /** Returns the list of names of Skylark callable Methods of objClass with structField=true. */
+ public static Set<String> getStructFieldNames(Class<?> objClass) {
+ try {
+ return fieldCache.get(objClass).keySet();
+ } catch (ExecutionException e) {
+ throw new IllegalStateException("Method loading failed: " + e);
+ }
+ }
+
+ /** Returns the list of Skylark callable Methods of objClass with the given name. */
public static List<MethodDescriptor> getMethods(Class<?> objClass, String methodName) {
try {
return methodCache.get(objClass).get(methodName);
} catch (ExecutionException e) {
- throw new IllegalStateException("method invocation failed: " + e);
+ throw new IllegalStateException("Method loading failed: " + e);
}
}
@@ -280,10 +339,25 @@ public final class FuncallExpression extends Expression {
try {
return methodCache.get(objClass).keySet();
} catch (ExecutionException e) {
- throw new IllegalStateException("method invocation failed: " + e);
+ throw new IllegalStateException("Method loading failed: " + e);
}
}
+ /**
+ * Invokes the given structField=true method and returns the result.
+ *
+ * @param methodDescriptor the descriptor of the method to invoke
+ * @param fieldName the name of the struct field
+ * @param obj the object on which to invoke the method
+ * @return the method return value
+ * @throws EvalException if there was an issue evaluating the method
+ */
+ public static Object invokeStructField(
+ MethodDescriptor methodDescriptor, String fieldName, Object obj) throws EvalException {
+ Preconditions.checkArgument(methodDescriptor.getAnnotation().structField());
+ return callMethod(methodDescriptor, fieldName, obj, new Object[0], Location.BUILTIN, null);
+ }
+
static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
Object[] args, Location loc, Environment env) throws EvalException {
try {