blob: 702862fae604293cfbac9a816b0f52fa244499e6 [file] [log] [blame]
// 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 static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Interner;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.util.StringCanonicalizer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Function Signatures for BUILD language (same as Python)
*
* <p>Starlark's function signatures are just like Python3's. A function may have 6 kinds of
* parameters: positional mandatory, positional optional, positional rest (aka *args or variadic
* parameter), keyword-only mandatory, keyword-only optional, keyword rest (aka **kwargs parameter).
* A caller may specify all arguments but the *args and **kwargs arguments by name, and thus all
* mandatory and optional parameters are named parameters.
*
* <p>To enable various optimizations in the argument processing routine, we sort parameters
* according the following constraints, enabling corresponding optimizations:
*
* <ol>
* <li>The positional mandatories come just before the positional optionals, so they can be filled
* in one go.
* <li>Positionals come first, so it's easy to prepend extra positional arguments such as "self"
* to an argument list, and we optimize for the common case of no key-only mandatory
* parameters. key-only parameters are thus grouped together. positional mandatory and
* key-only mandatory parameters are separate, but there is no loop over a contiguous chunk of
* them, anyway.
* <li>The named are all grouped together, with star and star_star rest parameters coming last.
* <li>Mandatory parameters in each category (positional and named-only) come before the optional
* parameters, for the sake of slightly better clarity to human implementers. This eschews an
* optimization whereby grouping optionals together allows to iterate over them in one go
* instead of two; however, this relatively minor optimization only matters when keyword
* arguments are passed, at which point it is dwarfed by the slowness of keyword processing.
* </ol>
*
* <p>Parameters are thus sorted in the following order: positional mandatory parameters (if any),
* positional optional parameters (if any), key-only mandatory parameters (if any), key-only
* optional parameters (if any), then star parameter (if any), then star_star parameter (if any).
*/
@AutoCodec
@AutoValue
public abstract class FunctionSignature {
// These abstract getters specify the actual parameter count fields to be defined by AutoValue.
/** Number of mandatory positional parameters */
public abstract int numMandatoryPositionals();
/** Number of optional positional parameters */
public abstract int numOptionalPositionals();
/** Number of mandatory named-only parameters. */
public abstract int numMandatoryNamedOnly();
/** Number of optional named-only parameters */
public abstract int numOptionalNamedOnly();
/** True if function has variadic parameter, {@code def f(*args)}. */
public abstract boolean hasVarargs();
/** True if function has residual keyword-argument parameter, {@code def f(**kwargs)}. */
public abstract boolean hasKwargs();
/** Parameter names. */
public abstract ImmutableList<String> getParameterNames();
// computed parameter counts
/** Number of optional and mandatory positional parameters. */
public int numPositionals() {
return numMandatoryPositionals() + numOptionalPositionals();
}
/** Number of optional and mandatory named-only parameters. */
public int numNamedOnly() {
return numMandatoryNamedOnly() + numOptionalNamedOnly();
}
/** number of optional parameters. */
public int numOptionals() {
return numOptionalPositionals() + numOptionalNamedOnly();
}
private boolean hasStar() {
return hasVarargs() || (numNamedOnly() > 0);
}
/** total number of parameters */
public int numParameters() {
return numPositionals() + numNamedOnly() + (hasStar() ? 1 : 0) + (hasKwargs() ? 1 : 0);
}
private static final Interner<ImmutableList<String>> namesInterner =
BlazeInterners.newWeakInterner();
/** Intern a list of names. */
private static ImmutableList<String> names(List<String> names) {
return namesInterner.intern(
names.stream().map(StringCanonicalizer::intern).collect(toImmutableList()));
}
// Interner.
// Are there really a significant number of duplicates? Why??
private static final Interner<FunctionSignature> signatureInterner =
BlazeInterners.newWeakInterner();
// TODO(adonovan): not a user-friendly API. Provide external callers with this function:
// FunctionSignature.parse("a, b=1, *, c, d=2, *args, **kwargs")
// implemented by invoking the Starlark parser. (Most uses are in tests.)
@AutoCodec.Instantiator
public static FunctionSignature create(
int numMandatoryPositionals,
int numOptionalPositionals,
int numMandatoryNamedOnly,
int numOptionalNamedOnly,
boolean hasVarargs,
boolean hasKwargs,
ImmutableList<String> parameterNames) {
Preconditions.checkArgument(
0 <= numMandatoryPositionals
&& 0 <= numOptionalPositionals
&& 0 <= numMandatoryNamedOnly
&& 0 <= numOptionalNamedOnly);
FunctionSignature sig =
new AutoValue_FunctionSignature(
numMandatoryPositionals,
numOptionalPositionals,
numMandatoryNamedOnly,
numOptionalNamedOnly,
hasVarargs,
hasKwargs,
names(parameterNames));
return signatureInterner.intern(sig);
}
@Override
public final String toString() {
StringBuilder sb = new StringBuilder();
toStringBuilder(sb, null); // no default values
return sb.toString();
}
// ElementPrinter returns the string form of the ith element of a sequence.
interface ElementPrinter {
String print(int i);
}
/**
* Appends a representation of this signature to a string buffer.
*
* @param printer output StringBuilder
* @param defaultValuePrinter optional callback for formatting i'th default value (if any).
*/
StringBuilder toStringBuilder(
final StringBuilder printer, @Nullable final ElementPrinter defaultValuePrinter) {
final ImmutableList<String> names = getParameterNames();
int mandatoryPositionals = numMandatoryPositionals();
int optionalPositionals = numOptionalPositionals();
int mandatoryNamedOnly = numMandatoryNamedOnly();
int optionalNamedOnly = numOptionalNamedOnly();
boolean hasVarargs = hasVarargs();
boolean hasKwargs = hasKwargs();
int positionals = mandatoryPositionals + optionalPositionals;
int namedOnly = mandatoryNamedOnly + optionalNamedOnly;
int named = positionals + namedOnly;
int args = named + (hasVarargs ? 1 : 0) + (hasKwargs ? 1 : 0);
int endMandatoryNamedOnly = positionals + mandatoryNamedOnly;
int iStarArg = named;
int iKwArg = args - 1;
class Show {
private boolean isMore = false;
private int j = 0;
public void comma() {
if (isMore) {
printer.append(", ");
}
isMore = true;
}
public void mandatory(int i) {
comma();
printer.append(names.get(i));
}
public void optional(int i) {
mandatory(i);
if (defaultValuePrinter != null) {
String str = defaultValuePrinter.print(j++);
printer.append(" = ").append(str != null ? str : "?");
}
}
}
Show show = new Show();
int i = 0;
for (; i < mandatoryPositionals; i++) {
show.mandatory(i);
}
for (; i < positionals; i++) {
show.optional(i);
}
if (hasStar()) {
show.comma();
printer.append("*");
if (hasVarargs) {
printer.append(names.get(iStarArg));
}
}
for (; i < endMandatoryNamedOnly; i++) {
show.mandatory(i);
}
for (; i < named; i++) {
show.optional(i);
}
if (hasKwargs) {
show.comma();
printer.append("**");
printer.append(names.get(iKwArg));
}
return printer;
}
/** Convert a list of Parameter into a FunctionSignature. */
static FunctionSignature fromParameters(Iterable<Parameter> parameters)
throws SignatureException {
int mandatoryPositionals = 0;
int optionalPositionals = 0;
int mandatoryNamedOnly = 0;
int optionalNamedOnly = 0;
boolean hasStarStar = false;
boolean hasStar = false;
@Nullable String star = null;
@Nullable String starStar = null;
ArrayList<String> params = new ArrayList<>();
// optional named-only parameters are kept aside to be spliced after the mandatory ones.
ArrayList<String> optionalNamedOnlyParams = new ArrayList<>();
boolean defaultRequired = false; // true after mandatory positionals and before star.
Set<String> paramNameSet = new HashSet<>(); // set of names, to avoid duplicates
for (Parameter param : parameters) {
if (hasStarStar) {
throw new SignatureException("illegal parameter after star-star parameter", param);
}
@Nullable String name = param.getName();
if (param.getName() != null) {
if (paramNameSet.contains(name)) {
throw new SignatureException("duplicate parameter name in function definition", param);
}
paramNameSet.add(name);
}
if (param instanceof Parameter.StarStar) {
hasStarStar = true;
starStar = name;
} else if (param instanceof Parameter.Star) {
if (hasStar) {
throw new SignatureException("duplicate star parameter in function definition", param);
}
hasStar = true;
defaultRequired = false;
if (param.getName() != null) {
star = name;
}
} else if (hasStar && param instanceof Parameter.Optional) {
optionalNamedOnly++;
optionalNamedOnlyParams.add(name);
} else {
params.add(name);
if (param instanceof Parameter.Optional) {
optionalPositionals++;
defaultRequired = true;
} else if (hasStar) {
mandatoryNamedOnly++;
} else if (defaultRequired) {
throw new SignatureException(
"a mandatory positional parameter must not follow an optional parameter", param);
} else {
mandatoryPositionals++;
}
}
}
params.addAll(optionalNamedOnlyParams);
if (star != null) {
params.add(star);
}
if (starStar != null) {
params.add(starStar);
}
return FunctionSignature.create(
mandatoryPositionals,
optionalPositionals,
mandatoryNamedOnly,
optionalNamedOnly,
star != null,
starStar != null,
ImmutableList.copyOf(params));
}
/**
* Constructs a function signature (with names) from signature description and names. This method
* covers the general case. The number of optional named-only parameters is deduced from the other
* arguments.
*
* @param numMandatoryPositionals an int for the number of mandatory positional parameters
* @param numOptionalPositionals an int for the number of optional positional parameters
* @param numMandatoryNamedOnly an int for the number of mandatory named-only parameters
* @param hasVarargs whether function is variadic parameter
* @param hasKwargs whether function accepts arbitrary named arguments
* @param names an Array of String for the parameter names
*/
static FunctionSignature of(
int numMandatoryPositionals,
int numOptionalPositionals,
int numMandatoryNamedOnly,
boolean hasVarargs,
boolean hasKwargs,
String... names) {
return create(
numMandatoryPositionals,
numOptionalPositionals,
numMandatoryNamedOnly,
names.length
- (hasKwargs ? 1 : 0)
- (hasVarargs ? 1 : 0)
- numMandatoryPositionals
- numOptionalPositionals
- numMandatoryNamedOnly,
hasVarargs,
hasKwargs,
ImmutableList.copyOf(names));
}
/**
* Constructs a function signature from mandatory positional argument names.
*
* @param names an Array of String for the positional parameter names
* @return a FunctionSignature
*/
public static FunctionSignature of(String... names) {
return of(names.length, 0, 0, false, false, names);
}
/**
* Constructs a function signature from positional argument names.
*
* @param numMandatory an int for the number of mandatory positional parameters
* @param names an Array of String for the positional parameter names
* @return a FunctionSignature
*/
public static FunctionSignature of(int numMandatory, String... names) {
return of(numMandatory, names.length - numMandatory, 0, false, false, names);
}
/**
* Constructs a function signature from named-only parameter names.
*
* @param numMandatory an int for the number of mandatory named-only parameters
* @param names an Array of String for the named-only parameter names
* @return a FunctionSignature
*/
public static FunctionSignature namedOnly(int numMandatory, String... names) {
return of(0, 0, numMandatory, false, false, names);
}
/** Invalid signature from Parser or from SkylarkCallable annotation. */
static class SignatureException extends Exception {
private final Parameter parameter;
/** SignatureException from a message and a Parameter */
SignatureException(String message, Parameter parameter) {
super(message);
this.parameter = parameter;
}
/** Returns the parameter that caused the exception. */
Parameter getParameter() {
return parameter;
}
}
/** A ready-made signature to allow only keyword parameters and put them in a kwarg parameter */
public static final FunctionSignature KWARGS = of(0, 0, 0, false, true, "kwargs");
/** A ready-made signature that accepts no arguments. */
public static final FunctionSignature NOARGS = of(0, 0, 0, false, false);
/** A ready-made signature that allows any arguments. */
public static final FunctionSignature ANY = of(0, 0, 0, true, true, "args", "kwargs");
}