aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax
diff options
context:
space:
mode:
authorGravatar cparsons <cparsons@google.com>2018-04-04 13:59:27 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-04-04 14:01:20 -0700
commit7520dcce42217c8076b06ed88c0e4e04ed99a0f4 (patch)
treef3c8156e096114dabddba39e8ae7ea21d00e2560 /src/main/java/com/google/devtools/build/lib/syntax
parent16e188b99a5b0d07a0976ba5eb6013b3de8befb9 (diff)
Migrate SkylarkNativeModule methods to use @SkylarkCallable instead of @SkylarkSignature
Most notably, this involves introduction of a new function abstraction, BuiltinMethod, which can wrap a {objc, SkylarkCallable} pair into a BaseFunction for later calling. (This is required due to the current layer of indirection on the end "native" module) RELNOTES: None. PiperOrigin-RevId: 191642467
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax')
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java137
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java15
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java86
4 files changed, 226 insertions, 14 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
index e445e46cf8..bad903c3df 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
@@ -487,7 +487,7 @@ public abstract class BaseFunction implements SkylarkValue {
Preconditions.checkState(!isConfigured()); // must not be configured yet
this.paramDoc = new ArrayList<>();
- this.signature = SkylarkSignatureProcessor.getSignature(
+ this.signature = SkylarkSignatureProcessor.getSignatureForCallable(
getName(), annotation, unconfiguredDefaultValues, paramDoc, getEnforcedArgumentTypes());
this.objectType = annotation.objectType().equals(Object.class)
? null : annotation.objectType();
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java
new file mode 100644
index 0000000000..8419e7fc64
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java
@@ -0,0 +1,137 @@
+// Copyright 2018 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.syntax;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.profiler.Profiler;
+import com.google.devtools.build.lib.profiler.ProfilerTask;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
+import com.google.devtools.build.lib.syntax.Environment.LexicalFrame;
+import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * A function-object abstraction on object methods exposed to skylark using {@link SkylarkCallable}.
+ */
+public class BuiltinCallable extends BaseFunction {
+
+ /** Represents a required interpreter parameter as dictated by {@link SkylarkCallable} */
+ public enum ExtraArgKind {
+ LOCATION, // SkylarkCallable.useLocation
+ SYNTAX_TREE, // SkylarkCallable.useAst
+ ENVIRONMENT, // SkylarkCallable.useEnvironment
+ SEMANTICS; // SkylarkCallable.useSemantics
+ }
+
+ // Builtins cannot create or modify variable bindings. So it's sufficient to use a shared
+ // instance.
+ private static final LexicalFrame SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS =
+ LexicalFrame.create(Mutability.IMMUTABLE);
+
+ private final Object obj;
+ private final MethodDescriptor descriptor;
+ private final List<ExtraArgKind> extraArgs;
+ private int innerArgumentCount;
+
+ public BuiltinCallable(String name, Object obj, MethodDescriptor descriptor) {
+ super(name);
+ this.obj = obj;
+ this.descriptor = descriptor;
+ this.extraArgs = getExtraArgs(descriptor.getAnnotation());
+ configure(obj, descriptor);
+ }
+
+ @Override
+ protected int getArgArraySize () {
+ return innerArgumentCount;
+ }
+
+ private static List<ExtraArgKind> getExtraArgs(SkylarkCallable annotation) {
+ ImmutableList.Builder<ExtraArgKind> extraArgs = ImmutableList.builder();
+ if (annotation.useLocation()) {
+ extraArgs.add(ExtraArgKind.LOCATION);
+ }
+ if (annotation.useAst()) {
+ extraArgs.add(ExtraArgKind.SYNTAX_TREE);
+ }
+ if (annotation.useEnvironment()) {
+ extraArgs.add(ExtraArgKind.ENVIRONMENT);
+ }
+ if (annotation.useSkylarkSemantics()) {
+ extraArgs.add(ExtraArgKind.SEMANTICS);
+ }
+ return extraArgs.build();
+ }
+
+ /** Configure a BaseFunction from a @SkylarkCallable-annotated method */
+ private void configure(Object obj, MethodDescriptor descriptor) {
+ Preconditions.checkState(!isConfigured()); // must not be configured yet
+
+ this.paramDoc = new ArrayList<>();
+ this.signature = SkylarkSignatureProcessor.getSignatureForCallable(
+ getName(), descriptor, paramDoc, getEnforcedArgumentTypes());
+ this.objectType = obj.getClass();
+ this.innerArgumentCount = signature.getSignature().getShape().getArguments() + extraArgs.size();
+ configure();
+ }
+
+ @Override
+ @Nullable
+ public Object call(Object[] args,
+ FuncallExpression ast, Environment env)
+ throws EvalException, InterruptedException {
+ Preconditions.checkNotNull(env);
+
+ // ast is null when called from Java (as there's no Skylark call site).
+ Location loc = ast == null ? Location.BUILTIN : ast.getLocation();
+
+ // Add extra arguments, if needed
+ int index = args.length - extraArgs.size();
+ for (ExtraArgKind extraArg : extraArgs) {
+ switch(extraArg) {
+ case LOCATION:
+ args[index] = loc;
+ break;
+
+ case SYNTAX_TREE:
+ args[index] = ast;
+ break;
+
+ case ENVIRONMENT:
+ args[index] = env;
+ break;
+
+ case SEMANTICS:
+ args[index] = env.getSemantics();
+ break;
+ }
+ index++;
+ }
+
+ Profiler.instance().startTask(ProfilerTask.SKYLARK_BUILTIN_FN, getName());
+
+ try {
+ env.enterScope(this, SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS, ast, env.getGlobals());
+ return FuncallExpression.callMethod(
+ descriptor, getName(), obj, args, ast.getLocation(), env);
+ } finally {
+ Profiler.instance().completeTask(ProfilerTask.SKYLARK_BUILTIN_FN);
+ env.exitScope();
+ }
+ }
+}
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 7a807f5e20..597508f04d 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
@@ -318,6 +318,21 @@ public final class FuncallExpression extends Expression {
}
/**
+ * Returns a {@link BuiltinCallable} representing a {@link SkylarkCallable}-annotated instance
+ * method of a given object with the given method name.
+ */
+ public static BuiltinCallable getBuiltinCallable(Object obj, String methodName) {
+ Class<?> objClass = obj.getClass();
+ List<MethodDescriptor> methodDescriptors = getMethods(objClass, methodName);
+ if (methodDescriptors.size() != 1) {
+ throw new IllegalStateException(String.format(
+ "Expected exactly 1 method named '%s' in %s, but found %s",
+ methodName, objClass, methodDescriptors.size()));
+ }
+ return new BuiltinCallable(methodName, obj, methodDescriptors.get(0));
+ }
+
+ /**
* Invokes the given structField=true method and returns the result.
*
* @param methodDescriptor the descriptor of the method to invoke
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
index 6424a47e44..a2dc39a377 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
@@ -14,10 +14,13 @@
package com.google.devtools.build.lib.syntax;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Booleans;
import com.google.devtools.build.lib.skylarkinterface.Param;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
import com.google.devtools.build.lib.syntax.BuiltinFunction.ExtraArgKind;
+import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
@@ -31,8 +34,50 @@ import javax.annotation.Nullable;
* to configure a given field.
*/
public class SkylarkSignatureProcessor {
+
/**
- * Extracts a {@code FunctionSignature.WithValues<Object, SkylarkType>} from an annotation
+ * Extracts a {@code FunctionSignature.WithValues<Object, SkylarkType>} from a
+ * {@link SkylarkCallable}-annotated method.
+ *
+ * @param name the name of the function
+ * @param descriptor the method descriptor
+ * @param paramDoc an optional list into which to store documentation strings
+ * @param enforcedTypesList an optional list into which to store effective types to enforce
+ */
+ public static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForCallable(
+ String name, MethodDescriptor descriptor,
+ @Nullable List<String> paramDoc, @Nullable List<SkylarkType> enforcedTypesList) {
+
+ SkylarkCallable annotation = descriptor.getAnnotation();
+
+ // TODO(cparsons): Validate these properties with the annotation processor instead.
+ Preconditions.checkArgument(annotation.name().isEmpty() || name.equals(annotation.name()),
+ "%s != %s", name, annotation.name());
+ boolean documented = annotation.documented();
+ if (annotation.doc().isEmpty() && documented) {
+ throw new RuntimeException(String.format("function %s is undocumented", name));
+ }
+ ImmutableList.Builder<Parameter<Object, SkylarkType>> parameters = ImmutableList.builder();
+
+ Class<?>[] javaMethodSignatureParams = descriptor.getMethod().getParameterTypes();
+
+ for (int paramIndex = 0; paramIndex < annotation.mandatoryPositionals(); paramIndex++) {
+ Parameter<Object, SkylarkType> parameter =
+ new Parameter.Mandatory<Object, SkylarkType>("arg" + paramIndex,
+ SkylarkType.of(javaMethodSignatureParams[paramIndex]));
+ parameters.add(parameter);
+ }
+
+ return getSignatureForCallable(name, documented, parameters.build(), annotation.parameters(),
+ /*extraPositionals=*/null,
+ /*extraKeywords=*/null, /*defaultValues=*/null, paramDoc, enforcedTypesList);
+ }
+
+
+ /**
+ * Extracts a {@code FunctionSignature.WithValues<Object, SkylarkType>} from a
+ * {@link SkylarkSignature} annotation.
+ *
* @param name the name of the function
* @param annotation the annotation
* @param defaultValues an optional list of default values
@@ -42,35 +87,50 @@ public class SkylarkSignatureProcessor {
// NB: the two arguments paramDoc and enforcedTypesList are used to "return" extra values via
// side-effects, and that's ugly
// TODO(bazel-team): use AutoValue to declare a value type to use as return value?
- public static FunctionSignature.WithValues<Object, SkylarkType> getSignature(
+ public static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForCallable(
String name, SkylarkSignature annotation,
@Nullable Iterable<Object> defaultValues,
@Nullable List<String> paramDoc, @Nullable List<SkylarkType> enforcedTypesList) {
Preconditions.checkArgument(name.equals(annotation.name()),
"%s != %s", name, annotation.name());
+ boolean documented = annotation.documented();
+ if (annotation.doc().isEmpty() && documented) {
+ throw new RuntimeException(String.format("function %s is undocumented", name));
+ }
+ return getSignatureForCallable(name, documented,
+ /*mandatoryPositionals=*/ImmutableList.<Parameter<Object, SkylarkType>>of(),
+ annotation.parameters(),
+ annotation.extraPositionals(),
+ annotation.extraKeywords(), defaultValues, paramDoc, enforcedTypesList);
+ }
+
+ private static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForCallable(
+ String name, boolean documented,
+ ImmutableList<Parameter<Object, SkylarkType>> mandatoryPositionals,
+ Param[] parameters,
+ @Nullable Param extraPositionals, @Nullable Param extraKeywords,
+ @Nullable Iterable<Object> defaultValues,
+ @Nullable List<String> paramDoc, @Nullable List<SkylarkType> enforcedTypesList) {
ArrayList<Parameter<Object, SkylarkType>> paramList = new ArrayList<>();
+ paramList.addAll(mandatoryPositionals);
HashMap<String, SkylarkType> enforcedTypes =
enforcedTypesList == null ? null : new HashMap<>();
HashMap<String, String> doc = new HashMap<>();
- boolean documented = annotation.documented();
- if (annotation.doc().isEmpty() && documented) {
- throw new RuntimeException(String.format("function %s is undocumented", name));
- }
Iterator<Object> defaultValuesIterator = defaultValues == null
? null : defaultValues.iterator();
try {
boolean named = false;
- for (Param param : annotation.parameters()) {
+ for (Param param : parameters) {
boolean mandatory = param.defaultValue() != null && param.defaultValue().isEmpty();
Object defaultValue = mandatory ? null : getDefaultValue(param, defaultValuesIterator);
if (param.named() && !param.positional() && !named) {
named = true;
@Nullable Param starParam = null;
- if (!annotation.extraPositionals().name().isEmpty()) {
- starParam = annotation.extraPositionals();
+ if (extraPositionals != null && !extraPositionals.name().isEmpty()) {
+ starParam = extraPositionals;
}
paramList.add(getParameter(name, starParam, enforcedTypes, doc, documented,
/*mandatory=*/false, /*star=*/true, /*starStar=*/false, /*defaultValue=*/null));
@@ -78,14 +138,14 @@ public class SkylarkSignatureProcessor {
paramList.add(getParameter(name, param, enforcedTypes, doc, documented,
mandatory, /*star=*/false, /*starStar=*/false, defaultValue));
}
- if (!annotation.extraPositionals().name().isEmpty() && !named) {
- paramList.add(getParameter(name, annotation.extraPositionals(), enforcedTypes, doc,
+ if (extraPositionals != null && !extraPositionals.name().isEmpty() && !named) {
+ paramList.add(getParameter(name, extraPositionals, enforcedTypes, doc,
documented, /*mandatory=*/false, /*star=*/true, /*starStar=*/false,
/*defaultValue=*/null));
}
- if (!annotation.extraKeywords().name().isEmpty()) {
+ if (extraKeywords != null && !extraKeywords.name().isEmpty()) {
paramList.add(
- getParameter(name, annotation.extraKeywords(), enforcedTypes, doc, documented,
+ getParameter(name, extraKeywords, enforcedTypes, doc, documented,
/*mandatory=*/false, /*star=*/false, /*starStar=*/true, /*defaultValue=*/null));
}
FunctionSignature.WithValues<Object, SkylarkType> signature =