| // Copyright 2014 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.Joiner; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.Ordering; | |
| import com.google.common.collect.Sets; | |
| import java.util.Arrays; | |
| import java.util.Set; | |
| import javax.annotation.Nullable; | |
| /** | |
| * BaseFunction is an abstract base class to simplify the task of defining a subclass of | |
| * StarlarkCallable. In general it should not appear as the type of a parameter or result in an API; | |
| * prefer StarlarkCallable, which abstracts all function-like Starlark values. | |
| * | |
| * <p>Subclasses of BaseFunction provide a {@code FunctionSignature} and an optional tuple of | |
| * default values for optional parameters, and BaseFunction implements the functionality of {@link | |
| * #toString}, {@link #repr}, and {@link #isImmutable}. The signature may also be used by | |
| * documentation tools. | |
| */ | |
| public abstract class BaseFunction implements StarlarkCallable { | |
| /** | |
| * Returns the signature of this function. This does not affect argument validation; it is only | |
| * for documentation and error messages. However, subclasses may choose to pass it to {@link | |
| * Starlark#matchSignature}. | |
| */ | |
| public abstract FunctionSignature getSignature(); | |
| /** | |
| * Returns the optional tuple of default values for optional parameters. For example, the defaults | |
| * for {@code def f(a, b=1, *, c, d=2)} would be {@code (1, 2)}. May return null if the function | |
| * has no optional parameters. | |
| * | |
| * <p>As with {@code getSignature}, this method does not affect argument validation; it is only | |
| * for documentation and error messages. However, subclasses may choose to pass it to {@link | |
| * Starlark#matchSignature}. | |
| */ | |
| @Nullable | |
| public Tuple<Object> getDefaultValues() { | |
| return null; | |
| } | |
| // TODO(adonovan): This has nothing to do with BaseFunction. | |
| // Move it to Starlark.matchSignature (in a follow-up, for ease of review). | |
| static Object[] matchSignature( | |
| FunctionSignature signature, | |
| StarlarkCallable func, // only for use in error messages | |
| Tuple<Object> defaults, | |
| @Nullable Mutability mu, | |
| Object[] positional, | |
| Object[] named) | |
| throws EvalException { | |
| // TODO(adonovan): simplify this function. Combine cases 1 and 2 without loss of efficiency. | |
| // TODO(adonovan): reduce the verbosity of errors. Printing func.toString is often excessive. | |
| // Make the error messages consistent in form. | |
| // TODO(adonovan): report an error if there were missing values in 'defaults'. | |
| Object[] arguments = new Object[signature.numParameters()]; | |
| ImmutableList<String> names = signature.getParameterNames(); | |
| // Note that this variable will be adjusted down if there are extra positionals, | |
| // after these extra positionals are dumped into starParam. | |
| int numPositionalArgs = positional.length; | |
| int numMandatoryPositionalParams = signature.numMandatoryPositionals(); | |
| int numOptionalPositionalParams = signature.numOptionalPositionals(); | |
| int numMandatoryNamedOnlyParams = signature.numMandatoryNamedOnly(); | |
| int numOptionalNamedOnlyParams = signature.numOptionalNamedOnly(); | |
| boolean hasVarargs = signature.hasVarargs(); | |
| boolean hasKwargs = signature.hasKwargs(); | |
| int numPositionalParams = numMandatoryPositionalParams + numOptionalPositionalParams; | |
| int numNamedOnlyParams = numMandatoryNamedOnlyParams + numOptionalNamedOnlyParams; | |
| int numNamedParams = numPositionalParams + numNamedOnlyParams; | |
| int kwargIndex = names.size() - 1; // only valid if hasKwargs | |
| // (1) handle positional arguments | |
| if (hasVarargs) { | |
| // Nota Bene: we collect extra positional arguments in a (tuple,) rather than a [list], | |
| // and this is actually the same as in Python. | |
| Object varargs; | |
| if (numPositionalArgs > numPositionalParams) { | |
| varargs = | |
| Tuple.wrap(Arrays.copyOfRange(positional, numPositionalParams, numPositionalArgs)); | |
| numPositionalArgs = numPositionalParams; // clip numPositionalArgs | |
| } else { | |
| varargs = Tuple.empty(); | |
| } | |
| arguments[numNamedParams] = varargs; | |
| } else if (numPositionalArgs > numPositionalParams) { | |
| throw new EvalException( | |
| null, | |
| numPositionalParams > 0 | |
| ? "too many (" + numPositionalArgs + ") positional arguments in call to " + func | |
| : func + " does not accept positional arguments, but got " + numPositionalArgs); | |
| } | |
| for (int i = 0; i < numPositionalArgs; i++) { | |
| arguments[i] = positional[i]; | |
| } | |
| // (2) handle keyword arguments | |
| if (named.length == 0) { | |
| // 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 Starlark.errorf( | |
| "insufficient arguments received by %s (got %s, expected at least %s)", | |
| func, numPositionalArgs, numMandatoryPositionalParams); | |
| } | |
| // We had no named argument, so fail if there were mandatory named-only parameters | |
| if (numMandatoryNamedOnlyParams > 0) { | |
| throw Starlark.errorf("missing mandatory keyword arguments in call to %s", func); | |
| } | |
| // Fill in defaults for missing optional parameters, that were conveniently grouped together, | |
| // thanks to the absence of mandatory named-only parameters as checked above. | |
| if (defaults != null) { | |
| int endOptionalParams = numPositionalParams + numOptionalNamedOnlyParams; | |
| for (int i = numPositionalArgs; i < endOptionalParams; i++) { | |
| arguments[i] = defaults.get(i - numMandatoryPositionalParams); | |
| } | |
| } | |
| // If there's a kwarg, it's empty. | |
| if (hasKwargs) { | |
| arguments[kwargIndex] = Dict.of(mu); | |
| } | |
| } else { | |
| // general case (2b): some keyword arguments may correspond to named parameters | |
| Dict<String, Object> kwargs = hasKwargs ? Dict.of(mu) : null; | |
| // Accept the named arguments that were passed. | |
| for (int i = 0; i < named.length; i += 2) { | |
| String keyword = (String) named[i]; // safe | |
| Object value = named[i + 1]; | |
| int pos = names.indexOf(keyword); // the list should be short, so linear scan is OK. | |
| if (0 <= pos && pos < numNamedParams) { | |
| if (arguments[pos] != null) { | |
| throw Starlark.errorf("%s got multiple values for parameter '%s'", func, keyword); | |
| } | |
| arguments[pos] = value; | |
| } else { | |
| if (!hasKwargs) { | |
| Set<String> unexpected = Sets.newHashSet(); | |
| for (int j = 0; j < named.length; j += 2) { | |
| unexpected.add((String) named[j]); | |
| } | |
| unexpected.removeAll(names.subList(0, numNamedParams)); | |
| // TODO(adonovan): do spelling check. | |
| throw Starlark.errorf( | |
| "unexpected keyword%s '%s' in call to %s", | |
| unexpected.size() > 1 ? "s" : "", | |
| Joiner.on("', '").join(Ordering.natural().sortedCopy(unexpected)), | |
| func); | |
| } | |
| int sz = kwargs.size(); | |
| kwargs.put(keyword, value, null); | |
| if (kwargs.size() == sz) { | |
| throw Starlark.errorf( | |
| "%s got multiple values for keyword argument '%s'", func, keyword); | |
| } | |
| } | |
| } | |
| if (hasKwargs) { | |
| arguments[kwargIndex] = kwargs; | |
| } | |
| // Check that all mandatory parameters were filled in general case 2b. | |
| // Note: it's possible that numPositionalArgs > numMandatoryPositionalParams but that's OK. | |
| for (int i = numPositionalArgs; i < numMandatoryPositionalParams; i++) { | |
| if (arguments[i] == null) { | |
| throw Starlark.errorf( | |
| "missing mandatory positional argument '%s' while calling %s", names.get(i), func); | |
| } | |
| } | |
| int endMandatoryNamedOnlyParams = numPositionalParams + numMandatoryNamedOnlyParams; | |
| for (int i = numPositionalParams; i < endMandatoryNamedOnlyParams; i++) { | |
| if (arguments[i] == null) { | |
| throw Starlark.errorf( | |
| "missing mandatory named-only argument '%s' while calling %s", names.get(i), func); | |
| } | |
| } | |
| // Get defaults for those parameters that weren't passed. | |
| if (defaults != null) { | |
| for (int i = Math.max(numPositionalArgs, numMandatoryPositionalParams); | |
| i < numPositionalParams; i++) { | |
| if (arguments[i] == null) { | |
| arguments[i] = defaults.get(i - numMandatoryPositionalParams); | |
| } | |
| } | |
| int numMandatoryParams = numMandatoryPositionalParams + numMandatoryNamedOnlyParams; | |
| for (int i = numMandatoryParams + numOptionalPositionalParams; i < numNamedParams; i++) { | |
| if (arguments[i] == null) { | |
| arguments[i] = defaults.get(i - numMandatoryParams); | |
| } | |
| } | |
| } | |
| } // End of general case 2b for argument passing. | |
| return arguments; | |
| } | |
| /** | |
| * Render this object in the form of an equivalent Python function signature. | |
| */ | |
| @Override | |
| public String toString() { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.append(getName()); | |
| sb.append('('); | |
| getSignature().toStringBuilder(sb, this::printDefaultValue); | |
| sb.append(')'); | |
| return sb.toString(); | |
| } | |
| private String printDefaultValue(int i) { | |
| Tuple<Object> defaultValues = getDefaultValues(); | |
| Object v = defaultValues != null ? defaultValues.get(i) : null; | |
| return v != null ? Starlark.repr(v) : null; | |
| } | |
| @Override | |
| public boolean isImmutable() { | |
| return true; | |
| } | |
| @Override | |
| public void repr(Printer printer) { | |
| printer.append("<function " + getName() + ">"); | |
| } | |
| } |