diff options
author | Taras Tsugrii <ttsugrii@fb.com> | 2018-07-30 10:48:31 -0700 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-07-30 10:49:57 -0700 |
commit | 7dbc5e03f1ced0e3a67e42e0f182579865d26af7 (patch) | |
tree | c181d7456e951a103cccf57b301f49ee7de489ff /src/main/java/com/google/devtools/build/lib/syntax | |
parent | f59022b9b19c0086adc9795fd8659f8bc988f747 (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
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax')
7 files changed, 462 insertions, 173 deletions
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); } } } |