aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Taras Tsugrii <ttsugrii@fb.com>2018-07-30 10:48:31 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-07-30 10:49:57 -0700
commit7dbc5e03f1ced0e3a67e42e0f182579865d26af7 (patch)
treec181d7456e951a103cccf57b301f49ee7de489ff
parentf59022b9b19c0086adc9795fd8659f8bc988f747 (diff)
[Skylark] Use POJOs instead of dynamic proxies.
Java uses dynamically generated proxy classes to access annotation properties and their methods are ~7X slower than plain getters. According to async-profiler 50%+ of `convertArgumentList` method time is spent in dynamic proxy methods, so optimizing their performance makes sense. This also makes the model less anemic, since POJOs can actually provide business methods. Closes #5666. PiperOrigin-RevId: 206608812
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/NativeInfo.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/skylarkdebug/server/DebuggerSerialization.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java13
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java14
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java200
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/MethodDescriptor.java184
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java159
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/ParamTypeDescriptor.java43
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java22
9 files changed, 465 insertions, 175 deletions
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 3798b1fc79..2652671007 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
@@ -19,7 +19,7 @@ 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 com.google.devtools.build.lib.syntax.MethodDescriptor;
import java.util.Map;
/** Base class for native implementations of {@link Info}. */
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/DebuggerSerialization.java b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/DebuggerSerialization.java
index 75d7e00c8f..ba3743de85 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/DebuggerSerialization.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/DebuggerSerialization.java
@@ -24,6 +24,7 @@ import com.google.devtools.build.lib.syntax.ClassObject;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalUtils;
import com.google.devtools.build.lib.syntax.FuncallExpression;
+import com.google.devtools.build.lib.syntax.MethodDescriptor;
import com.google.devtools.build.lib.syntax.Printer;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import java.lang.reflect.Array;
@@ -146,7 +147,7 @@ final class DebuggerSerialization {
}
ImmutableList.Builder<Value> children = ImmutableList.builder();
for (String fieldName : fieldNames) {
- FuncallExpression.MethodDescriptor method =
+ MethodDescriptor method =
FuncallExpression.getStructField(skylarkValue.getClass(), fieldName);
try {
children.add(
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
index 9a0a56b592..aa37b35677 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java
@@ -21,7 +21,6 @@ import com.google.devtools.build.lib.profiler.ProfilerTask;
import com.google.devtools.build.lib.profiler.SilentCloseable;
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;
@@ -53,7 +52,7 @@ public class BuiltinCallable extends BaseFunction {
super(name);
this.obj = obj;
this.descriptor = descriptor;
- this.extraArgs = getExtraArgs(descriptor.getAnnotation());
+ this.extraArgs = getExtraArgs(descriptor);
configure(obj, descriptor);
}
@@ -62,18 +61,18 @@ public class BuiltinCallable extends BaseFunction {
return innerArgumentCount;
}
- private static List<ExtraArgKind> getExtraArgs(SkylarkCallable annotation) {
+ private static List<ExtraArgKind> getExtraArgs(MethodDescriptor method) {
ImmutableList.Builder<ExtraArgKind> extraArgs = ImmutableList.builder();
- if (annotation.useLocation()) {
+ if (method.isUseLocation()) {
extraArgs.add(ExtraArgKind.LOCATION);
}
- if (annotation.useAst()) {
+ if (method.isUseAst()) {
extraArgs.add(ExtraArgKind.SYNTAX_TREE);
}
- if (annotation.useEnvironment()) {
+ if (method.isUseEnvironment()) {
extraArgs.add(ExtraArgKind.ENVIRONMENT);
}
- if (annotation.useSkylarkSemantics()) {
+ if (method.isUseSkylarkSemantics()) {
extraArgs.add(ExtraArgKind.SEMANTICS);
}
return extraArgs.build();
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
index b41f274c37..ad8b50379e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
@@ -15,7 +15,6 @@ package com.google.devtools.build.lib.syntax;
import com.google.common.collect.Streams;
import com.google.devtools.build.lib.events.Location;
-import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor;
import com.google.devtools.build.lib.util.SpellChecker;
import java.io.IOException;
import java.util.Optional;
@@ -126,19 +125,14 @@ public final class DotExpression extends Expression {
if (methods != null) {
Optional<MethodDescriptor> method =
- Streams.stream(methods)
- .filter(methodDescriptor -> methodDescriptor.getAnnotation().structField())
- .findFirst();
- if (method.isPresent() && method.get().getAnnotation().structField()) {
+ Streams.stream(methods).filter(MethodDescriptor::isStructField).findFirst();
+ if (method.isPresent() && method.get().isStructField()) {
return FuncallExpression.callMethod(
method.get(),
name,
objValue,
- FuncallExpression.extraInterpreterArgs(
- method.get().getAnnotation(),
- /* ast = */ null,
- loc,
- env).toArray(),
+ FuncallExpression.extraInterpreterArgs(method.get(), /* ast = */ null, loc, env)
+ .toArray(),
loc,
env);
}
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 fefcf70355..814b329a80 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
@@ -27,8 +27,6 @@ import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.events.Location;
-import com.google.devtools.build.lib.skylarkinterface.Param;
-import com.google.devtools.build.lib.skylarkinterface.ParamType;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
@@ -57,97 +55,6 @@ import javax.annotation.Nullable;
/** Syntax node for a function call expression. */
public final class FuncallExpression extends Expression {
- /**
- * A tuple of Param annotation to the skylark type it represents. While the type can be
- * inferred completely by the Param annotation, this tuple allows for the type of a given
- * parameter to be determined only once, as it is an expensive operation.
- */
- private static final class ParamInfo {
- private final Param param;
- private final SkylarkType type;
-
- public ParamInfo(Param param) {
- this.param = param;
- this.type = getSkylarkType(param);
- }
-
- private static SkylarkType getSkylarkType(Param param) {
- SkylarkType result = SkylarkType.BOTTOM;
- if (param.allowedTypes().length > 0) {
- Preconditions.checkState(Object.class.equals(param.type()));
- for (ParamType paramType : param.allowedTypes()) {
- SkylarkType t =
- paramType.generic1() != Object.class
- ? SkylarkType.of(paramType.type(), paramType.generic1())
- : SkylarkType.of(paramType.type());
- result = SkylarkType.Union.of(result, t);
- }
- } else {
- result =
- param.generic1() != Object.class
- ? SkylarkType.of(param.type(), param.generic1())
- : SkylarkType.of(param.type());
- }
-
- if (param.noneable()) {
- result = SkylarkType.Union.of(result, SkylarkType.NONE);
- }
- return result;
- }
-
- public Param getParam() {
- return param;
- }
-
- public SkylarkType getType() {
- return type;
- }
- }
-
- /**
- * A value class to store Methods with their corresponding SkylarkCallable annotations and some
- * information about the method.
- */
- public static final class MethodDescriptor {
-
- private final Method method;
- private final ParamInfo[] methodParams;
- private final SkylarkCallable annotation;
-
- private MethodDescriptor(Method method, SkylarkCallable annotation) {
- this.method = method;
- this.methodParams = methodParams(annotation);
- this.annotation = annotation;
- }
-
- private static ParamInfo[] methodParams(SkylarkCallable annotation) {
- Param[] annotationParameters = annotation.parameters();
- ParamInfo[] paramInfoArr = new ParamInfo[annotationParameters.length];
- for (int i = 0; i < paramInfoArr.length; i++) {
- paramInfoArr[i] = new ParamInfo(annotationParameters[i]);
- }
- return paramInfoArr;
- }
-
- Method getMethod() {
- return method;
- }
-
- /**
- * Returns the SkylarkCallable annotation corresponding to this method.
- */
- public SkylarkCallable getAnnotation() {
- return annotation;
- }
-
- /**
- * Returns an array of objects describing the parameters of this method.
- */
- public ParamInfo[] getMethodParams() {
- return methodParams;
- }
- }
-
private static final LoadingCache<Class<?>, Optional<MethodDescriptor>> selfCallCache =
CacheBuilder.newBuilder()
.build(
@@ -164,11 +71,10 @@ public final class FuncallExpression extends Expression {
if (callable != null && callable.selfCall()) {
if (returnValue != null) {
throw new IllegalArgumentException(
- String.format(
- "Class %s has two selfCall methods defined",
- key.getName()));
+ String.format(
+ "Class %s has two selfCall methods defined", key.getName()));
}
- returnValue = new MethodDescriptor(method, callable);
+ returnValue = MethodDescriptor.of(method, callable);
}
}
return Optional.ofNullable(returnValue);
@@ -198,10 +104,10 @@ public final class FuncallExpression extends Expression {
}
String name = callable.name();
if (methodMap.containsKey(name)) {
- methodMap.get(name).add(new MethodDescriptor(method, callable));
+ methodMap.get(name).add(MethodDescriptor.of(method, callable));
} else {
methodMap.put(
- name, Lists.newArrayList(new MethodDescriptor(method, callable)));
+ name, Lists.newArrayList(MethodDescriptor.of(method, callable)));
}
}
return ImmutableMap.copyOf(methodMap);
@@ -223,13 +129,11 @@ public final class FuncallExpression extends Expression {
.values()
.stream()
.flatMap(List::stream)
- .filter(
- methodDescriptor -> methodDescriptor.getAnnotation().structField())
+ .filter(MethodDescriptor::isStructField)
.collect(Collectors.toList());
for (MethodDescriptor fieldMethod : fieldMethods) {
- SkylarkCallable callable = fieldMethod.getAnnotation();
- String name = callable.name();
+ String name = fieldMethod.getName();
// TODO(b/72113542): Validate with annotation processor instead of at runtime.
if (!fieldNamesForCollisions.add(name)) {
throw new IllegalArgumentException(
@@ -444,7 +348,7 @@ public final class FuncallExpression extends Expression {
throw new IllegalStateException("Class " + obj.getClass() + " has no selfCall method");
}
MethodDescriptor descriptor = selfCallDescriptor.get();
- return new BuiltinCallable(descriptor.getAnnotation().name(), obj, descriptor);
+ return new BuiltinCallable(descriptor.getName(), obj, descriptor);
} catch (ExecutionException e) {
throw new IllegalStateException("Method loading failed: " + e);
}
@@ -480,11 +384,12 @@ public final class FuncallExpression extends Expression {
public static Object invokeStructField(
MethodDescriptor methodDescriptor, String fieldName, Object obj)
throws EvalException, InterruptedException {
- Preconditions.checkArgument(methodDescriptor.getAnnotation().structField(),
- "Can only be invoked on structField callables");
- Preconditions.checkArgument(!methodDescriptor.getAnnotation().useEnvironment()
- || !methodDescriptor.getAnnotation().useSkylarkSemantics()
- || !methodDescriptor.getAnnotation().useLocation(),
+ Preconditions.checkArgument(
+ methodDescriptor.isStructField(), "Can only be invoked on structField callables");
+ Preconditions.checkArgument(
+ !methodDescriptor.isUseEnvironment()
+ || !methodDescriptor.isUseSkylarkSemantics()
+ || !methodDescriptor.isUseLocation(),
"Cannot be invoked on structField callables with extra interpreter params");
return callMethod(methodDescriptor, fieldName, obj, new Object[0], Location.BUILTIN, null);
}
@@ -504,7 +409,7 @@ public final class FuncallExpression extends Expression {
return Runtime.NONE;
}
if (result == null) {
- if (methodDescriptor.getAnnotation().allowReturnNones()) {
+ if (methodDescriptor.isAllowReturnNones()) {
return Runtime.NONE;
} else {
throw new EvalException(
@@ -558,13 +463,12 @@ public final class FuncallExpression extends Expression {
ArgumentListConversionResult argumentListConversionResult = null;
if (methods != null) {
for (MethodDescriptor method : methods) {
- if (method.getAnnotation().structField()) {
+ if (method.isStructField()) {
// This indicates a built-in structField which returns a function which may have
// one or more arguments itself. For example, foo.bar('baz'), where foo.bar is a
// structField returning a function. Calling the "bar" callable of foo should
// not have 'baz' propagated, though extra interpreter arguments should be supplied.
- return new Pair<>(method,
- extraInterpreterArgs(method.getAnnotation(), null, getLocation(), environment));
+ return new Pair<>(method, extraInterpreterArgs(method, null, getLocation(), environment));
} else {
argumentListConversionResult = convertArgumentList(args, kwargs, method, environment);
if (argumentListConversionResult.getArguments() != null) {
@@ -607,35 +511,35 @@ public final class FuncallExpression extends Expression {
return matchingMethod;
}
- private static boolean isParamNamed(Param param) {
- return param.named() || param.legacyNamed();
+ private static boolean isParamNamed(ParamDescriptor param) {
+ return param.isNamed() || param.isLegacyNamed();
}
/**
- * Returns the extra interpreter arguments for the given {@link SkylarkCallable}, to be added
- * at the end of the argument list for the callable.
+ * Returns the extra interpreter arguments for the given {@link SkylarkCallable}, to be added at
+ * the end of the argument list for the callable.
*
- * <p>This method accepts null {@code ast} only if {@code callable.useAst()} is false. It is
- * up to the caller to validate this invariant.</p>
+ * <p>This method accepts null {@code ast} only if {@code callable.useAst()} is false. It is up to
+ * the caller to validate this invariant.
*/
- public static List<Object> extraInterpreterArgs(SkylarkCallable callable,
- @Nullable FuncallExpression ast, Location loc, Environment env) {
+ public static List<Object> extraInterpreterArgs(
+ MethodDescriptor method, @Nullable FuncallExpression ast, Location loc, Environment env) {
ImmutableList.Builder<Object> builder = ImmutableList.builder();
- if (callable.useLocation()) {
+ if (method.isUseLocation()) {
builder.add(loc);
}
- if (callable.useAst()) {
+ if (method.isUseAst()) {
if (ast == null) {
- throw new IllegalArgumentException("Callable expects to receive ast: " + callable.name());
+ throw new IllegalArgumentException("Callable expects to receive ast: " + method.getName());
}
builder.add(ast);
}
- if (callable.useEnvironment()) {
+ if (method.isUseEnvironment()) {
builder.add(env);
}
- if (callable.useSkylarkSemantics()) {
+ if (method.isUseSkylarkSemantics()) {
builder.add(env.getSemantics());
}
return builder.build();
@@ -650,12 +554,11 @@ public final class FuncallExpression extends Expression {
Map<String, Object> kwargs,
MethodDescriptor method,
Environment environment) {
- SkylarkCallable callable = method.getAnnotation();
ImmutableList.Builder<Object> builder = ImmutableList.builder();
ImmutableList.Builder<Object> extraArgsBuilder = ImmutableList.builder();
ImmutableMap.Builder<String, Object> extraKwargsBuilder = ImmutableMap.builder();
- boolean acceptsExtraArgs = !callable.extraPositionals().name().isEmpty();
- boolean acceptsExtraKwargs = !callable.extraKeywords().name().isEmpty();
+ boolean acceptsExtraArgs = method.isAcceptsExtraArgs();
+ boolean acceptsExtraKwargs = method.isAcceptsExtraKwargs();
int argIndex = 0;
@@ -664,45 +567,46 @@ public final class FuncallExpression extends Expression {
// Positional parameters are always enumerated before non-positional parameters,
// And default-valued positional parameters are always enumerated after other positional
// parameters. These invariants are validated by the SkylarkCallable annotation processor.
- for (ParamInfo paramInfo : method.getMethodParams()) {
- SkylarkType type = paramInfo.getType();
- Param param = paramInfo.getParam();
+ for (ParamDescriptor param : method.getParameters()) {
+ SkylarkType type = param.getSkylarkType();
Object value = null;
- if (argIndex < args.size() && param.positional()) { // Positional args and params remain.
+ if (argIndex < args.size() && param.isPositional()) { // Positional args and params remain.
value = args.get(argIndex);
if (!type.contains(value)) {
return ArgumentListConversionResult.fromError(
String.format(
"expected value of type '%s' for parameter '%s'",
- type.toString(), param.name()));
+ type.toString(), param.getName()));
}
- if (isParamNamed(param) && keys.contains(param.name())) {
+ if (isParamNamed(param) && keys.contains(param.getName())) {
return ArgumentListConversionResult.fromError(
- String.format("got multiple values for keyword argument '%s'", param.name()));
+ String.format("got multiple values for keyword argument '%s'", param.getName()));
}
argIndex++;
} else { // No more positional arguments, or no more positional parameters.
- if (isParamNamed(param) && keys.remove(param.name())) {
+ if (isParamNamed(param) && keys.remove(param.getName())) {
// Param specified by keyword argument.
- value = kwargs.get(param.name());
+ value = kwargs.get(param.getName());
if (!type.contains(value)) {
return ArgumentListConversionResult.fromError(
String.format(
"expected value of type '%s' for parameter '%s'",
- type.toString(), param.name()));
+ type.toString(), param.getName()));
}
} else { // Param not specified by user. Use default value.
- if (param.defaultValue().isEmpty()) {
+ if (param.getDefaultValue().isEmpty()) {
return ArgumentListConversionResult.fromError(
- String.format("parameter '%s' has no default value", param.name()));
+ String.format("parameter '%s' has no default value", param.getName()));
}
- value = SkylarkSignatureProcessor.getDefaultValue(param, null);
+ value =
+ SkylarkSignatureProcessor.getDefaultValue(
+ param.getName(), param.getDefaultValue(), null);
}
}
- if (!param.noneable() && value instanceof NoneType) {
+ if (!param.isNoneable() && value instanceof NoneType) {
return ArgumentListConversionResult.fromError(
- String.format("parameter '%s' cannot be None", param.name()));
+ String.format("parameter '%s' cannot be None", param.getName()));
}
builder.add(value);
}
@@ -740,7 +644,7 @@ public final class FuncallExpression extends Expression {
if (acceptsExtraKwargs) {
builder.add(SkylarkDict.copyOf(environment, extraKwargsBuilder.build()));
}
- builder.addAll(extraInterpreterArgs(callable, this, getLocation(), environment));
+ builder.addAll(extraInterpreterArgs(method, this, getLocation(), environment));
return ArgumentListConversionResult.fromArgumentList(builder.build());
}
@@ -907,7 +811,7 @@ public final class FuncallExpression extends Expression {
}
Pair<MethodDescriptor, List<Object>> javaMethod =
call.findJavaMethod(objClass, method, positionalArgs, keyWordArgs, env);
- if (javaMethod.first.getAnnotation().structField()) {
+ if (javaMethod.first.isStructField()) {
// Not a method but a callable attribute
try {
return callFunction(javaMethod.first.getMethod().invoke(obj), env);
@@ -945,14 +849,14 @@ public final class FuncallExpression extends Expression {
Object value = arg.getValue().eval(env);
if (arg.isPositional()) {
posargs.add(value);
- } else if (arg.isStar()) { // expand the starArg
+ } else if (arg.isStar()) { // expand the starArg
if (!(value instanceof Iterable)) {
throw new EvalException(
getLocation(),
"argument after * must be an iterable, not " + EvalUtils.getDataTypeName(value));
}
posargs.addAll((Iterable<Object>) value);
- } else if (arg.isStarStar()) { // expand the kwargs
+ } else if (arg.isStarStar()) { // expand the kwargs
ImmutableList<String> duplicates =
addKeywordArgsAndReturnDuplicates(kwargs, value, getLocation());
if (duplicates != null) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodDescriptor.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodDescriptor.java
new file mode 100644
index 0000000000..978b15f7c4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodDescriptor.java
@@ -0,0 +1,184 @@
+// 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.collect.ImmutableList;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * A value class to store Methods with their corresponding {@link SkylarkCallable} annotation
+ * metadata. This is needed because the annotation is sometimes in a superclass.
+ *
+ * <p>The annotation metadata is duplicated in this class to avoid usage of Java dynamic proxies
+ * which are ~7X slower.
+ */
+public final class MethodDescriptor {
+ private final Method method;
+ private final SkylarkCallable annotation;
+
+ private final String name;
+ private final String doc;
+ private final boolean documented;
+ private final boolean structField;
+ private final ImmutableList<ParamDescriptor> parameters;
+ private final ParamDescriptor extraPositionals;
+ private final ParamDescriptor extraKeywords;
+ private final boolean selfCall;
+ private final boolean allowReturnNones;
+ private final boolean useLocation;
+ private final boolean useAst;
+ private final boolean useEnvironment;
+ private final boolean useSkylarkSemantics;
+
+ private MethodDescriptor(
+ Method method,
+ SkylarkCallable annotation,
+ String name,
+ String doc,
+ boolean documented,
+ boolean structField,
+ ImmutableList<ParamDescriptor> parameters,
+ ParamDescriptor extraPositionals,
+ ParamDescriptor extraKeywords,
+ boolean selfCall,
+ boolean allowReturnNones,
+ boolean useLocation,
+ boolean useAst,
+ boolean useEnvironment,
+ boolean useSkylarkSemantics) {
+ this.method = method;
+ this.annotation = annotation;
+ this.name = name;
+ this.doc = doc;
+ this.documented = documented;
+ this.structField = structField;
+ this.parameters = parameters;
+ this.extraPositionals = extraPositionals;
+ this.extraKeywords = extraKeywords;
+ this.selfCall = selfCall;
+ this.allowReturnNones = allowReturnNones;
+ this.useLocation = useLocation;
+ this.useAst = useAst;
+ this.useEnvironment = useEnvironment;
+ this.useSkylarkSemantics = useSkylarkSemantics;
+ }
+
+ Method getMethod() {
+ return method;
+ }
+
+ /** Returns the SkylarkCallable annotation corresponding to this method. */
+ public SkylarkCallable getAnnotation() {
+ return annotation;
+ }
+
+ /** @return Skylark method descriptor for provided Java method and signature annotation. */
+ public static MethodDescriptor of(Method method, SkylarkCallable annotation) {
+ return new MethodDescriptor(
+ method,
+ annotation,
+ annotation.name(),
+ annotation.doc(),
+ annotation.documented(),
+ annotation.structField(),
+ Arrays.stream(annotation.parameters())
+ .map(ParamDescriptor::of)
+ .collect(ImmutableList.toImmutableList()),
+ ParamDescriptor.of(annotation.extraPositionals()),
+ ParamDescriptor.of(annotation.extraKeywords()),
+ annotation.selfCall(),
+ annotation.allowReturnNones(),
+ annotation.useLocation(),
+ annotation.useAst(),
+ annotation.useEnvironment(),
+ annotation.useSkylarkSemantics());
+ }
+
+ /** @see SkylarkCallable#name() */
+ public String getName() {
+ return name;
+ }
+
+ /** @see SkylarkCallable#structField() */
+ public boolean isStructField() {
+ return structField;
+ }
+
+ /** @see SkylarkCallable#useEnvironment() */
+ public boolean isUseEnvironment() {
+ return useEnvironment;
+ }
+
+ /** @see SkylarkCallable#useSkylarkSemantics() */
+ public boolean isUseSkylarkSemantics() {
+ return useSkylarkSemantics;
+ }
+
+ /** @see SkylarkCallable#useLocation() */
+ public boolean isUseLocation() {
+ return useLocation;
+ }
+
+ /** @see SkylarkCallable#allowReturnNones() */
+ public boolean isAllowReturnNones() {
+ return allowReturnNones;
+ }
+
+ /** @see SkylarkCallable#useAst() */
+ public boolean isUseAst() {
+ return useAst;
+ }
+
+ /** @see SkylarkCallable#extraPositionals() */
+ public ParamDescriptor getExtraPositionals() {
+ return extraPositionals;
+ }
+
+ public ParamDescriptor getExtraKeywords() {
+ return extraKeywords;
+ }
+
+ /** @return {@code true} if this method accepts extra arguments ({@code *args}) */
+ public boolean isAcceptsExtraArgs() {
+ return !getExtraPositionals().getName().isEmpty();
+ }
+
+ /** @see SkylarkCallable#extraKeywords() */
+ public boolean isAcceptsExtraKwargs() {
+ return !getExtraKeywords().getName().isEmpty();
+ }
+
+ /** @see SkylarkCallable#parameters() */
+ public ImmutableList<ParamDescriptor> getParameters() {
+ return parameters;
+ }
+
+ /** @see SkylarkCallable#documented() */
+ public boolean isDocumented() {
+ return documented;
+ }
+
+ /** @see SkylarkCallable#doc() */
+ public String getDoc() {
+ return doc;
+ }
+
+ /** @see SkylarkCallable#selfCall() */
+ public boolean isSelfCall() {
+ return selfCall;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java b/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java
new file mode 100644
index 0000000000..1bbc05b95a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java
@@ -0,0 +1,159 @@
+// 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.skylarkinterface.Param;
+import java.util.Arrays;
+
+/** A value class for storing {@link Param} metadata to avoid using Java proxies. */
+public final class ParamDescriptor {
+
+ private String name;
+ private final String defaultValue;
+ private final Class<?> type;
+ private final ImmutableList<ParamTypeDescriptor> allowedTypes;
+ private final Class<?> generic1;
+ private final boolean noneable;
+ private final boolean named;
+ private final boolean legacyNamed;
+ private final boolean positional;
+ // While the type can be inferred completely by the Param annotation, this tuple allows for the
+ // type of a given parameter to be determined only once, as it is an expensive operation.
+ private final SkylarkType skylarkType;
+
+ private ParamDescriptor(
+ String name,
+ String doc,
+ String defaultValue,
+ Class<?> type,
+ ImmutableList<ParamTypeDescriptor> allowedTypes,
+ Class<?> generic1,
+ boolean callbackEnabled,
+ boolean noneable,
+ boolean named,
+ boolean legacyNamed,
+ boolean positional,
+ SkylarkType skylarkType) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ this.type = type;
+ this.allowedTypes = allowedTypes;
+ this.generic1 = generic1;
+ this.noneable = noneable;
+ this.named = named;
+ this.legacyNamed = legacyNamed;
+ this.positional = positional;
+ this.skylarkType = skylarkType;
+ }
+
+ static ParamDescriptor of(Param param) {
+ ImmutableList<ParamTypeDescriptor> allowedTypes =
+ Arrays.stream(param.allowedTypes())
+ .map(ParamTypeDescriptor::of)
+ .collect(ImmutableList.toImmutableList());
+ Class<?> type = param.type();
+ Class<?> generic = param.generic1();
+ boolean noneable = param.noneable();
+ return new ParamDescriptor(
+ param.name(),
+ param.doc(),
+ param.defaultValue(),
+ type,
+ allowedTypes,
+ generic,
+ param.callbackEnabled(),
+ noneable,
+ param.named(),
+ param.legacyNamed(),
+ param.positional(),
+ getType(type, generic, allowedTypes, noneable));
+ }
+
+ /** @see Param#name() */
+ public String getName() {
+ return name;
+ }
+
+ /** @see Param#allowedTypes() */
+ public ImmutableList<ParamTypeDescriptor> getAllowedTypes() {
+ return allowedTypes;
+ }
+
+ /** @see Param#type() */
+ public Class<?> getType() {
+ return type;
+ }
+
+ private static SkylarkType getType(
+ Class<?> type,
+ Class<?> generic,
+ ImmutableList<ParamTypeDescriptor> allowedTypes,
+ boolean noneable) {
+ SkylarkType result = SkylarkType.BOTTOM;
+ if (!allowedTypes.isEmpty()) {
+ Preconditions.checkState(Object.class.equals(type));
+ for (ParamTypeDescriptor paramType : allowedTypes) {
+ SkylarkType t =
+ paramType.getGeneric1() != Object.class
+ ? SkylarkType.of(paramType.getType(), paramType.getGeneric1())
+ : SkylarkType.of(paramType.getType());
+ result = SkylarkType.Union.of(result, t);
+ }
+ } else {
+ result = generic != Object.class ? SkylarkType.of(type, generic) : SkylarkType.of(type);
+ }
+
+ if (noneable) {
+ result = SkylarkType.Union.of(result, SkylarkType.NONE);
+ }
+ return result;
+ }
+
+ /** @see Param#generic1() */
+ public Class<?> getGeneric1() {
+ return generic1;
+ }
+
+ /** @see Param#noneable() */
+ public boolean isNoneable() {
+ return noneable;
+ }
+
+ /** @see Param#positional() */
+ public boolean isPositional() {
+ return positional;
+ }
+
+ /** @see Param#named() */
+ public boolean isNamed() {
+ return named;
+ }
+
+ /** @see Param#legacyNamed() */
+ public boolean isLegacyNamed() {
+ return legacyNamed;
+ }
+
+ /** @see Param#defaultValue() */
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ SkylarkType getSkylarkType() {
+ return skylarkType;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ParamTypeDescriptor.java b/src/main/java/com/google/devtools/build/lib/syntax/ParamTypeDescriptor.java
new file mode 100644
index 0000000000..f414636383
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ParamTypeDescriptor.java
@@ -0,0 +1,43 @@
+// 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.devtools.build.lib.skylarkinterface.ParamType;
+
+/** A value class to store {@link ParamType} metadata to avoid using Java proxies. */
+public final class ParamTypeDescriptor {
+
+ private final Class<?> type;
+ private final Class<?> generic1;
+
+ private ParamTypeDescriptor(Class<?> type, Class<?> generic1) {
+ this.type = type;
+ this.generic1 = generic1;
+ }
+
+ /** @see ParamType#type() */
+ public Class<?> getType() {
+ return type;
+ }
+
+ /** @see ParamType#generic1() */
+ public Class<?> getGeneric1() {
+ return generic1;
+ }
+
+ static ParamTypeDescriptor of(ParamType paramType) {
+ return new ParamTypeDescriptor(paramType.type(), paramType.generic1());
+ }
+}
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 009bdedfd3..2c9e074f49 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
@@ -21,7 +21,6 @@ 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;
@@ -233,13 +232,18 @@ public class SkylarkSignatureProcessor {
}
static Object getDefaultValue(Param param, Iterator<Object> iterator) {
+ return getDefaultValue(param.name(), param.defaultValue(), iterator);
+ }
+
+ static Object getDefaultValue(
+ String paramName, String paramDefaultValue, Iterator<Object> iterator) {
if (iterator != null) {
return iterator.next();
- } else if (param.defaultValue().isEmpty()) {
+ } else if (paramDefaultValue.isEmpty()) {
return Runtime.NONE;
} else {
try {
- Object defaultValue = defaultValueCache.getIfPresent(param.defaultValue());
+ Object defaultValue = defaultValueCache.getIfPresent(paramDefaultValue);
if (defaultValue != null) {
return defaultValue;
}
@@ -252,14 +256,16 @@ public class SkylarkSignatureProcessor {
.setEventHandler(Environment.FAIL_FAST_HANDLER)
.build()
.update("unbound", Runtime.UNBOUND);
- defaultValue = BuildFileAST.eval(env, param.defaultValue());
- defaultValueCache.put(param.defaultValue(), defaultValue);
+ defaultValue = BuildFileAST.eval(env, paramDefaultValue);
+ defaultValueCache.put(paramDefaultValue, defaultValue);
return defaultValue;
}
} catch (Exception e) {
- throw new RuntimeException(String.format(
- "Exception while processing @SkylarkSignature.Param %s, default value %s",
- param.name(), param.defaultValue()), e);
+ throw new RuntimeException(
+ String.format(
+ "Exception while processing @SkylarkSignature.Param %s, default value %s",
+ paramName, paramDefaultValue),
+ e);
}
}
}