New class hierarchy for Skylark functions
* New hierarchy BaseFunction > UserModeFunction, BuiltinFunction.
The old hierarchy still exists for now, to be deleted after migration:
Function > AbstractFunction > MixedModeFunction >
(UserModeFunction, SkylarkFunction > SimpleSkylarkFunction)
(UserModeFunction is already migrated, and
BaseFunction implements Function, for now.)
* Function supports *args and **kwargs when calling functions, and
mandatory named-only parameters in the style of Python 3.
Notable difference with Python: *args binds the variable to a tuple,
because a Skylark list would have to be monomorphic.
* A better, simpler, safer FFI using reflection with BuiltinFunction.
Handles typechecking, passes parameters in a more Java style.
(Not used for now, will be used later.)
* A new annotation @SkylarkSignature, intended to replace @SkylarkBuiltin,
supports the full function call protocol, including default arguments.
* Support for annotating function Factory-s rather than functions.
--
MOS_MIGRATED_REVID=88958581
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
new file mode 100644
index 0000000..f1a6bd7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
@@ -0,0 +1,474 @@
+// Copyright 2014 Google Inc. 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.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.Type.ConversionException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * A base class for Skylark functions, whether builtin or user-defined.
+ *
+ * <p>Nomenclature:
+ * We call "Parameters" the formal parameters of a function definition.
+ * We call "Arguments" the actual values supplied at the call site.
+ *
+ * <p>The outer calling convention is like that of python3,
+ * with named parameters that can be mandatory or optional, and also be positional or named-only,
+ * and rest parameters for extra positional and keyword arguments.
+ * Callers supply a {@code List<Object>} args for positional arguments
+ * and a {@code Map<String, Object>} for keyword arguments,
+ * where positional arguments will be resolved first, then keyword arguments,
+ * with errors for a clash between the two, for missing mandatory parameter,
+ * or for unexpected extra positional or keyword argument in absence of rest parameter.
+ *
+ * <p>The inner calling convention is to pass the underlying method
+ * an {@code Object[]} of the type-checked argument values, one per expected parameter,
+ * parameters being sorted as documented in {@link FunctionSignature}.
+ *
+ * <p>The function may provide default values for optional parameters not provided by the caller.
+ * These default values can be null if there are no optional parameters or for builtin functions,
+ * but not for user-defined functions that have optional parameters.
+ */
+// TODO(bazel-team):
+// Provide optimized argument frobbing depending of FunctionSignature and CallerSignature
+// (that FuncallExpression must supply), optimizing for the all-positional and all-keyword cases.
+// Also, use better pure maps to minimize map O(n) re-creation events when processing keyword maps.
+public abstract class BaseFunction implements Function {
+
+ // The name of the function
+ private final String name;
+
+ // A function signature, including defaults and types
+ // never null after it is configured
+ @Nullable protected FunctionSignature.WithValues<Object, SkylarkType> signature;
+
+ // Location of the function definition, or null for builtin functions
+ @Nullable protected Location location;
+
+ // Some functions are also Namespaces or other Skylark entities.
+ @Nullable protected Class<?> objectType;
+
+ // Documentation for variables, if any
+ @Nullable protected List<String> paramDoc;
+
+ // True if this function is only allowed during the Loading Phase
+ protected boolean onlyLoadingPhase;
+
+ // The types actually enforced by the Skylark runtime, as opposed to those enforced by the JVM,
+ // or those displayed to the user in the documentation.
+ @Nullable protected List<SkylarkType> enforcedArgumentTypes;
+
+ // Defaults to be used when configure(annotation) is called (after the function is constructed).
+ @Nullable private Iterable<Object> unconfiguredDefaultValues;
+ // The configure(annotation) function will include these defaults in the function signature.
+ // We need to supply these defaultValues to the constructor, that will store them here, because
+ // they can't be supplied via Java annotations, due to the limitations in the annotation facility.
+ // (For extra brownies, we could supply them as Skylark expression strings, to be evaluated by our
+ // evaluator without the help of any unconfigured functions, or to be processed at compile-time;
+ // but we resolve annotations at runtime for now.)
+ // Limitations in Java annotations mean we can't express them in the SkylarkSignature annotation.
+ // (In the future, we could parse and evaluate simple Skylark expression strings, but then
+ // we'd have to be very careful of circularities during initialization).
+ // Note that though we want this list to be immutable, we don't use ImmutableList,
+ // because that can't store nulls and nulls are essential for some BuiltinFunction-s.
+ // We trust the user not to modify the list behind our back.
+
+
+ /** Returns the name of this function. */
+ public String getName() {
+ return name;
+ }
+
+ /** Returns the signature of this function. */
+ @Nullable public FunctionSignature.WithValues<Object, SkylarkType> getSignature() {
+ return signature;
+ }
+
+ /** This function may also be viewed by Skylark as being of a special ObjectType */
+ @Nullable public Class<?> getObjectType() {
+ return objectType;
+ }
+
+ /** Returns true if the BaseFunction is configured. */
+ public boolean isConfigured() {
+ return signature != null;
+ }
+
+ /** Returns true if the function is only available during loading phase */
+ public boolean isOnlyLoadingPhase() {
+ return onlyLoadingPhase;
+ }
+
+ /**
+ * Creates an unconfigured BaseFunction with the given name.
+ *
+ * @param name the function name
+ */
+ public BaseFunction(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Constructs a BaseFunction with a given name, signature and location.
+ *
+ * @param name the function name
+ * @param signature the signature with default values and types
+ * @param location the location of function definition
+ */
+ public BaseFunction(String name,
+ @Nullable FunctionSignature.WithValues<Object, SkylarkType> signature,
+ @Nullable Location location) {
+ this(name);
+ this.signature = signature;
+ this.location = location;
+ }
+
+ /**
+ * Constructs a BaseFunction with a given name, signature.
+ *
+ * @param name the function name
+ * @param signature the signature, with default values and types
+ */
+ public BaseFunction(String name,
+ @Nullable FunctionSignature.WithValues<Object, SkylarkType> signature) {
+ this(name, signature, null);
+ }
+
+ /**
+ * Constructs a BaseFunction with a given name and signature without default values or types.
+ *
+ * @param name the function name
+ * @param signature the signature, without default values or types
+ */
+ public BaseFunction(String name, FunctionSignature signature) {
+ this(name, FunctionSignature.WithValues.<Object, SkylarkType>create(signature), null);
+ }
+
+ /**
+ * Constructs a BaseFunction with a given name and list of unconfigured defaults.
+ *
+ * @param name the function name
+ * @param defaultValues a list of default values for the optional arguments to be configured.
+ */
+ public BaseFunction(String name, @Nullable Iterable<Object> defaultValues) {
+ this(name);
+ this.unconfiguredDefaultValues = defaultValues;
+ }
+
+ /** Get parameter documentation as a list corresponding to each parameter */
+ public List<String> getParamDoc() {
+ return paramDoc;
+ }
+
+ /**
+ * The size of the array required by the callee.
+ */
+ protected int getArgArraySize() {
+ return signature.getSignature().getShape().getArguments();
+ }
+
+ /**
+ * The types that will be actually enforced by Skylark itself, so we may skip those already
+ * enforced by the JVM during calls to BuiltinFunction, but also so we may lie to the user
+ * in the automatically-generated documentation
+ */
+ public List<SkylarkType> getEnforcedArgumentTypes() {
+ return enforcedArgumentTypes;
+ }
+
+ /**
+ * Process the caller-provided arguments into an array suitable for the callee (this function).
+ */
+ public Object[] processArguments(@Nullable List<Object> args,
+ @Nullable Map<String, Object> kwargs,
+ @Nullable Location loc)
+ throws EvalException {
+
+ Object[] arguments = new Object[getArgArraySize()];
+
+ // extract function signature
+ FunctionSignature sig = signature.getSignature();
+ FunctionSignature.Shape shape = sig.getShape();
+ ImmutableList<String> names = sig.getNames();
+ List<Object> defaultValues = signature.getDefaultValues();
+
+ // Note that this variable will be adjusted down if there are extra positionals,
+ // after these extra positionals are dumped into starParam.
+ int numPositionalArgs = args.size();
+
+ int numMandatoryPositionalParams = shape.getMandatoryPositionals();
+ int numOptionalPositionalParams = shape.getOptionalPositionals();
+ int numMandatoryNamedOnlyParams = shape.getMandatoryNamedOnly();
+ int numOptionalNamedOnlyParams = shape.getOptionalNamedOnly();
+ boolean hasStarParam = shape.hasStarArg();
+ boolean hasKwParam = shape.hasKwArg();
+ int numPositionalParams = numMandatoryPositionalParams + numOptionalPositionalParams;
+ int numNamedOnlyParams = numMandatoryNamedOnlyParams + numOptionalNamedOnlyParams;
+ int numNamedParams = numPositionalParams + numNamedOnlyParams;
+ int numOptionalParams = numOptionalPositionalParams + numOptionalNamedOnlyParams;
+ int endOptionalParams = numMandatoryPositionalParams + numOptionalParams;
+ int kwParamIndex = names.size() - 1; // only valid if hasKwParam
+
+ // (1) handle positional arguments
+ if (hasStarParam) {
+ // Nota Bene: we collect extra positional arguments in a (tuple,) rather than a [list],
+ // so the collection can be heterogeneous; that's a notable difference with Python.
+ int starParamIndex = numNamedParams;
+ if (numPositionalArgs > numPositionalParams) {
+ arguments[starParamIndex] = SkylarkList.tuple(
+ args.subList(numPositionalParams, numPositionalArgs));
+ numPositionalArgs = numPositionalParams; // clip numPositionalArgs
+ } else {
+ arguments[starParamIndex] = SkylarkList.EMPTY_TUPLE;
+ }
+ } else if (numPositionalArgs > numPositionalParams) {
+ throw new EvalException(loc,
+ numPositionalParams > 0
+ ? "too many (" + numPositionalArgs + ") positional arguments in call to " + this
+ : this + " does not accept positional arguments, but got " + numPositionalArgs);
+ }
+
+ for (int i = 0; i < numPositionalArgs; i++) {
+ arguments[i] = args.get(i);
+ }
+
+ // (2) handle keyword arguments
+ if (kwargs == null || kwargs.isEmpty()) {
+ // Easy case (2a): there are no keyword arguments.
+ // All arguments were positional, so check we had enough to fill all mandatory positionals.
+ if (numPositionalArgs < numMandatoryPositionalParams) {
+ throw new EvalException(loc, String.format(
+ "insufficient arguments received by %s (got %s, expected at least %s)",
+ this, numPositionalArgs, numMandatoryPositionalParams));
+ }
+ // We had no named argument, so fail if there were mandatory named-only parameters
+ if (numMandatoryNamedOnlyParams > 0) {
+ throw new EvalException(loc, String.format(
+ "missing mandatory keyword arguments in call to %s", this));
+ }
+ // Fill in defaults for missing optional parameters, that were conveniently grouped together.
+ if (defaultValues != null) {
+ int j = numPositionalArgs - numMandatoryPositionalParams;
+ for (int i = numPositionalArgs; i < endOptionalParams; i++) {
+ arguments[i] = defaultValues.get(j++);
+ }
+ }
+ // If there's a kwParam, it's empty.
+ if (hasKwParam) {
+ arguments[kwParamIndex] = ImmutableMap.<String, Object>of();
+ }
+ } else if (hasKwParam && numNamedParams == 0) {
+ // Easy case (2b): there are no named parameters, but there is a **kwParam.
+ // Therefore all keyword arguments go directly to the kwParam.
+ // Note that *starParam and **kwParam themselves don't count as named.
+ // Also note that no named parameters means no mandatory parameters that weren't passed,
+ // and no missing optional parameters for which to use a default. Thus, no loops.
+ arguments[kwParamIndex] = kwargs; // NB: not 2a means kwarg isn't null
+ } else {
+ // Hard general case (2c): some keyword arguments may correspond to named parameters
+ HashMap<String, Object> kwArg = hasKwParam ? new HashMap<String, Object>() : null;
+
+ // For nicer stabler error messages, start by checking against
+ // an argument being provided both as positional argument and as keyword argument.
+ ArrayList<String> bothPosKey = new ArrayList<>();
+ for (int i = 0; i < numPositionalArgs; i++) {
+ String name = names.get(i);
+ if (kwargs.containsKey(name)) { bothPosKey.add(name); }
+ }
+ if (!bothPosKey.isEmpty()) {
+ throw new EvalException(loc,
+ String.format("argument%s '%s' passed both by position and by name in call to %s",
+ (bothPosKey.size() > 1 ? "s" : ""), Joiner.on("', '").join(bothPosKey), this));
+ }
+
+ // Accept the arguments that were passed.
+ for (Map.Entry<String, Object> entry : kwargs.entrySet()) {
+ String keyword = entry.getKey();
+ Object value = entry.getValue();
+ int pos = names.indexOf(keyword); // the list should be short, so linear scan is OK.
+ if (0 <= pos && pos < numNamedParams) {
+ arguments[pos] = value;
+ } else {
+ if (!hasKwParam) {
+ List<String> unexpected = Ordering.natural().sortedCopy(Sets.difference(
+ kwargs.keySet(), ImmutableSet.copyOf(names.subList(0, numNamedParams))));
+ throw new EvalException(loc, String.format("unexpected keyword%s '%s' in call to %s",
+ unexpected.size() > 1 ? "s" : "", Joiner.on("', '").join(unexpected), this));
+ }
+ if (kwArg.containsKey(keyword)) {
+ throw new EvalException(loc, String.format(
+ "%s got multiple values for keyword argument '%s'", this, keyword));
+ }
+ kwArg.put(keyword, value);
+ }
+ }
+ if (hasKwParam) {
+ arguments[kwParamIndex] = ImmutableMap.copyOf(kwArg);
+ }
+
+ // Check that all mandatory parameters were filled in general case 2c.
+ // Note: it's possible that numPositionalArgs > numMandatoryPositionalParams but that's OK.
+ for (int i = numPositionalArgs; i < numMandatoryPositionalParams; i++) {
+ if (arguments[i] == null) {
+ throw new EvalException(loc, String.format(
+ "missing mandatory positional argument '%s' while calling %s",
+ names.get(i), toString()));
+ }
+ }
+
+ for (int i = numNamedParams - numMandatoryNamedOnlyParams; i < numNamedParams; i++) {
+ if (arguments[i] == null) {
+ throw new EvalException(loc, String.format(
+ "missing mandatory named-only argument '%s' while calling %s",
+ names.get(i), toString()));
+ }
+ }
+
+ // Get defaults for those parameters that weren't passed.
+ if (defaultValues != null) {
+ for (int j = numPositionalArgs > numMandatoryPositionalParams
+ ? numPositionalArgs - numMandatoryPositionalParams : 0;
+ j < numOptionalParams; j++) {
+ int i = j + numMandatoryPositionalParams;
+ if (arguments[i] == null) {
+ arguments[i] = defaultValues.get(j);
+ }
+ }
+ }
+ } // End of general case 2c for argument passing.
+
+ return arguments;
+ }
+
+ /** check types and convert as required */
+ protected void canonicalizeArguments(Object[] arguments, Location loc) throws EvalException {
+ // TODO(bazel-team): maybe link syntax.SkylarkType and package.Type,
+ // so we can simultaneously typecheck and convert?
+ // Note that a BuiltinFunction already does typechecking of simple types.
+
+ List<SkylarkType> types = getEnforcedArgumentTypes();
+
+ // Check types, if supplied
+ if (types == null) {
+ return;
+ }
+ List<String> names = signature.getSignature().getNames();
+ int length = types.size();
+ for (int i = 0; i < length; i++) {
+ Object value = arguments[i];
+ SkylarkType type = types.get(i);
+ if (value != null && type != null && !type.contains(value)) {
+ throw new EvalException(loc,
+ String.format("expected %s for '%s' while calling %s but got %s instead: %s",
+ type, names.get(i), getName(), EvalUtils.getDataTypeName(value, true), value));
+ }
+ }
+ }
+
+ /**
+ * The outer calling convention to a BaseFunction.
+ *
+ * @param args a list of all positional arguments (as in *starArg)
+ * @param kwargs a map for key arguments (as in **kwArgs)
+ * @param ast the expression for this function's definition
+ * @param env the lexical Environment for the function call
+ * @return the value resulting from evaluating the function with the given arguments
+ * @throws construction of EvalException-s containing source information.
+ */
+ @Override
+ public Object call(@Nullable List<Object> args,
+ @Nullable Map<String, Object> kwargs,
+ @Nullable FuncallExpression ast,
+ @Nullable Environment env)
+ throws EvalException, InterruptedException {
+
+ Preconditions.checkState(isConfigured(), "Function %s was not configured", getName());
+
+ // ast is null when called from Java (as there's no Skylark call site).
+ Location loc = ast == null ? location : ast.getLocation();
+
+ Object[] arguments = processArguments(args, kwargs, loc);
+ canonicalizeArguments(arguments, loc);
+
+ try {
+ return call(arguments, ast, env);
+ } catch (ConversionException e) {
+ throw new EvalException(loc, e.getMessage());
+ }
+ }
+
+ /**
+ * Inner call to a BaseFunction
+ * subclasses need to @Override this method.
+ *
+ * @param args an array of argument values sorted as per the signature.
+ * @param ast the source code for the function if user-defined
+ * @param ast the lexical environment of the function call
+ */
+ // Don't make it abstract, so that subclasses may be defined that @Override the outer call() only.
+ public Object call(Object[] args,
+ @Nullable FuncallExpression ast, @Nullable Environment env)
+ throws EvalException, ConversionException, InterruptedException {
+ throw new EvalException(ast.getLocation(),
+ String.format("function %s not implemented", getName()));
+ }
+
+ /**
+ * Render this object in the form of an equivalent Python function signature.
+ */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(getName());
+ if (signature != null) {
+ sb.append('(');
+ signature.toStringBuffer(sb);
+ sb.append(')');
+ } // if unconfigured, don't even output parentheses
+ return sb.toString();
+ }
+
+ /** Configure a BaseFunction from a @SkylarkSignature annotation */
+ public void configure(SkylarkSignature annotation) {
+ Preconditions.checkState(!isConfigured()); // must not be configured yet
+
+ this.paramDoc = new ArrayList<>();
+ this.signature = SkylarkSignatureProcessor.getSignature(
+ getName(), annotation, unconfiguredDefaultValues, paramDoc, getEnforcedArgumentTypes());
+ this.objectType = annotation.objectType().equals(Object.class)
+ ? null : annotation.objectType();
+ this.onlyLoadingPhase = annotation.onlyLoadingPhase();
+ configure();
+ }
+
+ /** Configure a function based on its signature */
+ protected void configure() {
+ // this function is called after the signature was initialized
+ Preconditions.checkState(signature != null);
+ enforcedArgumentTypes = signature.getTypes();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java
new file mode 100644
index 0000000..23b5c7d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java
@@ -0,0 +1,330 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.Type.ConversionException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ExecutionException;
+
+import javax.annotation.Nullable;
+
+/**
+ * A class for Skylark functions provided as builtins by the Skylark implementation
+ */
+public class BuiltinFunction extends BaseFunction {
+
+ /** ExtraArgKind so you can tweek your function's own calling convention */
+ public static enum ExtraArgKind {
+ LOCATION,
+ SYNTAX_TREE,
+ ENVIRONMENT;
+ }
+ // Predefined system add-ons to function signatures
+ public static final ExtraArgKind[] USE_LOC =
+ new ExtraArgKind[] {ExtraArgKind.LOCATION};
+ public static final ExtraArgKind[] USE_LOC_ENV =
+ new ExtraArgKind[] {ExtraArgKind.LOCATION, ExtraArgKind.ENVIRONMENT};
+ public static final ExtraArgKind[] USE_AST =
+ new ExtraArgKind[] {ExtraArgKind.SYNTAX_TREE};
+ public static final ExtraArgKind[] USE_AST_ENV =
+ new ExtraArgKind[] {ExtraArgKind.SYNTAX_TREE, ExtraArgKind.ENVIRONMENT};
+
+
+ // The underlying invoke() method.
+ @Nullable private Method invokeMethod;
+
+ // extra arguments required beside signature.
+ @Nullable private ExtraArgKind[] extraArgs;
+
+ // The count of arguments in the inner invoke method,
+ // to be used as size of argument array by the outer call method.
+ private int innerArgumentCount;
+
+
+ /** Create unconfigured function from its name */
+ public BuiltinFunction(String name) {
+ super(name);
+ }
+
+ /** Creates an unconfigured BuiltinFunction with the given name and defaultValues */
+ public BuiltinFunction(String name, Iterable<Object> defaultValues) {
+ super(name, defaultValues);
+ }
+
+ /** Creates a BuiltinFunction with the given name and signature */
+ public BuiltinFunction(String name, FunctionSignature signature) {
+ super(name, signature);
+ configure();
+ }
+
+ /** Creates a BuiltinFunction with the given name and signature with values */
+ public BuiltinFunction(String name,
+ FunctionSignature.WithValues<Object, SkylarkType> signature) {
+ super(name, signature);
+ configure();
+ }
+
+ /** Creates a BuiltinFunction with the given name and signature and extra arguments */
+ public BuiltinFunction(String name, FunctionSignature signature, ExtraArgKind[] extraArgs) {
+ super(name, signature);
+ this.extraArgs = extraArgs;
+ configure();
+ }
+
+ /** Creates a BuiltinFunction with the given name, signature with values, and extra arguments */
+ public BuiltinFunction(String name,
+ FunctionSignature.WithValues<Object, SkylarkType> signature, ExtraArgKind[] extraArgs) {
+ super(name, signature);
+ this.extraArgs = extraArgs;
+ configure();
+ }
+
+
+ /** Creates a BuiltinFunction from the given name and a Factory */
+ public BuiltinFunction(String name, Factory factory) {
+ super(name);
+ configure(factory);
+ }
+
+ @Override
+ protected int getArgArraySize () {
+ return innerArgumentCount;
+ }
+
+ protected ExtraArgKind[] getExtraArgs () {
+ return extraArgs;
+ }
+
+ @Override
+ @Nullable
+ public Object call(Object[] args,
+ @Nullable FuncallExpression ast, @Nullable Environment env)
+ throws EvalException, InterruptedException {
+ final Location loc = (ast == null) ? location : ast.getLocation();
+
+ // Add extra arguments, if needed
+ if (extraArgs != null) {
+ int i = args.length - extraArgs.length;
+ for (BuiltinFunction.ExtraArgKind extraArg : extraArgs) {
+ switch(extraArg) {
+ case LOCATION:
+ args[i] = loc;
+ break;
+
+ case SYNTAX_TREE:
+ args[i] = ast;
+ break;
+
+ case ENVIRONMENT:
+ args[i] = env;
+ break;
+ }
+ i++;
+ }
+ }
+
+ // Last but not least, actually make an inner call to the function with the resolved arguments.
+ try {
+ return invokeMethod.invoke(this, args);
+ } catch (InvocationTargetException x) {
+ Throwable e = x.getCause();
+ if (e instanceof EvalException) {
+ throw (EvalException) e;
+ } else if (e instanceof InterruptedException) {
+ throw (InterruptedException) e;
+ } else if (e instanceof ConversionException
+ || e instanceof ClassCastException
+ || e instanceof ExecutionException
+ || e instanceof IllegalStateException) {
+ throw new EvalException(loc, e.getMessage(), e);
+ } else if (e instanceof IllegalArgumentException) {
+ // Assume it was thrown by SkylarkType.cast and has a good message.
+ throw new EvalException(loc, String.format("%s\nin call to %s", e.getMessage(), this), e);
+ } else {
+ throw badCallException(loc, e, args);
+ }
+ } catch (IllegalArgumentException e) {
+ // Either this was thrown by Java itself, or it's a bug
+ // To cover the first case, let's manually check the arguments.
+ final int len = args.length - ((extraArgs == null) ? 0 : extraArgs.length);
+ final Class<?>[] types = invokeMethod.getParameterTypes();
+ for (int i = 0; i < args.length; i++) {
+ if (args[i] != null && !types[i].isAssignableFrom(args[i].getClass())) {
+ final String paramName = i < len
+ ? signature.getSignature().getNames().get(i) : extraArgs[i - len].name();
+ throw new EvalException(loc, String.format(
+ "expected %s for '%s' while calling %s but got %s instead",
+ EvalUtils.getDataTypeNameFromClass(types[i]), paramName, getName(),
+ EvalUtils.getDataTypeName(args[i])), e);
+ }
+ }
+ throw badCallException(loc, e, args);
+ } catch (IllegalAccessException e) {
+ throw badCallException(loc, e, args);
+ }
+ }
+
+ private IllegalStateException badCallException(Location loc, Throwable e, Object... args) {
+ // If this happens, it's a bug in our code.
+ return new IllegalStateException(String.format("%s%s (%s)\n"
+ + "while calling %s with args %s\nJava parameter types: %s\nSkylark type checks: %s",
+ (loc == null) ? "" : loc + ": ",
+ e.getClass().getName(), e.getMessage(), this,
+ Arrays.asList(args),
+ Arrays.asList(invokeMethod.getParameterTypes()),
+ signature.getTypes()), e);
+ }
+
+
+ /** Configure the reflection mechanism */
+ @Override
+ public void configure(SkylarkSignature annotation) {
+ Preconditions.checkState(!isConfigured()); // must not be configured yet
+ enforcedArgumentTypes = new ArrayList<>();
+ this.extraArgs = SkylarkSignatureProcessor.getExtraArgs(annotation);
+ super.configure(annotation);
+ }
+
+ // finds the method and makes it accessible (which is needed to find it, and later to use it)
+ protected Method findMethod(final String name) {
+ Method found = null;
+ for (Method method : this.getClass().getDeclaredMethods()) {
+ method.setAccessible(true);
+ if (name.equals(method.getName())) {
+ if (method != null) {
+ throw new IllegalArgumentException(String.format(
+ "function %s has more than one method named %s", getName(), name));
+ }
+ found = method;
+ }
+ }
+ if (found == null) {
+ throw new NoSuchElementException(String.format(
+ "function %s doesn't have a method named %s", getName(), name));
+ }
+ return found;
+ }
+
+ /** Configure the reflection mechanism */
+ @Override
+ protected void configure() {
+ invokeMethod = findMethod("invoke");
+
+ int arguments = signature.getSignature().getShape().getArguments();
+ innerArgumentCount = arguments + (extraArgs == null ? 0 : extraArgs.length);
+ Class<?>[] parameterTypes = invokeMethod.getParameterTypes();
+ Preconditions.checkArgument(innerArgumentCount == parameterTypes.length, getName());
+
+ // TODO(bazel-team): also grab the returnType from the annotations,
+ // and check it against method return type
+ if (enforcedArgumentTypes != null) {
+ for (int i = 0; i < arguments; i++) {
+ SkylarkType enforcedType = enforcedArgumentTypes.get(i);
+ if (enforcedType != null) {
+ Class<?> parameterType = parameterTypes[i];
+ String msg = String.format("fun %s, param %s, enforcedType: %s (%s); parameterType: %s",
+ getName(), signature.getSignature().getNames().get(i),
+ enforcedType, enforcedType.getClass(), parameterType);
+ if (enforcedType instanceof SkylarkType.Simple) {
+ Preconditions.checkArgument(
+ enforcedType == SkylarkType.of(parameterType), msg);
+ // No need to enforce Simple types on the Skylark side, the JVM will do it for us.
+ enforcedArgumentTypes.set(i, null);
+ } else if (enforcedType instanceof SkylarkType.Combination) {
+ Preconditions.checkArgument(
+ enforcedType.getType() == parameterType, msg);
+ } else {
+ Preconditions.checkArgument(
+ parameterType == Object.class || parameterType == null, msg);
+ }
+ }
+ }
+ }
+ // No need for the enforcedArgumentTypes List if all the types were Simple
+ enforcedArgumentTypes = FunctionSignature.<SkylarkType>valueListOrNull(enforcedArgumentTypes);
+ }
+
+ /** Configure by copying another function's configuration */
+ // Alternatively, we could have an extension BuiltinFunctionSignature of FunctionSignature,
+ // and use *that* instead of a Factory.
+ public void configure(BuiltinFunction.Factory factory) {
+ // this function must not be configured yet, but the factory must be
+ Preconditions.checkState(!isConfigured());
+ Preconditions.checkState(factory.isConfigured(),
+ "function factory is not configured for %s", getName());
+
+ this.paramDoc = factory.getParamDoc();
+ this.signature = factory.getSignature();
+ this.extraArgs = factory.getExtraArgs();
+ this.objectType = factory.getObjectType();
+ this.onlyLoadingPhase = factory.isOnlyLoadingPhase();
+ configure();
+ }
+
+ /**
+ * A Factory allows for a @SkylarkSignature annotation to be provided and processed in advance
+ * for a function that will be defined later as a closure (see e.g. in PackageFactory).
+ *
+ * <p>Each instance of this class must define a method create that closes over some (final)
+ * variables and returns a BuiltinFunction.
+ */
+ public abstract static class Factory extends BuiltinFunction {
+ @Nullable private Method createMethod;
+
+ /** Create unconfigured function Factory from its name */
+ public Factory(String name) {
+ super(name);
+ }
+
+ /** Creates an unconfigured function Factory with the given name and defaultValues */
+ public Factory(String name, Iterable<Object> defaultValues) {
+ super(name, defaultValues);
+ }
+
+ @Override
+ public void configure() {
+ if (createMethod != null) {
+ return;
+ }
+ createMethod = findMethod("create");
+ }
+
+ @Override
+ public Object call(Object[] args, @Nullable FuncallExpression ast, @Nullable Environment env)
+ throws EvalException {
+ throw new EvalException(null, "Tried to invoke a Factory for function " + this);
+ }
+
+ /** Instantiate the Factory
+ * @param args arguments to pass to the create method
+ * @return a new BuiltinFunction that closes over the arguments
+ */
+ public BuiltinFunction apply(Object... args) {
+ try {
+ return (BuiltinFunction) createMethod.invoke(this, args);
+ } catch (InvocationTargetException | IllegalArgumentException | IllegalAccessException e) {
+ throw new RuntimeException(String.format(
+ "Exception while applying BuiltinFunction.Factory %s: %s",
+ this, e.getMessage()), e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FunctionDefStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/FunctionDefStatement.java
index 28639bc..5b0119e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FunctionDefStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FunctionDefStatement.java
@@ -13,7 +13,6 @@
// 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.syntax.SkylarkType.SkylarkFunctionType;
@@ -34,12 +33,6 @@
public FunctionDefStatement(Ident ident,
FunctionSignature.WithValues<Expression, Expression> args,
Collection<Statement> statements) {
-
- // TODO(bazel-team): lift the following limitation from {@link MixedModeFunction}
- FunctionSignature.Shape shape = args.getSignature().getShape();
- Preconditions.checkArgument(!shape.hasKwArg() && !shape.hasStarArg()
- && shape.getNamedOnly() == 0, "no star, star-star or named-only parameters (for now)");
-
this.ident = ident;
this.args = args;
this.statements = ImmutableList.copyOf(statements);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
index 9805e45..5816007 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
@@ -1151,13 +1151,6 @@
expect(TokenKind.RPAREN);
expect(TokenKind.COLON);
List<Statement> block = parseSuite();
- // TODO(bazel-team): lift this limitation
- FunctionSignature.Shape shape = args.getSignature().getShape();
- if (shape.hasKwArg() || shape.hasStarArg() || shape.getNamedOnly() > 0) {
- reportError(lexer.createLocation(start, token.right),
- "no star, star-star or named-only parameters (for now)");
- return;
- }
FunctionDefStatement stmt = new FunctionDefStatement(ident, args, block);
list.add(setLocation(stmt, start, token.right));
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignature.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignature.java
new file mode 100644
index 0000000..1bd34a9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignature.java
@@ -0,0 +1,90 @@
+// Copyright 2014 Google Inc. 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * An annotation to mark built-in keyword argument methods accessible from Skylark.
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SkylarkSignature {
+
+ // TODO(bazel-team): parse most everything from single string specifying the signature
+ // in Skylark syntax, e.g.: signature = "foo(a: string, b: ListOf(int)) -> NoneType"
+ // String signature() default "";
+
+ String name();
+
+ String doc();
+
+ Param[] mandatoryPositionals() default {};
+
+ Param[] optionalPositionals() default {};
+
+ Param[] optionalNamedOnly() default {};
+
+ Param[] mandatoryNamedOnly() default {};
+
+ Param[] extraPositionals() default {};
+
+ Param[] extraKeywords() default {};
+
+ boolean undocumented() default false;
+
+ Class<?> objectType() default Object.class;
+
+ Class<?> returnType() default Object.class;
+
+ boolean onlyLoadingPhase() default false;
+
+ // TODO(bazel-team): determine this way whether to accept mutable Lists
+ // boolean mutableLists() default false;
+
+ boolean useLocation() default false;
+
+ boolean useAst() default false;
+
+ boolean useEnvironment() default false;
+
+ /**
+ * An annotation for parameters of Skylark built-in functions.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Param {
+
+ String name();
+
+ String doc();
+
+ String defaultValue() default "";
+
+ Class<?> type() default Object.class;
+
+ Class<?> generic1() default Object.class;
+
+ boolean callbackEnabled() default false;
+
+ boolean noneable() default false;
+
+ // TODO(bazel-team): parse the type from a single field in Skylark syntax,
+ // and allow a Union as "ThisType or ThatType or NoneType":
+ // String type() default "Object";
+ }
+}
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
new file mode 100644
index 0000000..6140188
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
@@ -0,0 +1,247 @@
+// Copyright 2014 Google Inc. 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.devtools.build.lib.syntax.BuiltinFunction.ExtraArgKind;
+import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * This class defines utilities to process @SkylarkSignature annotations
+ * to configure a given field.
+ */
+public class SkylarkSignatureProcessor {
+ /**
+ * Extracts a {@code FunctionSignature.WithValues<Object, SkylarkType>} from an annotation
+ * @param name the name of the function
+ * @param annotation the annotation
+ * @param defaultValues an optional list of default values
+ * @param paramDoc an optional list into which to store documentation strings
+ * @param enforcedTypesList an optional list into which to store effective types to enforce
+ */
+ // 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(
+ 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());
+ ArrayList<Parameter<Object, SkylarkType>> paramList = new ArrayList<>();
+ HashMap<String, SkylarkType> enforcedTypes = enforcedTypesList == null
+ ? null : new HashMap<String, SkylarkType>();
+ HashMap<String, String> doc = new HashMap<>();
+
+ Iterator<Object> defaultValuesIterator = defaultValues == null
+ ? null : defaultValues.iterator();
+ try {
+ for (Param param : annotation.mandatoryPositionals()) {
+ paramList.add(getParameter(name, param, doc, enforcedTypes,
+ /*mandatory=*/true, /*star=*/false, /*starStar=*/false, /*defaultValue=*/null));
+ }
+ for (Param param : annotation.optionalPositionals()) {
+ paramList.add(getParameter(name, param, doc, enforcedTypes,
+ /*mandatory=*/false, /*star=*/false, /*starStar=*/false,
+ /*defaultValue=*/getDefaultValue(param, defaultValuesIterator)));
+ }
+ if (annotation.extraPositionals().length > 0
+ || annotation.optionalNamedOnly().length > 0
+ || annotation.mandatoryNamedOnly().length > 0) {
+ @Nullable Param starParam = null;
+ if (annotation.extraPositionals().length > 0) {
+ Preconditions.checkArgument(annotation.extraPositionals().length == 1);
+ starParam = annotation.extraPositionals()[0];
+ }
+ paramList.add(getParameter(name, starParam, doc, enforcedTypes,
+ /*mandatory=*/false, /*star=*/true, /*starStar=*/false, /*defaultValue=*/null));
+ }
+ for (Param param : annotation.optionalNamedOnly()) {
+ paramList.add(getParameter(name, param, doc, enforcedTypes,
+ /*mandatory=*/false, /*star=*/false, /*starStar=*/false,
+ /*defaultValue=*/getDefaultValue(param, defaultValuesIterator)));
+ }
+ for (Param param : annotation.mandatoryNamedOnly()) {
+ paramList.add(getParameter(name, param, doc, enforcedTypes,
+ /*mandatory=*/true, /*star=*/false, /*starStar=*/false, /*defaultValue=*/null));
+ }
+ if (annotation.extraKeywords().length > 0) {
+ Preconditions.checkArgument(annotation.extraKeywords().length == 1);
+ paramList.add(
+ getParameter(name, annotation.extraKeywords()[0], doc, enforcedTypes,
+ /*mandatory=*/false, /*star=*/false, /*starStar=*/true, /*defaultValue=*/null));
+ }
+ FunctionSignature.WithValues<Object, SkylarkType> signature =
+ FunctionSignature.WithValues.<Object, SkylarkType>of(paramList);
+ for (String paramName : signature.getSignature().getNames()) {
+ if (enforcedTypesList != null) {
+ enforcedTypesList.add(enforcedTypes.get(paramName));
+ }
+ if (paramDoc != null) {
+ paramDoc.add(doc.get(paramName));
+ }
+ }
+ return signature;
+ } catch (FunctionSignature.SignatureException e) {
+ throw new RuntimeException(String.format(
+ "Invalid signature while configuring BuiltinFunction %s", name), e);
+ }
+ }
+
+ /**
+ * Configures the parameter of this Skylark function using the annotation.
+ */
+ // TODO(bazel-team): Maybe have the annotation be a string representing the
+ // python-style calling convention including default values, and have the regular Parser
+ // process it? (builtin function call not allowed when evaluating values, but more complex
+ // values are possible by referencing variables in some definition environment).
+ // Then the only per-parameter information needed is a documentation string.
+ private static Parameter<Object, SkylarkType> getParameter(String name,
+ Param param, Map<String, String> paramDoc, Map<String, SkylarkType> enforcedTypes,
+ boolean mandatory, boolean star, boolean starStar, @Nullable Object defaultValue)
+ throws FunctionSignature.SignatureException {
+
+ @Nullable SkylarkType officialType = null;
+ @Nullable SkylarkType enforcedType = null;
+ if (star && param == null) { // pseudo-parameter to separate positional from named-only
+ return new Parameter.Star<>(null);
+ }
+ if (param.type() != Object.class) {
+ if (param.type() == List.class) {
+ // NB: a List in the annotation actually indicates either a List or a SkylarkList
+ // and we trust the BuiltinFunction to do the enforcement.
+ // For automatic document generation purpose, we lie and just say it's a list;
+ // hopefully user code should never be exposed to the java List case
+ officialType = SkylarkType.of(SkylarkList.class, param.generic1());
+ enforcedType = SkylarkType.Union.of(
+ SkylarkType.of(List.class),
+ SkylarkType.of(SkylarkList.class, param.generic1()));
+ Preconditions.checkArgument(enforcedType instanceof SkylarkType.Union);
+ } else if (param.generic1() != Object.class) {
+ // Otherwise, we will enforce the proper parametric type for Skylark list and set objects
+ officialType = SkylarkType.of(param.type(), param.generic1());
+ enforcedType = officialType;
+ } else {
+ officialType = SkylarkType.of(param.type());
+ enforcedType = officialType;
+ }
+ if (param.callbackEnabled()) {
+ officialType = SkylarkType.Union.of(
+ officialType, SkylarkType.SkylarkFunctionType.of(name, officialType));
+ enforcedType = SkylarkType.Union.of(
+ enforcedType, SkylarkType.SkylarkFunctionType.of(name, enforcedType));
+ }
+ if (param.noneable()) {
+ officialType = SkylarkType.Union.of(officialType, SkylarkType.NONE);
+ enforcedType = SkylarkType.Union.of(enforcedType, SkylarkType.NONE);
+ }
+ }
+ if (enforcedTypes != null) {
+ enforcedTypes.put(param.name(), enforcedType);
+ }
+ if (paramDoc != null) {
+ paramDoc.put(param.name(), param.doc());
+ }
+ if (starStar) {
+ return new Parameter.StarStar<>(param.name(), officialType);
+ } else if (star) {
+ return new Parameter.Star<>(param.name(), officialType);
+ } else if (mandatory) {
+ return new Parameter.Mandatory<>(param.name(), officialType);
+ } else {
+ return new Parameter.Optional<>(param.name(), officialType, defaultValue);
+ }
+ }
+
+ private static Object getDefaultValue(Param param, Iterator<Object> iterator) {
+ if (iterator != null) {
+ return iterator.next();
+ } else if (param.defaultValue().isEmpty()) {
+ return Environment.NONE;
+ } else {
+ try {
+ // TODO(bazel-team): when we have Evaluation, remove the throw and uncomment the return.
+ throw new RuntimeException("Not Implemented Yet!");
+ // return new Evaluation ().eval(param.defaultValue());
+ } catch (Exception e) {
+ throw new RuntimeException(String.format(
+ "Exception while processing @SkylarkSignature.Param %s, default value %s: %s",
+ param.name(), param.defaultValue(), e.getMessage()), e);
+ }
+ }
+ }
+
+ /** Extract additional signature information for BuiltinFunction-s */
+ public static ExtraArgKind[] getExtraArgs(SkylarkSignature annotation) {
+ final int numExtraArgs = (annotation.useLocation() ? 1 : 0)
+ + (annotation.useAst() ? 1 : 0) + (annotation.useEnvironment() ? 1 : 0);
+ if (numExtraArgs == 0) {
+ return null;
+ }
+ final ExtraArgKind[] extraArgs = new ExtraArgKind[numExtraArgs];
+ int i = 0;
+ if (annotation.useLocation()) {
+ extraArgs[i++] = ExtraArgKind.LOCATION;
+ }
+ if (annotation.useAst()) {
+ extraArgs[i++] = ExtraArgKind.SYNTAX_TREE;
+ }
+ if (annotation.useEnvironment()) {
+ extraArgs[i++] = ExtraArgKind.ENVIRONMENT;
+ }
+ return extraArgs;
+ }
+
+ /**
+ * Configure all BaseFunction-s in a class from their @SkylarkSignature annotations
+ * @param type a class containing BuiltinFunction fields that need be configured.
+ * This function is typically called in a static block to initialize a class,
+ * i.e. a class {@code Foo} containing @SkylarkSignature annotations would end with
+ * {@code static { SkylarkSignatureProcessor.configureSkylarkFunctions(Foo.class); }}
+ */
+ public static void configureSkylarkFunctions(Class<?> type) {
+ for (Field field : type.getDeclaredFields()) {
+ if (field.isAnnotationPresent(SkylarkSignature.class)) {
+ // The annotated fields are often private, but we need access them.
+ field.setAccessible(true);
+ SkylarkSignature annotation = field.getAnnotation(SkylarkSignature.class);
+ Object value = null;
+ try {
+ value = Preconditions.checkNotNull(field.get(null),
+ String.format(
+ "Error while trying to configure %s.%s: its value is null", type, field));
+ if (BaseFunction.class.isAssignableFrom(field.getType())) {
+ BaseFunction function = (BaseFunction) value;
+ if (!function.isConfigured()) {
+ function.configure(annotation);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(String.format(
+ "Error while trying to configure %s.%s (value %s)", type, field, value), e);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
index 58a5b4c..5bb9019 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java
@@ -20,7 +20,7 @@
* The actual function registered in the environment. This function is defined in the
* parsed code using {@link FunctionDefStatement}.
*/
-public class UserDefinedFunction extends MixedModeFunction {
+public class UserDefinedFunction extends BaseFunction {
private final ImmutableList<Statement> statements;
private final SkylarkEnvironment definitionEnv;