(Rollforward) Convert MethodLibrary's SkylarkSignature methods to SkylarkCallable
Original description:
Note that converted parameter @Param annotations need legacyNamed = true (and in some places, noneable = True) to retain compatibility with the bugginess of SkylarkSignature.
This facilitates followup to clean up these methods / parameters -- given the refactor, we can enforce some of these parameters are positional-only (subject to an --incompatible flag)
Progress toward #5010
RELNOTES: None.
PiperOrigin-RevId: 243891931
diff --git a/src/main/java/com/google/devtools/build/docgen/ApiExporter.java b/src/main/java/com/google/devtools/build/docgen/ApiExporter.java
index 8e1dad6..b04952f 100644
--- a/src/main/java/com/google/devtools/build/docgen/ApiExporter.java
+++ b/src/main/java/com/google/devtools/build/docgen/ApiExporter.java
@@ -27,6 +27,7 @@
import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.syntax.BaseFunction;
+import com.google.devtools.build.lib.syntax.BuiltinCallable;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.FunctionSignature;
import com.google.devtools.build.lib.syntax.MethodDescriptor;
@@ -93,6 +94,15 @@
Value.Builder value = Value.newBuilder();
if (obj instanceof BaseFunction) {
value = collectFunctionInfo((BaseFunction) obj);
+ } else if (obj instanceof BuiltinCallable) {
+ BuiltinCallable builtinCallable = (BuiltinCallable) obj;
+ MethodDescriptor descriptor =
+ builtinCallable.getMethodDescriptor(StarlarkSemantics.DEFAULT_SEMANTICS);
+ value =
+ collectFunctionInfo(
+ descriptor.getName(),
+ SkylarkSignatureProcessor.getSignatureForCallable(
+ descriptor.getName(), descriptor, null, null));
} else {
value.setName(entry.getKey());
}
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
index 1b8a6ed..53863be 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java
@@ -42,8 +42,7 @@
FuncallExpression ast,
Environment env)
throws EvalException, InterruptedException {
- MethodDescriptor methodDescriptor =
- FuncallExpression.getMethod(env.getSemantics(), obj.getClass(), methodName);
+ MethodDescriptor methodDescriptor = getMethodDescriptor(env.getSemantics());
// TODO(cparsons): Profiling should be done at the MethodDescriptor level.
try (SilentCloseable c =
@@ -55,6 +54,10 @@
}
}
+ public MethodDescriptor getMethodDescriptor(StarlarkSemantics semantics) {
+ return FuncallExpression.getMethod(semantics, obj.getClass(), methodName);
+ }
+
@Override
public void repr(SkylarkPrinter printer) {
printer.append("<built-in function " + methodName + ">");
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 3785595..cc2ae1f 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
@@ -660,7 +660,7 @@
String.format(
"unexpected keyword%s %s",
unexpectedKeywords.size() > 1 ? "s" : "",
- Joiner.on(",").join(Iterables.transform(unexpectedKeywords, s -> "'" + s + "'"))),
+ Joiner.on(", ").join(Iterables.transform(unexpectedKeywords, s -> "'" + s + "'"))),
method,
objClass);
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
index 9c5787f..b9ff20d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
@@ -27,9 +27,10 @@
import com.google.devtools.build.lib.events.Event;
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.SkylarkGlobalLibrary;
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.EvalUtils.ComparisonException;
import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
@@ -44,13 +45,11 @@
import javax.annotation.Nullable;
/** A helper class containing built in functions for the Skylark language. */
+@SkylarkGlobalLibrary
public class MethodLibrary {
- private MethodLibrary() {}
-
- @SkylarkSignature(
+ @SkylarkCallable(
name = "min",
- returnType = Object.class,
doc =
"Returns the smallest one of all given arguments. "
+ "If only one argument is provided, it must be a non-empty iterable. "
@@ -61,22 +60,16 @@
@Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked."),
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction min =
- new BuiltinFunction("min") {
- @SuppressWarnings("unused") // Accessed via Reflection.
- public Object invoke(SkylarkList<?> args, Location loc, Environment env)
- throws EvalException {
- try {
- return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR.reverse(), loc, env);
- } catch (ComparisonException e) {
- throw new EvalException(loc, e);
- }
- }
- };
+ public Object min(SkylarkList<?> args, Location loc, Environment env) throws EvalException {
+ try {
+ return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR.reverse(), loc, env);
+ } catch (ComparisonException e) {
+ throw new EvalException(loc, e);
+ }
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "max",
- returnType = Object.class,
doc =
"Returns the largest one of all given arguments. "
+ "If only one argument is provided, it must be a non-empty iterable."
@@ -87,18 +80,13 @@
@Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked."),
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction max =
- new BuiltinFunction("max") {
- @SuppressWarnings("unused") // Accessed via Reflection.
- public Object invoke(SkylarkList<?> args, Location loc, Environment env)
- throws EvalException {
- try {
- return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR, loc, env);
- } catch (ComparisonException e) {
- throw new EvalException(loc, e);
- }
- }
- };
+ public Object max(SkylarkList<?> args, Location loc, Environment env) throws EvalException {
+ try {
+ return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR, loc, env);
+ } catch (ComparisonException e) {
+ throw new EvalException(loc, e);
+ }
+ }
/** Returns the maximum element from this list, as determined by maxOrdering. */
private static Object findExtreme(
@@ -114,9 +102,8 @@
}
}
- @SkylarkSignature(
+ @SkylarkCallable(
name = "all",
- returnType = Boolean.class,
doc =
"Returns true if all elements evaluate to True or if the collection is empty. "
+ "Elements are converted to boolean using the <a href=\"#bool\">bool</a> function."
@@ -126,22 +113,18 @@
@Param(
name = "elements",
type = Object.class,
- doc = "A string or a collection of elements.")
+ noneable = true,
+ doc = "A string or a collection of elements.",
+ legacyNamed = true)
},
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction all =
- new BuiltinFunction("all") {
- @SuppressWarnings("unused") // Accessed via Reflection.
- public Boolean invoke(Object collection, Location loc, Environment env)
- throws EvalException {
- return !hasElementWithBooleanValue(collection, false, loc, env);
- }
- };
+ public Boolean all(Object collection, Location loc, Environment env) throws EvalException {
+ return !hasElementWithBooleanValue(collection, false, loc, env);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "any",
- returnType = Boolean.class,
doc =
"Returns true if at least one element evaluates to True. "
+ "Elements are converted to boolean using the <a href=\"#bool\">bool</a> function."
@@ -151,18 +134,15 @@
@Param(
name = "elements",
type = Object.class,
- doc = "A string or a collection of elements.")
+ noneable = true,
+ doc = "A string or a collection of elements.",
+ legacyNamed = true)
},
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction any =
- new BuiltinFunction("any") {
- @SuppressWarnings("unused") // Accessed via Reflection.
- public Boolean invoke(Object collection, Location loc, Environment env)
- throws EvalException {
- return hasElementWithBooleanValue(collection, true, loc, env);
- }
- };
+ public Boolean any(Object collection, Location loc, Environment env) throws EvalException {
+ return hasElementWithBooleanValue(collection, true, loc, env);
+ }
private static boolean hasElementWithBooleanValue(
Object collection, boolean value, Location loc, Environment env) throws EvalException {
@@ -175,35 +155,29 @@
return false;
}
- // supported list methods
- @SkylarkSignature(
+ @SkylarkCallable(
name = "sorted",
- returnType = MutableList.class,
doc =
"Sort a collection. Elements should all belong to the same orderable type, they are "
+ "sorted by their value (in ascending order). "
+ "It is an error if elements are not comparable (for example int with string)."
+ "<pre class=\"language-python\">sorted([3, 5, 4]) == [3, 4, 5]</pre>",
- parameters = {@Param(name = "self", type = Object.class, doc = "This collection.")},
+ parameters = {
+ @Param(name = "self", type = Object.class, doc = "This collection.", legacyNamed = true)
+ },
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction sorted =
- new BuiltinFunction("sorted") {
- public MutableList<?> invoke(Object self, Location loc, Environment env)
- throws EvalException {
- try {
- return MutableList.copyOf(
- env,
- EvalUtils.SKYLARK_COMPARATOR.sortedCopy(EvalUtils.toCollection(self, loc, env)));
- } catch (EvalUtils.ComparisonException e) {
- throw new EvalException(loc, e);
- }
- }
- };
+ public MutableList<?> sorted(Object self, Location loc, Environment env) throws EvalException {
+ try {
+ return MutableList.copyOf(
+ env, EvalUtils.SKYLARK_COMPARATOR.sortedCopy(EvalUtils.toCollection(self, loc, env)));
+ } catch (EvalUtils.ComparisonException e) {
+ throw new EvalException(loc, e);
+ }
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "reversed",
- returnType = MutableList.class,
doc =
"Returns a list that contains the elements of the original sequence in reversed order."
+ "<pre class=\"language-python\">reversed([3, 5, 4]) == [4, 5, 3]</pre>",
@@ -211,151 +185,130 @@
@Param(
name = "sequence",
type = Object.class,
- doc = "The sequence to be reversed (string, list or tuple).")
+ doc = "The sequence to be reversed (string, list or tuple).",
+ legacyNamed = true),
},
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction reversed =
- new BuiltinFunction("reversed") {
- @SuppressWarnings("unused") // Accessed via Reflection.
- public MutableList<?> invoke(Object sequence, Location loc, Environment env)
- throws EvalException {
- // We only allow lists and strings.
- if (sequence instanceof SkylarkDict) {
- throw new EvalException(
- loc, "Argument to reversed() must be a sequence, not a dictionary.");
- } else if (sequence instanceof NestedSet || sequence instanceof SkylarkNestedSet) {
- throw new EvalException(
- loc, "Argument to reversed() must be a sequence, not a depset.");
- }
- ArrayDeque<Object> tmpList = new ArrayDeque<>();
- for (Object element : EvalUtils.toIterable(sequence, loc, env)) {
- tmpList.addFirst(element);
- }
- return MutableList.copyOf(env, tmpList);
- }
- };
+ public MutableList<?> reversed(Object sequence, Location loc, Environment env)
+ throws EvalException {
+ // We only allow lists and strings.
+ if (sequence instanceof SkylarkDict) {
+ throw new EvalException(loc, "Argument to reversed() must be a sequence, not a dictionary.");
+ } else if (sequence instanceof NestedSet || sequence instanceof SkylarkNestedSet) {
+ throw new EvalException(loc, "Argument to reversed() must be a sequence, not a depset.");
+ }
+ ArrayDeque<Object> tmpList = new ArrayDeque<>();
+ for (Object element : EvalUtils.toIterable(sequence, loc, env)) {
+ tmpList.addFirst(element);
+ }
+ return MutableList.copyOf(env, tmpList);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "tuple",
- returnType = Tuple.class,
doc =
"Converts a collection (e.g. list, tuple or dictionary) to a tuple."
+ "<pre class=\"language-python\">tuple([1, 2]) == (1, 2)\n"
+ "tuple((2, 3, 2)) == (2, 3, 2)\n"
+ "tuple({5: \"a\", 2: \"b\", 4: \"c\"}) == (5, 2, 4)</pre>",
- parameters = {@Param(name = "x", doc = "The object to convert.")},
+ parameters = {@Param(name = "x", doc = "The object to convert.", legacyNamed = true)},
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction tuple =
- new BuiltinFunction("tuple") {
- public Tuple<?> invoke(Object x, Location loc, Environment env) throws EvalException {
- return Tuple.copyOf(EvalUtils.toCollection(x, loc, env));
- }
- };
+ public Tuple<?> tuple(Object x, Location loc, Environment env) throws EvalException {
+ return Tuple.copyOf(EvalUtils.toCollection(x, loc, env));
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "list",
- returnType = MutableList.class,
doc =
"Converts a collection (e.g. list, tuple or dictionary) to a list."
+ "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n"
+ "list((2, 3, 2)) == [2, 3, 2]\n"
+ "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [5, 2, 4]</pre>",
- parameters = {@Param(name = "x", doc = "The object to convert.")},
+ parameters = {@Param(name = "x", doc = "The object to convert.", legacyNamed = true)},
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction list =
- new BuiltinFunction("list") {
- public MutableList<?> invoke(Object x, Location loc, Environment env) throws EvalException {
- return MutableList.copyOf(env, EvalUtils.toCollection(x, loc, env));
- }
- };
+ public MutableList<?> list(Object x, Location loc, Environment env) throws EvalException {
+ return MutableList.copyOf(env, EvalUtils.toCollection(x, loc, env));
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "len",
- returnType = Integer.class,
doc = "Returns the length of a string, list, tuple, depset, or dictionary.",
- parameters = {@Param(name = "x", doc = "The object to check length of.")},
+ parameters = {@Param(name = "x", doc = "The object to check length of.", legacyNamed = true)},
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction len =
- new BuiltinFunction("len") {
- public Integer invoke(Object x, Location loc, Environment env) throws EvalException {
- if (x instanceof String) {
- return ((String) x).length();
- } else if (x instanceof Map) {
- return ((Map<?, ?>) x).size();
- } else if (x instanceof SkylarkList) {
- return ((SkylarkList<?>) x).size();
- } else if (x instanceof SkylarkNestedSet) {
- if (env.getSemantics().incompatibleDepsetIsNotIterable()) {
- throw new EvalException(
- loc,
- EvalUtils.getDataTypeName(x)
- + " is not iterable. You may use `len(<depset>.to_list())` instead. Use "
- + "--incompatible_depset_is_not_iterable=false to temporarily disable this "
- + "check.");
- }
- return ((SkylarkNestedSet) x).toCollection().size();
- } else if (x instanceof Iterable) {
- // Iterables.size() checks if x is a Collection so it's efficient in that sense.
- return Iterables.size((Iterable<?>) x);
- } else {
- throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable");
- }
- }
- };
+ public Integer len(Object x, Location loc, Environment env) throws EvalException {
+ if (x instanceof String) {
+ return ((String) x).length();
+ } else if (x instanceof Map) {
+ return ((Map<?, ?>) x).size();
+ } else if (x instanceof SkylarkList) {
+ return ((SkylarkList<?>) x).size();
+ } else if (x instanceof SkylarkNestedSet) {
+ if (env.getSemantics().incompatibleDepsetIsNotIterable()) {
+ throw new EvalException(
+ loc,
+ EvalUtils.getDataTypeName(x)
+ + " is not iterable. You may use `len(<depset>.to_list())` instead. Use "
+ + "--incompatible_depset_is_not_iterable=false to temporarily disable this "
+ + "check.");
+ }
+ return ((SkylarkNestedSet) x).toCollection().size();
+ } else if (x instanceof Iterable) {
+ // Iterables.size() checks if x is a Collection so it's efficient in that sense.
+ return Iterables.size((Iterable<?>) x);
+ } else {
+ throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable");
+ }
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "str",
- returnType = String.class,
doc =
"Converts any object to string. This is useful for debugging."
+ "<pre class=\"language-python\">str(\"ab\") == \"ab\"\n"
+ "str(8) == \"8\"</pre>",
- parameters = {@Param(name = "x", doc = "The object to convert.")})
- private static final BuiltinFunction str =
- new BuiltinFunction("str") {
- public String invoke(Object x) {
- return Printer.str(x);
- }
- };
+ parameters = {
+ @Param(name = "x", doc = "The object to convert.", legacyNamed = true, noneable = true)
+ })
+ public String str(Object x) {
+ return Printer.str(x);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "repr",
- returnType = String.class,
doc =
"Converts any object to a string representation. This is useful for debugging.<br>"
+ "<pre class=\"language-python\">repr(\"ab\") == '\"ab\"'</pre>",
- parameters = {@Param(name = "x", doc = "The object to convert.")})
- private static final BuiltinFunction repr =
- new BuiltinFunction("repr") {
- public String invoke(Object x) {
- return Printer.repr(x);
- }
- };
+ parameters = {
+ @Param(name = "x", doc = "The object to convert.", legacyNamed = true, noneable = true)
+ })
+ public String repr(Object x) {
+ return Printer.repr(x);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "bool",
- returnType = Boolean.class,
doc =
"Constructor for the bool type. "
+ "It returns <code>False</code> if the object is <code>None</code>, <code>False"
+ "</code>, an empty string (<code>\"\"</code>), the number <code>0</code>, or an "
+ "empty collection (e.g. <code>()</code>, <code>[]</code>). "
+ "Otherwise, it returns <code>True</code>.",
- parameters = {@Param(name = "x", doc = "The variable to convert.")})
- private static final BuiltinFunction bool =
- new BuiltinFunction("bool") {
- public Boolean invoke(Object x) throws EvalException {
- return EvalUtils.toBoolean(x);
- }
- };
+ parameters = {
+ @Param(name = "x", doc = "The variable to convert.", legacyNamed = true, noneable = true)
+ })
+ public Boolean bool(Object x) throws EvalException {
+ return EvalUtils.toBoolean(x);
+ }
- @SkylarkSignature(
+ private final ImmutableMap<String, Integer> intPrefixes =
+ ImmutableMap.of("0b", 2, "0o", 8, "0x", 16);
+
+ @SkylarkCallable(
name = "int",
- returnType = Integer.class,
doc =
"Returns x as an int value."
+ "<ul>"
@@ -394,7 +347,7 @@
+ "int(\"-0x10\", 0) == -16"
+ "</pre>",
parameters = {
- @Param(name = "x", type = Object.class, doc = "The string to convert."),
+ @Param(name = "x", type = Object.class, doc = "The string to convert.", legacyNamed = true),
@Param(
name = "base",
type = Object.class,
@@ -403,117 +356,106 @@
"The base used to interpret a string value; defaults to 10. Must be between 2 "
+ "and 36 (inclusive), or 0 to detect the base as if <code>x</code> were an "
+ "integer literal. This parameter must not be supplied if the value is not a "
- + "string.")
+ + "string.",
+ legacyNamed = true)
},
useLocation = true)
- private static final BuiltinFunction int_ =
- new BuiltinFunction("int") {
- private final ImmutableMap<String, Integer> intPrefixes =
- ImmutableMap.of("0b", 2, "0o", 8, "0x", 16);
+ public Integer convertToInt(Object x, Object base, Location loc) throws EvalException {
+ if (x instanceof String) {
+ if (base == Runtime.UNBOUND) {
+ base = 10;
+ } else if (!(base instanceof Integer)) {
+ throw new EvalException(
+ loc, "base must be an integer (got '" + EvalUtils.getDataTypeName(base) + "')");
+ }
+ return fromString((String) x, loc, (Integer) base);
+ } else {
+ if (base != Runtime.UNBOUND) {
+ throw new EvalException(loc, "int() can't convert non-string with explicit base");
+ }
+ if (x instanceof Boolean) {
+ return ((Boolean) x).booleanValue() ? 1 : 0;
+ } else if (x instanceof Integer) {
+ return (Integer) x;
+ }
+ throw new EvalException(loc, Printer.format("%r is not of type string or int or bool", x));
+ }
+ }
- @SuppressWarnings("unused")
- public Integer invoke(Object x, Object base, Location loc) throws EvalException {
- if (x instanceof String) {
- if (base == Runtime.UNBOUND) {
- base = 10;
- } else if (!(base instanceof Integer)) {
- throw new EvalException(
- loc, "base must be an integer (got '" + EvalUtils.getDataTypeName(base) + "')");
- }
- return fromString((String) x, loc, (Integer) base);
- } else {
- if (base != Runtime.UNBOUND) {
- throw new EvalException(loc, "int() can't convert non-string with explicit base");
- }
- if (x instanceof Boolean) {
- return ((Boolean) x).booleanValue() ? 1 : 0;
- } else if (x instanceof Integer) {
- return (Integer) x;
- }
- throw new EvalException(
- loc, Printer.format("%r is not of type string or int or bool", x));
- }
+ private int fromString(String string, Location loc, int base) throws EvalException {
+ String stringForErrors = string;
+
+ boolean isNegative = false;
+ if (string.isEmpty()) {
+ throw new EvalException(loc, Printer.format("string argument to int() cannot be empty"));
+ }
+ char c = string.charAt(0);
+ if (c == '+') {
+ string = string.substring(1);
+ } else if (c == '-') {
+ string = string.substring(1);
+ isNegative = true;
+ }
+
+ String prefix = getIntegerPrefix(string);
+ String digits;
+ if (prefix == null) {
+ // Nothing to strip. Infer base 10 if autodetection was requested (base == 0).
+ digits = string;
+ if (base == 0) {
+ if (string.length() > 1 && string.startsWith("0")) {
+ // We don't infer the base when input starts with '0' (due
+ // to confusion between octal and decimal).
+ throw new EvalException(
+ loc,
+ Printer.format(
+ "cannot infer base for int() when value begins with a 0: %r", stringForErrors));
}
+ base = 10;
+ }
+ } else {
+ // Strip prefix. Infer base from prefix if unknown (base == 0), or else verify its
+ // consistency.
+ digits = string.substring(prefix.length());
+ int expectedBase = intPrefixes.get(prefix);
+ if (base == 0) {
+ base = expectedBase;
+ } else if (base != expectedBase) {
+ throw new EvalException(
+ loc,
+ Printer.format("invalid literal for int() with base %d: %r", base, stringForErrors));
+ }
+ }
- private int fromString(String string, Location loc, int base) throws EvalException {
- String stringForErrors = string;
+ if (base < 2 || base > 36) {
+ throw new EvalException(loc, "int() base must be >= 2 and <= 36");
+ }
+ try {
+ // Negate by prepending a negative symbol, rather than by using arithmetic on the
+ // result, to handle the edge case of -2^31 correctly.
+ String parseable = isNegative ? "-" + digits : digits;
+ return Integer.parseInt(parseable, base);
+ } catch (NumberFormatException | ArithmeticException e) {
+ throw new EvalException(
+ loc,
+ Printer.format("invalid literal for int() with base %d: %r", base, stringForErrors),
+ e);
+ }
+ }
- boolean isNegative = false;
- if (string.isEmpty()) {
- throw new EvalException(
- loc, Printer.format("string argument to int() cannot be empty"));
- }
- char c = string.charAt(0);
- if (c == '+') {
- string = string.substring(1);
- } else if (c == '-') {
- string = string.substring(1);
- isNegative = true;
- }
+ @Nullable
+ private String getIntegerPrefix(String value) {
+ value = Ascii.toLowerCase(value);
+ for (String prefix : intPrefixes.keySet()) {
+ if (value.startsWith(prefix)) {
+ return prefix;
+ }
+ }
+ return null;
+ }
- String prefix = getIntegerPrefix(string);
- String digits;
- if (prefix == null) {
- // Nothing to strip. Infer base 10 if autodetection was requested (base == 0).
- digits = string;
- if (base == 0) {
- if (string.length() > 1 && string.startsWith("0")) {
- // We don't infer the base when input starts with '0' (due
- // to confusion between octal and decimal).
- throw new EvalException(
- loc,
- Printer.format(
- "cannot infer base for int() when value begins with a 0: %r",
- stringForErrors));
- }
- base = 10;
- }
- } else {
- // Strip prefix. Infer base from prefix if unknown (base == 0), or else verify its
- // consistency.
- digits = string.substring(prefix.length());
- int expectedBase = intPrefixes.get(prefix);
- if (base == 0) {
- base = expectedBase;
- } else if (base != expectedBase) {
- throw new EvalException(
- loc,
- Printer.format(
- "invalid literal for int() with base %d: %r", base, stringForErrors));
- }
- }
-
- if (base < 2 || base > 36) {
- throw new EvalException(loc, "int() base must be >= 2 and <= 36");
- }
- try {
- // Negate by prepending a negative symbol, rather than by using arithmetic on the
- // result, to handle the edge case of -2^31 correctly.
- String parseable = isNegative ? "-" + digits : digits;
- return Integer.parseInt(parseable, base);
- } catch (NumberFormatException | ArithmeticException e) {
- throw new EvalException(
- loc,
- Printer.format("invalid literal for int() with base %d: %r", base, stringForErrors),
- e);
- }
- }
-
- @Nullable
- private String getIntegerPrefix(String value) {
- value = Ascii.toLowerCase(value);
- for (String prefix : intPrefixes.keySet()) {
- if (value.startsWith(prefix)) {
- return prefix;
- }
- }
- return null;
- }
- };
-
- @SkylarkSignature(
+ @SkylarkCallable(
name = "dict",
- returnType = SkylarkDict.class,
doc =
"Creates a <a href=\"dict.html\">dictionary</a> from an optional positional "
+ "argument and an optional set of keyword arguments. In the case where the same key "
@@ -527,49 +469,43 @@
defaultValue = "[]",
doc =
"Either a dictionary or a list of entries. Entries must be tuples or lists with "
- + "exactly two elements: key, value."),
+ + "exactly two elements: key, value.",
+ legacyNamed = true),
},
extraKeywords = @Param(name = "kwargs", doc = "Dictionary of additional entries."),
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction dict =
- new BuiltinFunction("dict") {
- public SkylarkDict<?, ?> invoke(
- Object args, SkylarkDict<String, Object> kwargs, Location loc, Environment env)
- throws EvalException {
- SkylarkDict<?, ?> argsDict =
- (args instanceof SkylarkDict)
- ? (SkylarkDict<?, ?>) args
- : SkylarkDict.getDictFromArgs(args, loc, env);
- return SkylarkDict.plus(argsDict, kwargs, env);
- }
- };
+ public SkylarkDict<?, ?> dict(
+ Object args, SkylarkDict<?, ?> kwargs, Location loc, Environment env) throws EvalException {
+ SkylarkDict<?, ?> argsDict =
+ (args instanceof SkylarkDict)
+ ? (SkylarkDict<?, ?>) args
+ : SkylarkDict.getDictFromArgs(args, loc, env);
+ return SkylarkDict.plus(argsDict, kwargs, env);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "enumerate",
- returnType = MutableList.class,
doc =
"Returns a list of pairs (two-element tuples), with the index (int) and the item from"
+ " the input list.\n<pre class=\"language-python\">"
+ "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]</pre>\n",
- parameters = {@Param(name = "list", type = SkylarkList.class, doc = "input list.")},
+ parameters = {
+ @Param(name = "list", type = SkylarkList.class, doc = "input list.", legacyNamed = true)
+ },
useEnvironment = true)
- private static final BuiltinFunction enumerate =
- new BuiltinFunction("enumerate") {
- public MutableList<?> invoke(SkylarkList<?> input, Environment env) throws EvalException {
- int count = 0;
- ArrayList<SkylarkList<?>> result = new ArrayList<>(input.size());
- for (Object obj : input) {
- result.add(Tuple.of(count, obj));
- count++;
- }
- return MutableList.wrapUnsafe(env, result);
- }
- };
+ public MutableList<?> enumerate(SkylarkList<?> input, Environment env) throws EvalException {
+ int count = 0;
+ ArrayList<SkylarkList<?>> result = new ArrayList<>(input.size());
+ for (Object obj : input) {
+ result.add(Tuple.of(count, obj));
+ count++;
+ }
+ return MutableList.wrapUnsafe(env, result);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "hash",
- returnType = Integer.class,
doc =
"Return a hash value for a string. This is computed deterministically using the same "
+ "algorithm as Java's <code>String.hashCode()</code>, namely: "
@@ -578,17 +514,19 @@
// Deterministic hashing is important for the consistency of builds, hence why we
// promise a specific algorithm. This is in contrast to Java (Object.hashCode()) and
// Python, which promise stable hashing only within a given execution of the program.
- parameters = {@Param(name = "value", type = String.class, doc = "String value to hash.")})
- private static final BuiltinFunction hash =
- new BuiltinFunction("hash") {
- public Integer invoke(String value) throws EvalException {
- return value.hashCode();
- }
- };
+ parameters = {
+ @Param(
+ name = "value",
+ type = String.class,
+ doc = "String value to hash.",
+ legacyNamed = true)
+ })
+ public Integer hash(String value) throws EvalException {
+ return value.hashCode();
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "range",
- returnType = SkylarkList.class,
doc =
"Creates a list where items go from <code>start</code> to <code>stop</code>, using a "
+ "<code>step</code> increment. If a single argument is provided, items will "
@@ -602,7 +540,8 @@
type = Integer.class,
doc =
"Value of the start element if stop is provided, "
- + "otherwise value of stop and the actual start is 0"),
+ + "otherwise value of stop and the actual start is 0",
+ legacyNamed = true),
@Param(
name = "stop_or_none",
type = Integer.class,
@@ -610,62 +549,60 @@
defaultValue = "None",
doc =
"optional index of the first item <i>not</i> to be included in the resulting "
- + "list; generation of the list stops before <code>stop</code> is reached."),
+ + "list; generation of the list stops before <code>stop</code> is reached.",
+ legacyNamed = true),
@Param(
name = "step",
type = Integer.class,
defaultValue = "1",
- doc = "The increment (default is 1). It may be negative.")
+ doc = "The increment (default is 1). It may be negative.",
+ legacyNamed = true)
},
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction range =
- new BuiltinFunction("range") {
- public SkylarkList<Integer> invoke(
- Integer startOrStop, Object stopOrNone, Integer step, Location loc, Environment env)
- throws EvalException {
- int start;
- int stop;
- if (stopOrNone == Runtime.NONE) {
- start = 0;
- stop = startOrStop;
- } else {
- start = startOrStop;
- stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'");
- }
- if (step == 0) {
- throw new EvalException(loc, "step cannot be 0");
- }
- return RangeList.of(start, stop, step);
- }
- };
+ public SkylarkList<Integer> range(
+ Integer startOrStop, Object stopOrNone, Integer step, Location loc, Environment env)
+ throws EvalException {
+ int start;
+ int stop;
+ if (stopOrNone == Runtime.NONE) {
+ start = 0;
+ stop = startOrStop;
+ } else {
+ start = startOrStop;
+ stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'");
+ }
+ if (step == 0) {
+ throw new EvalException(loc, "step cannot be 0");
+ }
+ return RangeList.of(start, stop, step);
+ }
/** Returns true if the object has a field of the given name, otherwise false. */
- @SkylarkSignature(
+ @SkylarkCallable(
name = "hasattr",
- returnType = Boolean.class,
doc =
"Returns True if the object <code>x</code> has an attribute or method of the given "
+ "<code>name</code>, otherwise False. Example:<br>"
+ "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>",
parameters = {
- @Param(name = "x", doc = "The object to check."),
- @Param(name = "name", type = String.class, doc = "The name of the attribute.")
+ @Param(name = "x", doc = "The object to check.", legacyNamed = true, noneable = true),
+ @Param(
+ name = "name",
+ type = String.class,
+ doc = "The name of the attribute.",
+ legacyNamed = true)
},
useEnvironment = true)
- private static final BuiltinFunction hasattr =
- new BuiltinFunction("hasattr") {
- @SuppressWarnings("unused")
- public Boolean invoke(Object obj, String name, Environment env) throws EvalException {
- if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) {
- return true;
- }
- // shouldn't this filter things with struct_field = false?
- return DotExpression.hasMethod(env.getSemantics(), obj, name);
- }
- };
+ public Boolean hasAttr(Object obj, String name, Environment env) throws EvalException {
+ if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) {
+ return true;
+ }
+ // shouldn't this filter things with struct_field = false?
+ return DotExpression.hasMethod(env.getSemantics(), obj, name);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "getattr",
doc =
"Returns the struct's field of the given name if it exists. If not, it either returns "
@@ -676,70 +613,69 @@
+ "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n"
+ "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>",
parameters = {
- @Param(name = "x", doc = "The struct whose attribute is accessed."),
- @Param(name = "name", doc = "The name of the struct attribute."),
+ @Param(
+ name = "x",
+ doc = "The struct whose attribute is accessed.",
+ legacyNamed = true,
+ noneable = true),
+ @Param(name = "name", doc = "The name of the struct attribute.", legacyNamed = true),
@Param(
name = "default",
defaultValue = "unbound",
doc =
"The default value to return in case the struct "
- + "doesn't have an attribute of the given name.")
+ + "doesn't have an attribute of the given name.",
+ legacyNamed = true,
+ noneable = true)
},
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction getattr =
- new BuiltinFunction("getattr") {
- @SuppressWarnings("unused")
- public Object invoke(
- Object obj, String name, Object defaultValue, Location loc, Environment env)
- throws EvalException, InterruptedException {
- Object result = DotExpression.eval(obj, name, loc, env);
- if (result == null) {
- if (defaultValue != Runtime.UNBOUND) {
- return defaultValue;
- }
- throw DotExpression.getMissingFieldException(
- obj, name, loc, env.getSemantics(), "attribute");
- }
- return result;
- }
- };
+ public Object getAttr(Object obj, String name, Object defaultValue, Location loc, Environment env)
+ throws EvalException, InterruptedException {
+ Object result = DotExpression.eval(obj, name, loc, env);
+ if (result == null) {
+ if (defaultValue != Runtime.UNBOUND) {
+ return defaultValue;
+ }
+ throw DotExpression.getMissingFieldException(obj, name, loc, env.getSemantics(), "attribute");
+ }
+ return result;
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "dir",
- returnType = MutableList.class,
doc =
"Returns a list of strings: the names of the attributes and "
+ "methods of the parameter object.",
- parameters = {@Param(name = "x", doc = "The object to check.")},
+ parameters = {
+ @Param(name = "x", doc = "The object to check.", legacyNamed = true, noneable = true)
+ },
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction dir =
- new BuiltinFunction("dir") {
- public MutableList<?> invoke(Object object, Location loc, Environment env)
- throws EvalException {
- // Order the fields alphabetically.
- Set<String> fields = new TreeSet<>();
- if (object instanceof ClassObject) {
- fields.addAll(((ClassObject) object).getFieldNames());
- }
- fields.addAll(Runtime.getBuiltinRegistry().getFunctionNames(object.getClass()));
- fields.addAll(FuncallExpression.getMethodNames(env.getSemantics(), object.getClass()));
- return MutableList.copyOf(env, fields);
- }
- };
+ public MutableList<?> dir(Object object, Location loc, Environment env) throws EvalException {
+ // Order the fields alphabetically.
+ Set<String> fields = new TreeSet<>();
+ if (object instanceof ClassObject) {
+ fields.addAll(((ClassObject) object).getFieldNames());
+ }
+ fields.addAll(Runtime.getBuiltinRegistry().getFunctionNames(object.getClass()));
+ fields.addAll(FuncallExpression.getMethodNames(env.getSemantics(), object.getClass()));
+ return MutableList.copyOf(env, fields);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "fail",
doc =
"Raises an error that cannot be intercepted. It can be used anywhere, "
+ "both in the loading phase and in the analysis phase.",
- returnType = Runtime.NoneType.class,
parameters = {
@Param(
name = "msg",
type = Object.class,
- doc = "Error to display for the user. The object is converted to a string."),
+ doc = "Error to display for the user. The object is converted to a string.",
+ defaultValue = "None",
+ legacyNamed = true,
+ noneable = true),
@Param(
name = "attr",
type = String.class,
@@ -747,23 +683,20 @@
defaultValue = "None",
doc =
"The name of the attribute that caused the error. This is used only for "
- + "error reporting.")
+ + "error reporting.",
+ legacyNamed = true)
},
useLocation = true)
- private static final BuiltinFunction fail =
- new BuiltinFunction("fail") {
- public Runtime.NoneType invoke(Object msg, Object attr, Location loc) throws EvalException {
- String str = Printer.str(msg);
- if (attr != Runtime.NONE) {
- str = String.format("attribute %s: %s", attr, str);
- }
- throw new EvalException(loc, str);
- }
- };
+ public Runtime.NoneType fail(Object msg, Object attr, Location loc) throws EvalException {
+ String str = Printer.str(msg);
+ if (attr != Runtime.NONE) {
+ str = String.format("attribute %s: %s", attr, str);
+ }
+ throw new EvalException(loc, str);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "print",
- returnType = Runtime.NoneType.class,
doc =
"Prints <code>args</code> as debug output. It will be prefixed with the string <code>"
+ "\"DEBUG\"</code> and the location (file and line number) of this call. The "
@@ -787,26 +720,21 @@
extraPositionals = @Param(name = "args", doc = "The objects to print."),
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction print =
- new BuiltinFunction("print") {
- public Runtime.NoneType invoke(
- String sep, SkylarkList<?> starargs, Location loc, Environment env)
- throws EvalException {
- String msg = starargs.stream().map(Printer::debugPrint).collect(joining(sep));
- // As part of the integration test "skylark_flag_test.sh", if the
- // "--internal_skylark_flag_test_canary" flag is enabled, append an extra marker string to
- // the output.
- if (env.getSemantics().internalSkylarkFlagTestCanary()) {
- msg += "<== skylark flag test ==>";
- }
- env.handleEvent(Event.debug(loc, msg));
- return Runtime.NONE;
- }
- };
+ public Runtime.NoneType print(String sep, SkylarkList<?> starargs, Location loc, Environment env)
+ throws EvalException {
+ String msg = starargs.stream().map(Printer::debugPrint).collect(joining(sep));
+ // As part of the integration test "skylark_flag_test.sh", if the
+ // "--internal_skylark_flag_test_canary" flag is enabled, append an extra marker string to
+ // the output.
+ if (env.getSemantics().internalSkylarkFlagTestCanary()) {
+ msg += "<== skylark flag test ==>";
+ }
+ env.handleEvent(Event.debug(loc, msg));
+ return Runtime.NONE;
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "type",
- returnType = String.class,
doc =
"Returns the type name of its argument. This is useful for debugging and "
+ "type-checking. Examples:"
@@ -820,18 +748,20 @@
+ "<pre class=\"language-python\">"
+ "if type(x) == type([]): # if x is a list"
+ "</pre>",
- parameters = {@Param(name = "x", doc = "The object to check type of.")})
- private static final BuiltinFunction type =
- new BuiltinFunction("type") {
- public String invoke(Object object) {
- // There is no 'type' type in Skylark, so we return a string with the type name.
- return EvalUtils.getDataTypeName(object, false);
- }
- };
+ parameters = {
+ @Param(
+ name = "x",
+ doc = "The object to check type of.",
+ legacyNamed = true,
+ noneable = true)
+ })
+ public String type(Object object) {
+ // There is no 'type' type in Skylark, so we return a string with the type name.
+ return EvalUtils.getDataTypeName(object, false);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "depset",
- returnType = SkylarkNestedSet.class,
doc =
"Creates a <a href=\"depset.html\">depset</a>. The <code>direct</code> parameter is a "
+ "list of direct elements of the depset, and <code>transitive</code> parameter is "
@@ -861,14 +791,16 @@
"Deprecated: Either an iterable whose items become the direct elements of "
+ "the new depset, in left-to-right order, or else a depset that becomes "
+ "a transitive element of the new depset. In the latter case, "
- + "<code>transitive</code> cannot be specified."),
+ + "<code>transitive</code> cannot be specified.",
+ legacyNamed = true),
@Param(
name = "order",
type = String.class,
defaultValue = "\"default\"",
doc =
"The traversal strategy for the new depset. See "
- + "<a href=\"depset.html\">here</a> for the possible values."),
+ + "<a href=\"depset.html\">here</a> for the possible values.",
+ legacyNamed = true),
@Param(
name = "direct",
type = SkylarkList.class,
@@ -888,55 +820,52 @@
defaultValue = "None")
},
useLocation = true)
- private static final BuiltinFunction depset =
- new BuiltinFunction("depset") {
- public SkylarkNestedSet invoke(
- Object items, String orderString, Object direct, Object transitive, Location loc)
- throws EvalException {
- Order order;
- try {
- order = Order.parse(orderString);
- } catch (IllegalArgumentException ex) {
- throw new EvalException(loc, ex);
- }
+ public SkylarkNestedSet depset(
+ Object items, String orderString, Object direct, Object transitive, Location loc)
+ throws EvalException {
+ Order order;
+ try {
+ order = Order.parse(orderString);
+ } catch (IllegalArgumentException ex) {
+ throw new EvalException(loc, ex);
+ }
- if (transitive == Runtime.NONE && direct == Runtime.NONE) {
- // Legacy behavior.
- return SkylarkNestedSet.of(order, items, loc);
- }
+ if (transitive == Runtime.NONE && direct == Runtime.NONE) {
+ // Legacy behavior.
+ return SkylarkNestedSet.of(order, items, loc);
+ }
- if (direct != Runtime.NONE && !isEmptySkylarkList(items)) {
- throw new EvalException(
- loc, "Do not pass both 'direct' and 'items' argument to depset constructor.");
- }
+ if (direct != Runtime.NONE && !isEmptySkylarkList(items)) {
+ throw new EvalException(
+ loc, "Do not pass both 'direct' and 'items' argument to depset constructor.");
+ }
- // Non-legacy behavior: either 'transitive' or 'direct' were specified.
- Iterable<Object> directElements;
- if (direct != Runtime.NONE) {
- directElements = ((SkylarkList<?>) direct).getContents(Object.class, "direct");
- } else {
- SkylarkType.checkType(items, SkylarkList.class, "items");
- directElements = ((SkylarkList<?>) items).getContents(Object.class, "items");
- }
+ // Non-legacy behavior: either 'transitive' or 'direct' were specified.
+ Iterable<Object> directElements;
+ if (direct != Runtime.NONE) {
+ directElements = ((SkylarkList<?>) direct).getContents(Object.class, "direct");
+ } else {
+ SkylarkType.checkType(items, SkylarkList.class, "items");
+ directElements = ((SkylarkList<?>) items).getContents(Object.class, "items");
+ }
- Iterable<SkylarkNestedSet> transitiveList;
- if (transitive != Runtime.NONE) {
- SkylarkType.checkType(transitive, SkylarkList.class, "transitive");
- transitiveList =
- ((SkylarkList<?>) transitive).getContents(SkylarkNestedSet.class, "transitive");
- } else {
- transitiveList = ImmutableList.of();
- }
- SkylarkNestedSet.Builder builder = SkylarkNestedSet.builder(order, loc);
- for (Object directElement : directElements) {
- builder.addDirect(directElement);
- }
- for (SkylarkNestedSet transitiveSet : transitiveList) {
- builder.addTransitive(transitiveSet);
- }
- return builder.build();
- }
- };
+ Iterable<SkylarkNestedSet> transitiveList;
+ if (transitive != Runtime.NONE) {
+ SkylarkType.checkType(transitive, SkylarkList.class, "transitive");
+ transitiveList =
+ ((SkylarkList<?>) transitive).getContents(SkylarkNestedSet.class, "transitive");
+ } else {
+ transitiveList = ImmutableList.of();
+ }
+ SkylarkNestedSet.Builder builder = SkylarkNestedSet.builder(order, loc);
+ for (Object directElement : directElements) {
+ builder.addDirect(directElement);
+ }
+ for (SkylarkNestedSet transitiveSet : transitiveList) {
+ builder.addTransitive(transitiveSet);
+ }
+ return builder.build();
+ }
private static boolean isEmptySkylarkList(Object o) {
return o instanceof SkylarkList && ((SkylarkList) o).isEmpty();
@@ -946,7 +875,7 @@
* Returns a function-value implementing "select" (i.e. configurable attributes) in the specified
* package context.
*/
- @SkylarkSignature(
+ @SkylarkCallable(
name = "select",
doc =
"<code>select()</code> is the helper function that makes a rule attribute "
@@ -954,29 +883,31 @@
+ "configurable</a>. See "
+ "<a href=\"$BE_ROOT/functions.html#select\">build encyclopedia</a> for details.",
parameters = {
- @Param(name = "x", type = SkylarkDict.class, doc = "The parameter to convert."),
+ @Param(
+ name = "x",
+ type = SkylarkDict.class,
+ doc = "The parameter to convert.",
+ legacyNamed = true),
@Param(
name = "no_match_error",
type = String.class,
defaultValue = "''",
- doc = "Optional custom error to report if no condition matches.")
+ doc = "Optional custom error to report if no condition matches.",
+ legacyNamed = true)
},
useLocation = true)
- private static final BuiltinFunction select =
- new BuiltinFunction("select") {
- public Object invoke(SkylarkDict<?, ?> dict, String noMatchError, Location loc)
- throws EvalException {
- for (Object key : dict.keySet()) {
- if (!(key instanceof String)) {
- throw new EvalException(
- loc, String.format("Invalid key: %s. select keys must be label references", key));
- }
- }
- return SelectorList.of(new SelectorValue(dict, noMatchError));
- }
- };
+ public Object select(SkylarkDict<?, ?> dict, String noMatchError, Location loc)
+ throws EvalException {
+ for (Object key : dict.keySet()) {
+ if (!(key instanceof String)) {
+ throw new EvalException(
+ loc, String.format("Invalid key: %s. select keys must be label references", key));
+ }
+ }
+ return SelectorList.of(new SelectorValue(dict, noMatchError));
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "zip",
doc =
"Returns a <code>list</code> of <code>tuple</code>s, where the i-th tuple contains "
@@ -989,36 +920,32 @@
+ "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n"
+ "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>",
extraPositionals = @Param(name = "args", doc = "lists to zip."),
- returnType = MutableList.class,
useLocation = true,
useEnvironment = true)
- private static final BuiltinFunction zip =
- new BuiltinFunction("zip") {
- public MutableList<?> invoke(SkylarkList<?> args, Location loc, Environment env)
- throws EvalException {
- Iterator<?>[] iterators = new Iterator<?>[args.size()];
- for (int i = 0; i < args.size(); i++) {
- iterators[i] = EvalUtils.toIterable(args.get(i), loc, env).iterator();
- }
- ArrayList<Tuple<?>> result = new ArrayList<>();
- boolean allHasNext;
- do {
- allHasNext = !args.isEmpty();
- List<Object> elem = Lists.newArrayListWithExpectedSize(args.size());
- for (Iterator<?> iterator : iterators) {
- if (iterator.hasNext()) {
- elem.add(iterator.next());
- } else {
- allHasNext = false;
- }
- }
- if (allHasNext) {
- result.add(Tuple.copyOf(elem));
- }
- } while (allHasNext);
- return MutableList.wrapUnsafe(env, result);
+ public MutableList<?> zip(SkylarkList<?> args, Location loc, Environment env)
+ throws EvalException {
+ Iterator<?>[] iterators = new Iterator<?>[args.size()];
+ for (int i = 0; i < args.size(); i++) {
+ iterators[i] = EvalUtils.toIterable(args.get(i), loc, env).iterator();
+ }
+ ArrayList<Tuple<?>> result = new ArrayList<>();
+ boolean allHasNext;
+ do {
+ allHasNext = !args.isEmpty();
+ List<Object> elem = Lists.newArrayListWithExpectedSize(args.size());
+ for (Iterator<?> iterator : iterators) {
+ if (iterator.hasNext()) {
+ elem.add(iterator.next());
+ } else {
+ allHasNext = false;
}
- };
+ }
+ if (allHasNext) {
+ result.add(Tuple.copyOf(elem));
+ }
+ } while (allHasNext);
+ return MutableList.wrapUnsafe(env, result);
+ }
/** Skylark int type. */
@SkylarkModule(
@@ -1053,17 +980,6 @@
/** Adds bindings for all the builtin functions of this class to the given map builder. */
public static void addBindingsToBuilder(ImmutableMap.Builder<String, Object> builder) {
- for (BaseFunction function : allFunctions) {
- builder.put(function.getName(), function);
- }
- }
-
- private static final ImmutableList<BaseFunction> allFunctions =
- ImmutableList.of(
- all, any, bool, depset, dict, dir, fail, getattr, hasattr, hash, enumerate, int_, len,
- list, max, min, print, range, repr, reversed, select, sorted, str, tuple, type, zip);
-
- static {
- SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class);
+ Runtime.setupSkylarkLibrary(builder, new MethodLibrary());
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java b/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java
index 7a47cc1..e44b45d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ParamDescriptor.java
@@ -73,7 +73,11 @@
this.flagResponsibleForDisable = flagResponsibleForDisable;
}
- static ParamDescriptor of(Param param, StarlarkSemantics starlarkSemantics) {
+ /**
+ * Returns a {@link ParamDescriptor} representing the given raw {@link Param} annotation and the
+ * given semantics.
+ */
+ public static ParamDescriptor of(Param param, StarlarkSemantics starlarkSemantics) {
ImmutableList<ParamTypeDescriptor> allowedTypes =
Arrays.stream(param.allowedTypes())
.map(ParamTypeDescriptor::of)
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
index 7656031..3f37168 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
@@ -2022,7 +2022,9 @@
"silly_rule(name = 'silly')");
thrown.handleAssertionErrors(); // Compatibility with JUnit 4.11
thrown.expect(AssertionError.class);
- thrown.expectMessage("<rule context for //test:silly> is not of type string or int or bool");
+ thrown.expectMessage(
+ "expected value of type 'function' for parameter 'implementation', "
+ + "for call to function rule");
getConfiguredTarget("//test:silly");
}
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkStringRepresentationsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkStringRepresentationsTest.java
index d1c833f..52590c1 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkStringRepresentationsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkStringRepresentationsTest.java
@@ -267,12 +267,12 @@
@Test
public void testStringRepresentations_Rules() throws Exception {
assertStringRepresentation("native.cc_library", "<built-in rule cc_library>");
- assertStringRepresentation("rule(implementation=str)", "<rule>");
+ assertStringRepresentation("def f(): pass", "rule(implementation=f)", "<rule>");
}
@Test
public void testStringRepresentations_Aspects() throws Exception {
- assertStringRepresentation("aspect(implementation=str)", "<aspect>");
+ assertStringRepresentation("def f(): pass", "aspect(implementation=f)", "<aspect>");
}
@Test
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
index 12d3287..bf4cc44 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
@@ -335,7 +335,8 @@
"dict('a')")
.testIfErrorContains("cannot convert item #0 to a sequence", "dict(['a'])")
.testIfErrorContains("cannot convert item #0 to a sequence", "dict([('a')])")
- .testIfErrorContains("too many (3) positional arguments", "dict((3,4), (3,2), (1,2))")
+ .testIfErrorContains(
+ "expected no more than 1 positional arguments, but got 3", "dict((3,4), (3,2), (1,2))")
.testIfErrorContains(
"item #0 has length 3, but exactly two elements are required",
"dict([('a', 'b', 'c')])");
@@ -464,8 +465,8 @@
.testStatement("hash('skylark')", "skylark".hashCode())
.testStatement("hash('google')", "google".hashCode())
.testIfErrorContains(
- "argument 'value' has type 'NoneType', but should be 'string'\n"
- + "in call to builtin function hash(value)",
+ "expected value of type 'string' for parameter 'value', "
+ + "for call to function hash(value)",
"hash(None)");
}
@@ -534,8 +535,8 @@
public void testEnumerateBadArg() throws Exception {
new BothModesTest()
.testIfErrorContains(
- "argument 'list' has type 'string', but should be 'sequence'\n"
- + "in call to builtin function enumerate(list)",
+ "expected value of type 'sequence' for parameter 'list', "
+ + "for call to function enumerate(list)",
"enumerate('a')");
}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/RuntimeTest.java b/src/test/java/com/google/devtools/build/lib/syntax/RuntimeTest.java
index e2fc1e6..5444ef8 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/RuntimeTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/RuntimeTest.java
@@ -19,7 +19,6 @@
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
-import java.lang.reflect.Field;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -106,13 +105,4 @@
.matches("Attempted to register function 'dummyFunc' in namespace '(.*)DummyType' after "
+ "registry has already been frozen");
}
-
- @Test
- public void checkStaticallyRegistered_Global() throws Exception {
- Field lenField = MethodLibrary.class.getDeclaredField("len");
- lenField.setAccessible(true);
- Object lenFieldValue = lenField.get(null);
- List<Object> builtins = Runtime.getBuiltinRegistry().getBuiltins();
- assertThat(builtins).contains(lenFieldValue);
- }
}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index 4f2a446..1cd50c0 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -1998,9 +1998,10 @@
@Test
public void testPrintBadKwargs() throws Exception {
- new SkylarkTest().testIfExactError(
- "unexpected keywords 'end', 'other' in call to print(*args, sep: string = \" \")",
- "print(end='x', other='y')");
+ new SkylarkTest()
+ .testIfErrorContains(
+ "unexpected keywords 'end', 'other', for call to function print(sep = \" \", *args)",
+ "print(end='x', other='y')");
}
// Override tests in EvaluationTest incompatible with Skylark
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java
index 40e13e4..ed522f7 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java
@@ -266,12 +266,11 @@
@Test
public void testTooManyPositionals() throws Exception {
- new BothModesTest().testIfExactError(
- "too many (3) positional arguments in call to "
- + "depset(items = [], order: string = \"default\", *, "
- + "direct: sequence or NoneType = None, "
- + "transitive: sequence of depsets or NoneType = None)",
- "depset([], 'default', [])");
+ new BothModesTest()
+ .testIfErrorContains(
+ "expected no more than 2 positional arguments, but got 3, for call to function "
+ + "depset(items = [], order = \"default\", direct = None, transitive = None)",
+ "depset([], 'default', [])");
}
diff --git a/src/test/starlark/testdata/int_constructor.sky b/src/test/starlark/testdata/int_constructor.sky
index 03c43e4..f6ee91e 100644
--- a/src/test/starlark/testdata/int_constructor.sky
+++ b/src/test/starlark/testdata/int_constructor.sky
@@ -23,7 +23,7 @@
---
int('ab') ### invalid literal for int\(\) with base 10: "ab"
---
-int(None) ### None is not of type string or int or bool
+int(None) ### parameter 'x' cannot be None, for call to function int(x, base = unbound)
---
int('123', 3) ### invalid literal for int\(\) with base 3: "123"
---
diff --git a/src/test/starlark/testdata/int_function.sky b/src/test/starlark/testdata/int_function.sky
index 7d6a382..29d066f 100644
--- a/src/test/starlark/testdata/int_function.sky
+++ b/src/test/starlark/testdata/int_function.sky
@@ -10,10 +10,10 @@
assert_eq(int(False), 0)
---
-int(None) ### None is not of type string or int or bool
+int(None) ### parameter 'x' cannot be None, for call to function int(x, base = unbound)
---
# This case is allowed in Python but not Skylark
-int() ### insufficient arguments received
+int() ### parameter 'x' has no default value, for call to function int(x, base = unbound)
---
# string, no base
diff --git a/src/test/starlark/testdata/reversed.sky b/src/test/starlark/testdata/reversed.sky
index 08beba8..bf14c68 100644
--- a/src/test/starlark/testdata/reversed.sky
+++ b/src/test/starlark/testdata/reversed.sky
@@ -7,7 +7,7 @@
assert_eq(reversed('bbb'.elems()), ['b', 'b', 'b'])
---
-reversed(None) ### type 'NoneType' is not iterable
+reversed(None) ### parameter 'sequence' cannot be None, for call to function reversed(sequence)
---
reversed(1) ### type 'int' is not iterable
---
diff --git a/src/tools/skylark/java/com/google/devtools/skylark/skylint/Environment.java b/src/tools/skylark/java/com/google/devtools/skylark/skylint/Environment.java
index 68d18e9..f0ae3e7 100644
--- a/src/tools/skylark/java/com/google/devtools/skylark/skylint/Environment.java
+++ b/src/tools/skylark/java/com/google/devtools/skylark/skylint/Environment.java
@@ -14,6 +14,7 @@
package com.google.devtools.skylark.skylint;
+import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.packages.BazelLibrary;
import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
import com.google.devtools.build.lib.syntax.ASTNode;
@@ -49,7 +50,12 @@
env.addBuiltin("None");
env.addBuiltin("True");
env.addBuiltin("False");
- env.setupFunctions(MethodLibrary.class, BazelLibrary.class);
+ env.setupFunctions(BazelLibrary.class);
+ ImmutableMap.Builder<String, Object> builtinMap = ImmutableMap.builder();
+ MethodLibrary.addBindingsToBuilder(builtinMap);
+ for (String builtinName : builtinMap.build().keySet()) {
+ env.addBuiltin(builtinName);
+ }
return env;
}