diff options
author | cparsons <cparsons@google.com> | 2018-02-05 02:01:28 -0800 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-02-05 02:02:56 -0800 |
commit | 6c1c0660dd51420160eb1c0f5eac7e67917a231c (patch) | |
tree | 756d35b5aad7d6f136d44df2450df63e1884733b /src/main/java/com/google/devtools | |
parent | 152b22dd9d817e7f01f88cd8b62928052f0a32df (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')
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 { |