diff options
Diffstat (limited to 'src/main/java/com')
7 files changed, 310 insertions, 126 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java index 0456b44512..750ccf2658 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java +++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java @@ -1360,7 +1360,7 @@ public final class PackageFactory { } byte[] buildFileBytes = maybeGetBuildFileBytes(buildFile, eventHandler); if (buildFileBytes == null) { - throw new BuildFileContainsErrorsException(packageId, "IOException occured"); + throw new BuildFileContainsErrorsException(packageId, "IOException occurred"); } Globber globber = createLegacyGlobber(buildFile.getParentDirectory(), packageId, locator); @@ -1507,9 +1507,10 @@ public final class PackageFactory { */ private ClassObject newNativeModule() { ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); - Runtime.BuiltinRegistry builtins = Runtime.getBuiltinRegistry(); - for (String nativeFunction : builtins.getFunctionNames(SkylarkNativeModule.class)) { - builder.put(nativeFunction, builtins.getFunction(SkylarkNativeModule.class, nativeFunction)); + SkylarkNativeModule nativeModuleInstance = new SkylarkNativeModule(); + for (String nativeFunction : FuncallExpression.getMethodNames(SkylarkNativeModule.class)) { + builder.put(nativeFunction, + FuncallExpression.getBuiltinCallable(nativeModuleInstance, nativeFunction)); } builder.putAll(ruleFunctions); builder.put("package", newPackageFunction(packageArguments)); @@ -1529,6 +1530,17 @@ public final class PackageFactory { // or if not possible, at least make them straight copies from the native module variant. // or better, use a common Environment.Frame for these common bindings // (that shares a backing ImmutableMap for the bindings?) + Object packageNameFunction; + Object repositoryNameFunction; + try { + packageNameFunction = nativeModule.getValue("package_name"); + repositoryNameFunction = nativeModule.getValue("repository_name"); + } catch (EvalException exception) { + // This should not occur, as nativeModule.getValue should never throw an exception. + throw new IllegalStateException( + "error getting package_name or repository_name functions from the native module", + exception); + } pkgEnv .setup("native", nativeModule) .setup("distribs", newDistribsFunction.apply(context)) @@ -1537,8 +1549,8 @@ public final class PackageFactory { .setup("exports_files", newExportsFilesFunction.apply()) .setup("package_group", newPackageGroupFunction.apply()) .setup("package", newPackageFunction(packageArguments)) - .setup("package_name", SkylarkNativeModule.packageName) - .setup("repository_name", SkylarkNativeModule.repositoryName) + .setup("package_name", packageNameFunction) + .setup("repository_name", repositoryNameFunction) .setup("environment_group", newEnvironmentGroupFunction.apply(context)); for (Entry<String, BuiltinRuleFunction> entry : ruleFunctions.entrySet()) { diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java index 0f23a59ed5..43220320f5 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java +++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java @@ -17,17 +17,15 @@ package com.google.devtools.build.lib.packages; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.skylarkinterface.Param; +import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory; -import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature; -import com.google.devtools.build.lib.syntax.BuiltinFunction; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.syntax.SkylarkDict; import com.google.devtools.build.lib.syntax.SkylarkList; -import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; import com.google.devtools.build.lib.syntax.Type.ConversionException; /** @@ -36,7 +34,6 @@ import com.google.devtools.build.lib.syntax.Type.ConversionException; */ @SkylarkModule( name = "native", - namespace = true, category = SkylarkModuleCategory.BUILTIN, doc = "A built-in module to support native rules and other package helper functions. " @@ -49,10 +46,8 @@ import com.google.devtools.build.lib.syntax.Type.ConversionException; ) public class SkylarkNativeModule { - @SkylarkSignature( + @SkylarkCallable( name = "glob", - objectType = SkylarkNativeModule.class, - returnType = SkylarkList.class, doc = "Glob returns a list of every file in the current package that:<ul>\n" + "<li>Matches at least one pattern in <code>include</code>.</li>\n" @@ -86,24 +81,19 @@ public class SkylarkNativeModule { useAst = true, useEnvironment = true ) - private static final BuiltinFunction glob = - new BuiltinFunction("glob") { - public SkylarkList invoke( - SkylarkList include, - SkylarkList exclude, - Integer excludeDirectories, - FuncallExpression ast, - Environment env) - throws EvalException, ConversionException, InterruptedException { - env.checkLoadingPhase("native.glob", ast.getLocation()); - return PackageFactory.callGlob(null, include, exclude, excludeDirectories != 0, ast, env); - } - }; + public SkylarkList<?> glob( + SkylarkList<?> include, + SkylarkList<?> exclude, + Integer excludeDirectories, + FuncallExpression ast, + Environment env) + throws EvalException, ConversionException, InterruptedException { + env.checkLoadingPhase("native.glob", ast.getLocation()); + return PackageFactory.callGlob(null, include, exclude, excludeDirectories != 0, ast, env); + } - @SkylarkSignature( + @SkylarkCallable( name = "existing_rule", - objectType = SkylarkNativeModule.class, - returnType = Object.class, doc = "Returns a dictionary representing the attributes of a previously defined rule, " + "or None if the rule does not exist.", @@ -113,48 +103,38 @@ public class SkylarkNativeModule { useAst = true, useEnvironment = true ) - private static final BuiltinFunction existingRule = - new BuiltinFunction("existing_rule") { - public Object invoke(String name, FuncallExpression ast, Environment env) - throws EvalException, InterruptedException { - env.checkLoadingOrWorkspacePhase("native.existing_rule", ast.getLocation()); - SkylarkDict<String, Object> rule = PackageFactory.callGetRuleFunction(name, ast, env); - if (rule != null) { - return rule; - } + public Object existingRule(String name, FuncallExpression ast, Environment env) + throws EvalException, InterruptedException { + env.checkLoadingOrWorkspacePhase("native.existing_rule", ast.getLocation()); + SkylarkDict<String, Object> rule = PackageFactory.callGetRuleFunction(name, ast, env); + if (rule != null) { + return rule; + } - return Runtime.NONE; - } - }; + return Runtime.NONE; + } /* If necessary, we could allow filtering by tag (anytag, alltags), name (regexp?), kind ? For now, we ignore this, since users can implement it in Skylark. */ - @SkylarkSignature( + @SkylarkCallable( name = "existing_rules", - objectType = SkylarkNativeModule.class, - returnType = SkylarkDict.class, doc = "Returns a dict containing all the rules instantiated so far. " + "The map key is the name of the rule. The map value is equivalent to the " + "existing_rule output for that rule.", - parameters = {}, useAst = true, useEnvironment = true ) - private static final BuiltinFunction existingRules = - new BuiltinFunction("existing_rules") { - public SkylarkDict<String, SkylarkDict<String, Object>> invoke( - FuncallExpression ast, Environment env) - throws EvalException, InterruptedException { - env.checkLoadingOrWorkspacePhase("native.existing_rules", ast.getLocation()); - return PackageFactory.callGetRulesFunction(ast, env); - } - }; + public SkylarkDict<String, SkylarkDict<String, Object>> existingRules( + FuncallExpression ast, Environment env) + throws EvalException, InterruptedException { + env.checkLoadingOrWorkspacePhase("native.existing_rules", ast.getLocation()); + return PackageFactory.callGetRulesFunction(ast, env); + } - @SkylarkSignature(name = "package_group", objectType = SkylarkNativeModule.class, - returnType = Runtime.NoneType.class, + @SkylarkCallable(name = "package_group", doc = "This function defines a set of packages and assigns a label to the group. " + "The label can be referenced in <code>visibility</code> attributes.", parameters = { @@ -167,19 +147,17 @@ public class SkylarkNativeModule { defaultValue = "[]", named = true, positional = false, doc = "Other package groups that are included in this one.")}, useAst = true, useEnvironment = true) - private static final BuiltinFunction packageGroup = new BuiltinFunction("package_group") { - public Runtime.NoneType invoke(String name, SkylarkList packages, SkylarkList includes, - FuncallExpression ast, Environment env) throws EvalException, ConversionException { - env.checkLoadingPhase("native.package_group", ast.getLocation()); - return PackageFactory.callPackageFunction(name, packages, includes, ast, env); - } - }; + public Runtime.NoneType packageGroup(String name, SkylarkList<?> packages, + SkylarkList<?> includes, + FuncallExpression ast, Environment env) throws EvalException { + env.checkLoadingPhase("native.package_group", ast.getLocation()); + return PackageFactory.callPackageFunction(name, packages, includes, ast, env); + } - @SkylarkSignature(name = "exports_files", objectType = SkylarkNativeModule.class, - returnType = Runtime.NoneType.class, - doc = "Specifies a list of files belonging to this package that are exported to other " - + "packages but not otherwise mentioned.", - parameters = { + @SkylarkCallable(name = "exports_files", + doc = "Specifies a list of files belonging to this package that are exported to other " + + "packages but not otherwise mentioned.", + parameters = { @Param(name = "srcs", type = SkylarkList.class, generic1 = String.class, doc = "The list of files to export."), // TODO(bazel-team): make it possible to express the precise type ListOf(LabelDesignator) @@ -189,20 +167,16 @@ public class SkylarkNativeModule { + "every package."), @Param(name = "licenses", type = SkylarkList.class, generic1 = String.class, noneable = true, defaultValue = "None", doc = "Licenses to be specified.")}, - useAst = true, useEnvironment = true) - private static final BuiltinFunction exportsFiles = new BuiltinFunction("exports_files") { - public Runtime.NoneType invoke(SkylarkList srcs, Object visibility, Object licenses, - FuncallExpression ast, Environment env) - throws EvalException, ConversionException { - env.checkLoadingPhase("native.exports_files", ast.getLocation()); - return PackageFactory.callExportsFiles(srcs, visibility, licenses, ast, env); - } - }; + useAst = true, useEnvironment = true) + public Runtime.NoneType exportsFiles(SkylarkList<?> srcs, Object visibility, Object licenses, + FuncallExpression ast, Environment env) + throws EvalException { + env.checkLoadingPhase("native.exports_files", ast.getLocation()); + return PackageFactory.callExportsFiles(srcs, visibility, licenses, ast, env); + } - @SkylarkSignature( + @SkylarkCallable( name = "package_name", - objectType = SkylarkNativeModule.class, - returnType = String.class, doc = "The name of the package being evaluated. " + "For example, in the BUILD file <code>some/package/BUILD</code>, its value " @@ -214,21 +188,16 @@ public class SkylarkNativeModule { useAst = true, useEnvironment = true ) - static final BuiltinFunction packageName = - new BuiltinFunction("package_name") { - public String invoke(FuncallExpression ast, Environment env) - throws EvalException, ConversionException { - env.checkLoadingPhase("native.package_name", ast.getLocation()); - PackageIdentifier packageId = - PackageFactory.getContext(env, ast.getLocation()).getBuilder().getPackageIdentifier(); - return packageId.getPackageFragment().getPathString(); - } - }; + public String packageName(FuncallExpression ast, Environment env) + throws EvalException { + env.checkLoadingPhase("native.package_name", ast.getLocation()); + PackageIdentifier packageId = + PackageFactory.getContext(env, ast.getLocation()).getBuilder().getPackageIdentifier(); + return packageId.getPackageFragment().getPathString(); + } - @SkylarkSignature( + @SkylarkCallable( name = "repository_name", - objectType = SkylarkNativeModule.class, - returnType = String.class, doc = "The name of the repository the rule or build extension is called from. " + "For example, in packages that are called into existence by the WORKSPACE stanza " @@ -240,20 +209,11 @@ public class SkylarkNativeModule { useLocation = true, useEnvironment = true ) - static final BuiltinFunction repositoryName = - new BuiltinFunction("repository_name") { - public String invoke(Location location, Environment env) - throws EvalException, ConversionException { - env.checkLoadingPhase("native.repository_name", location); - PackageIdentifier packageId = - PackageFactory.getContext(env, location).getBuilder().getPackageIdentifier(); - return packageId.getRepository().toString(); - } - }; - - public static final SkylarkNativeModule NATIVE_MODULE = new SkylarkNativeModule(); - - static { - SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkNativeModule.class); + public String repositoryName(Location location, Environment env) + throws EvalException { + env.checkLoadingPhase("native.repository_name", location); + PackageIdentifier packageId = + PackageFactory.getContext(env, location).getBuilder().getPackageIdentifier(); + return packageId.getRepository().toString(); } } diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java index 4e7af5f001..6868a98099 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java +++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java @@ -46,7 +46,6 @@ import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.FunctionSignature; import com.google.devtools.build.lib.syntax.Mutability; import com.google.devtools.build.lib.syntax.ParserInputSource; -import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.syntax.Runtime.NoneType; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.syntax.SkylarkSemantics; @@ -551,9 +550,10 @@ public class WorkspaceFactory { private static ClassObject newNativeModule( ImmutableMap<String, BaseFunction> workspaceFunctions, String version) { ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); - Runtime.BuiltinRegistry builtins = Runtime.getBuiltinRegistry(); - for (String nativeFunction : builtins.getFunctionNames(SkylarkNativeModule.class)) { - builder.put(nativeFunction, builtins.getFunction(SkylarkNativeModule.class, nativeFunction)); + SkylarkNativeModule nativeModuleInstance = new SkylarkNativeModule(); + for (String nativeFunction : FuncallExpression.getMethodNames(SkylarkNativeModule.class)) { + builder.put(nativeFunction, + FuncallExpression.getBuiltinCallable(nativeModuleInstance, nativeFunction)); } for (Map.Entry<String, BaseFunction> function : workspaceFunctions.entrySet()) { builder.put(function.getKey(), function.getValue()); 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 = |