(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;
   }