blob: 034ef3fbc2d2acef3a6c04c26f7b5a8b63dd6352 [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 com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
/**
* 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
abstract class FunctionSignature {
// These abstract getters specify the actual parameter count fields to be defined by AutoValue.
/** Number of mandatory positional parameters */
abstract int numMandatoryPositionals();
/** Number of optional positional parameters */
abstract int numOptionalPositionals();
/** Number of mandatory named-only parameters. */
abstract int numMandatoryNamedOnly();
/** Number of optional named-only parameters */
abstract int numOptionalNamedOnly();
/** True if function has variadic parameter, {@code def f(*args)}. */
abstract boolean hasVarargs();
/** True if function has residual keyword-argument parameter, {@code def f(**kwargs)}. */
abstract boolean hasKwargs();
/** Parameter names. */
abstract ImmutableList<String> getParameterNames();
// computed parameter counts
/** Number of optional and mandatory positional parameters. */
int numPositionals() {
return numMandatoryPositionals() + numOptionalPositionals();
}
/** Number of optional and mandatory named-only parameters. */
int numNamedOnly() {
return numMandatoryNamedOnly() + numOptionalNamedOnly();
}
/** number of optional parameters. */
int numOptionals() {
return numOptionalPositionals() + numOptionalNamedOnly();
}
private boolean hasStar() {
return hasVarargs() || (numNamedOnly() > 0);
}
/** total number of parameters */
int numParameters() {
return numPositionals() + numNamedOnly() + (hasStar() ? 1 : 0) + (hasKwargs() ? 1 : 0);
}
// 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
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);
return new AutoValue_FunctionSignature(
numMandatoryPositionals,
numOptionalPositionals,
numMandatoryNamedOnly,
numOptionalNamedOnly,
hasVarargs,
hasKwargs,
parameterNames);
}
@Override
public final String toString() {
StringBuilder printer = new StringBuilder();
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;
void comma() {
if (isMore) {
printer.append(", ");
}
isMore = true;
}
void mandatory(int i) {
comma();
printer.append(names.get(i));
}
void optional(int i) {
mandatory(i);
}
}
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.toString();
}
/**
* 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
*/
private 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));
}
/** A ready-made signature that accepts no arguments. */
static final FunctionSignature NOARGS = of(0, 0, 0, false, false);
}