Use BuiltinFunction for all builtins

Replace the uses of AbstractFunction, MixedModeFunction,
SkylarkFunction and SimpleSkylarkFunction by BuiltinFunction.

--
MOS_MIGRATED_REVID=91763158
diff --git a/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java
index 07988a9..9d7faa8 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java
@@ -14,19 +14,15 @@
 
 package com.google.devtools.build.lib.packages;
 
-import static com.google.devtools.build.lib.syntax.SkylarkFunction.cast;
-
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
-import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.packages.Type.ConversionException;
-import com.google.devtools.build.lib.syntax.AbstractFunction;
-import com.google.devtools.build.lib.syntax.AbstractFunction.NoArgFunction;
+import com.google.devtools.build.lib.syntax.BuiltinFunction;
 import com.google.devtools.build.lib.syntax.ClassObject;
 import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.DotExpression;
@@ -35,7 +31,6 @@
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.FuncallExpression;
 import com.google.devtools.build.lib.syntax.Function;
-import com.google.devtools.build.lib.syntax.MixedModeFunction;
 import com.google.devtools.build.lib.syntax.SelectorList;
 import com.google.devtools.build.lib.syntax.SelectorValue;
 import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
@@ -44,11 +39,12 @@
 import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
 import com.google.devtools.build.lib.syntax.SkylarkSignature;
 import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
+import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
 import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -106,7 +102,7 @@
     return str.substring(start, stop);
   }
 
-  public static int getListIndex(Object key, int listSize, FuncallExpression ast)
+  public static int getListIndex(Object key, int listSize, Location loc)
       throws ConversionException, EvalException {
     // Get the nth element in the list
     int index = Type.INTEGER.convert(key, "index operand");
@@ -114,7 +110,7 @@
       index += listSize;
     }
     if (index < 0 || index >= listSize) {
-      throw new EvalException(ast.getLocation(), "List index out of range (index is "
+      throw new EvalException(loc, "List index out of range (index is "
           + index + ", but list has " + listSize + " elements)");
     }
     return index;
@@ -129,25 +125,20 @@
       mandatoryPositionals = {
         @Param(name = "self", type = String.class, doc = "This string, a separator."),
         @Param(name = "elements", type = HackHackEitherList.class, doc = "The objects to join.")})
-  private static Function join = new MixedModeFunction("join",
-      ImmutableList.of("this", "elements"), 2, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
-      String thisString = Type.STRING.convert(args[0], "'join' operand");
-      List<?> seq = Type.OBJECT_LIST.convert(args[1], "'join' argument");
-      return Joiner.on(thisString).join(seq);
-    }};
+  private static BuiltinFunction join = new BuiltinFunction("join") {
+    public String invoke(String self, Object elements) throws ConversionException {
+      List<?> seq = Type.OBJECT_LIST.convert(elements, "'join.elements' argument");
+      return Joiner.on(self).join(seq);
+    }
+  };
 
   @SkylarkSignature(name = "lower", objectType = StringModule.class, returnType = String.class,
       doc = "Returns the lower case version of this string.",
       mandatoryPositionals = {
         @Param(name = "self", type = String.class, doc = "This string, to convert to lower case.")})
-  private static Function lower = new MixedModeFunction("lower",
-          ImmutableList.of("this"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
-      String thiz = Type.STRING.convert(args[0], "'lower' operand");
-      return thiz.toLowerCase();
+  private static BuiltinFunction lower = new BuiltinFunction("lower") {
+    public String invoke(String self) {
+      return self.toLowerCase();
     }
   };
 
@@ -155,12 +146,9 @@
       doc = "Returns the upper case version of this string.",
       mandatoryPositionals = {
         @Param(name = "self", type = String.class, doc = "This string, to convert to upper case.")})
-    private static Function upper = new MixedModeFunction("upper",
-        ImmutableList.of("this"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
-      String thiz = Type.STRING.convert(args[0], "'upper' operand");
-      return thiz.toUpperCase();
+  private static BuiltinFunction upper = new BuiltinFunction("upper") {
+    public String invoke(String self) {
+      return self.toUpperCase();
     }
   };
 
@@ -176,26 +164,20 @@
         @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None",
             doc = "The maximum number of replacements.")},
       useLocation = true)
-  private static Function replace =
-    new MixedModeFunction("replace", ImmutableList.of("this", "old", "new", "maxsplit"), 3, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException,
-        ConversionException {
-      String thiz = Type.STRING.convert(args[0], "'replace' operand");
-      String old = Type.STRING.convert(args[1], "'replace' argument");
-      String neww = Type.STRING.convert(args[2], "'replace' argument");
-      int maxsplit =
-          args[3] != null ? Type.INTEGER.convert(args[3], "'replace' argument")
-              : Integer.MAX_VALUE;
+  private static BuiltinFunction replace = new BuiltinFunction("replace") {
+    public String invoke(String self, String oldString, String newString, Object maxSplitO,
+        Location loc) throws EvalException, ConversionException {
       StringBuffer sb = new StringBuffer();
+      Integer maxSplit = Type.INTEGER.convertOptional(
+          maxSplitO, "'maxsplit' argument of 'replace'", /*label*/null, Integer.MAX_VALUE);
       try {
-        Matcher m = Pattern.compile(old, Pattern.LITERAL).matcher(thiz);
-        for (int i = 0; i < maxsplit && m.find(); i++) {
-          m.appendReplacement(sb, Matcher.quoteReplacement(neww));
+        Matcher m = Pattern.compile(oldString, Pattern.LITERAL).matcher(self);
+        for (int i = 0; i < maxSplit && m.find(); i++) {
+          m.appendReplacement(sb, Matcher.quoteReplacement(newString));
         }
         m.appendTail(sb);
       } catch (IllegalStateException e) {
-        throw new EvalException(ast.getLocation(), e.getMessage() + " in call to replace");
+        throw new EvalException(loc, e.getMessage() + " in call to replace");
       }
       return sb.toString();
     }
@@ -213,20 +195,14 @@
         @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None",
             doc = "The maximum number of splits.")},
       useEnvironment = true)
-  private static Function split = new MixedModeFunction("split",
-      ImmutableList.of("this", "sep", "maxsplit"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast, Environment env)
-        throws ConversionException {
-      String thiz = Type.STRING.convert(args[0], "'split' operand");
-      String sep = args[1] != null
-          ? Type.STRING.convert(args[1], "'split' argument")
-          : " ";
-      int maxsplit = args[2] != null
-          ? Type.INTEGER.convert(args[2], "'split' argument") + 1 // last is remainder
-          : -1;
-      String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(thiz, maxsplit);
-      List<String> result = java.util.Arrays.asList(ss);
+  private static BuiltinFunction split = new BuiltinFunction("split") {
+    public Object invoke(String self, String sep, Object maxSplitO,
+        Environment env) throws ConversionException {
+      int maxSplit = Type.INTEGER.convertOptional(
+          maxSplitO, "'split' argument of 'split'", /*label*/null, -2);
+      // + 1 because the last result is the remainder, and default of -2 so that after +1 it's -1
+      String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(self, maxSplit + 1);
+      List<String> result = Arrays.<String>asList(ss);
       return env.isSkylarkEnabled() ? SkylarkList.list(result, String.class) : result;
     }
   };
@@ -244,19 +220,6 @@
     return subpos < 0 ? subpos : subpos + start;
   }
 
-  // Common implementation for find, rfind, index, rindex.
-  // forward is true iff we want to return the last matching index.
-  private static int stringFind(String functionName, boolean forward, Object[] args)
-      throws ConversionException {
-    String thiz = Type.STRING.convert(args[0], functionName + " operand");
-    String sub = Type.STRING.convert(args[1], functionName + " argument");
-    int start = 0;
-    if (!EvalUtils.isNullOrNone(args[2])) {
-      start = Type.INTEGER.convert(args[2], functionName + " argument");
-    }
-    return stringFind(forward, thiz, sub, start, args[3], functionName + " argument");
-  }
-
   @SkylarkSignature(name = "rfind", objectType = StringModule.class, returnType = Integer.class,
       doc = "Returns the last index where <code>sub</code> is found, "
           + "or -1 if no such index exists, optionally restricting to "
@@ -270,13 +233,12 @@
             doc = "Restrict to search from this position."),
         @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
             doc = "optional position before which to restrict to search.")})
-  private static Function rfind =
-      new MixedModeFunction("rfind", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
-          return stringFind("rfind", false, args);
-        }
-      };
+  private static BuiltinFunction rfind = new BuiltinFunction("rfind") {
+    public Integer invoke(String self, String sub, Integer start, Object end)
+        throws ConversionException {
+      return stringFind(false, self, sub, start, end, "'end' argument to rfind");
+    }
+  };
 
   @SkylarkSignature(name = "find", objectType = StringModule.class, returnType = Integer.class,
       doc = "Returns the first index where <code>sub</code> is found, "
@@ -291,14 +253,12 @@
             doc = "Restrict to search from this position."),
         @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
             doc = "optional position before which to restrict to search.")})
-  private static Function find =
-      new MixedModeFunction("find", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast)
-            throws ConversionException {
-          return stringFind("find", true, args);
-        }
-      };
+  private static BuiltinFunction find = new BuiltinFunction("find") {
+    public Integer invoke(String self, String sub, Integer start, Object end)
+        throws ConversionException {
+      return stringFind(true, self, sub, start, end, "'end' argument to find");
+    }
+  };
 
   @SkylarkSignature(name = "rindex", objectType = StringModule.class, returnType = Integer.class,
       doc = "Returns the last index where <code>sub</code> is found, "
@@ -314,20 +274,17 @@
         @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
             doc = "optional position before which to restrict to search.")},
       useLocation = true)
-  private static Function rindex =
-      new MixedModeFunction("rindex", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast)
-            throws EvalException, ConversionException {
-          int res = stringFind("rindex", false, args);
-          if (res < 0) {
-            throw new EvalException(ast.getLocation(),
-                "substring " + EvalUtils.prettyPrintValue(args[1])
-                + " not found in " + EvalUtils.prettyPrintValue(args[0]));
-          }
-          return res;
-        }
-      };
+  private static BuiltinFunction rindex = new BuiltinFunction("rindex") {
+    public Integer invoke(String self, String sub, Integer start, Object end,
+        Location loc) throws EvalException, ConversionException {
+      int res = stringFind(false, self, sub, start, end, "'end' argument to rindex");
+      if (res < 0) {
+        throw new EvalException(loc, String.format("substring %s not found in %s",
+                EvalUtils.prettyPrintValue(sub), EvalUtils.prettyPrintValue(self)));
+      }
+      return res;
+    }
+  };
 
   @SkylarkSignature(name = "index", objectType = StringModule.class, returnType = Integer.class,
       doc = "Returns the first index where <code>sub</code> is found, "
@@ -343,20 +300,17 @@
         @Param(name = "end", type = Integer.class, noneable = true,
             doc = "optional position before which to restrict to search.")},
       useLocation = true)
-  private static Function index =
-      new MixedModeFunction("index", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast)
-            throws EvalException, ConversionException {
-          int res = stringFind("index", true, args);
-          if (res < 0) {
-            throw new EvalException(ast.getLocation(),
-                "substring " + EvalUtils.prettyPrintValue(args[1])
-                + " not found in " + EvalUtils.prettyPrintValue(args[0]));
-          }
-          return res;
-        }
-      };
+  private static BuiltinFunction index = new BuiltinFunction("index") {
+    public Integer invoke(String self, String sub, Integer start, Object end,
+        Location loc) throws EvalException, ConversionException {
+      int res = stringFind(true, self, sub, start, end, "'end' argument to index");
+      if (res < 0) {
+        throw new EvalException(loc, String.format("substring %s not found in %s",
+                EvalUtils.prettyPrintValue(sub), EvalUtils.prettyPrintValue(self)));
+      }
+      return res;
+    }
+  };
 
   @SkylarkSignature(name = "count", objectType = StringModule.class, returnType = Integer.class,
       doc = "Returns the number of (non-overlapping) occurrences of substring <code>sub</code> in "
@@ -370,30 +324,22 @@
             doc = "Restrict to search from this position."),
         @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
             doc = "optional position before which to restrict to search.")})
-  private static Function count =
-      new MixedModeFunction("count", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast)
-            throws ConversionException {
-          String thiz = Type.STRING.convert(args[0], "'count' operand");
-          String sub = Type.STRING.convert(args[1], "'count' argument");
-          int start = 0;
-          if (args[2] != null) {
-            start = Type.INTEGER.convert(args[2], "'count' argument");
-          }
-          String str = pythonSubstring(thiz, start, args[3], "'end' argument to 'count'");
-          if (sub.isEmpty()) {
-            return str.length() + 1;
-          }
-          int count = 0;
-          int index = -1;
-          while ((index = str.indexOf(sub)) >= 0) {
-            count++;
-            str = str.substring(index + sub.length());
-          }
-          return count;
-        }
-      };
+  private static BuiltinFunction count = new BuiltinFunction("count") {
+    public Integer invoke(String self, String sub, Integer start, Object end)
+        throws ConversionException {
+      String str = pythonSubstring(self, start, end, "'end' operand of 'find'");
+      if (sub.isEmpty()) {
+        return str.length() + 1;
+      }
+      int count = 0;
+      int index = -1;
+      while ((index = str.indexOf(sub)) >= 0) {
+        count++;
+        str = str.substring(index + sub.length());
+      }
+      return count;
+    }
+  };
 
   @SkylarkSignature(name = "endswith", objectType = StringModule.class, returnType = Boolean.class,
       doc = "Returns True if the string ends with <code>sub</code>, "
@@ -407,21 +353,13 @@
             doc = "Test beginning at this position."),
         @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
             doc = "optional position at which to stop comparing.")})
-  private static Function endswith =
-      new MixedModeFunction("endswith", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast)
-            throws ConversionException {
-          String thiz = Type.STRING.convert(args[0], "'endswith' operand");
-          String sub = Type.STRING.convert(args[1], "'endswith' argument");
-          int start = 0;
-          if (args[2] != null) {
-            start = Type.INTEGER.convert(args[2], "'endswith' argument");
-          }
-          return pythonSubstring(thiz, start, args[3], "'end' argument to 'endswith'")
-              .endsWith(sub);
-        }
-      };
+  private static BuiltinFunction endswith = new BuiltinFunction(
+      "endswith", SkylarkList.tuple(0, Environment.NONE)) {
+    public Boolean invoke(String self, String sub, Integer start, Object end)
+        throws ConversionException {
+      return pythonSubstring(self, start, end, "'end' operand of 'endswith'").endsWith(sub);
+    }
+  };
 
   @SkylarkSignature(name = "startswith", objectType = StringModule.class,
       returnType = Boolean.class,
@@ -436,18 +374,10 @@
             doc = "Test beginning at this position."),
         @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
             doc = "Stop comparing at this position.")})
-  private static Function startswith =
-    new MixedModeFunction("startswith", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
-      String thiz = Type.STRING.convert(args[0], "'startswith' operand");
-      String sub = Type.STRING.convert(args[1], "'startswith' argument");
-      int start = 0;
-      if (args[2] != null) {
-        start = Type.INTEGER.convert(args[2], "'startswith' argument");
-      }
-      return pythonSubstring(thiz, start, args[3], "'end' argument to 'startswith'")
-          .startsWith(sub);
+  private static BuiltinFunction startswith = new BuiltinFunction("startswith") {
+    public Boolean invoke(String self, String sub, Integer start, Object end)
+        throws ConversionException {
+      return pythonSubstring(self, start, end, "'end' operand of 'startswith'").startsWith(sub);
     }
   };
 
@@ -457,15 +387,11 @@
           + "have been stripped from the beginning and the end of the string.",
       mandatoryPositionals = {
         @Param(name = "self", type = String.class, doc = "This string.")})
-  private static Function strip =
-      new MixedModeFunction("strip", ImmutableList.of("this"), 1, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast)
-            throws ConversionException {
-          String operand = Type.STRING.convert(args[0], "'strip' operand");
-          return operand.trim();
-        }
-      };
+  private static BuiltinFunction strip = new BuiltinFunction("strip") {
+    public String invoke(String self) {
+      return self.trim();
+    }
+  };
 
   // slice operator
   @SkylarkSignature(name = "$slice", documented = false,
@@ -475,30 +401,22 @@
         @Param(name = "end", type = Integer.class, doc = "end position of the slice.")},
       doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.",
       useLocation = true, useEnvironment = true)
-  private static Function slice = new MixedModeFunction("$slice",
-      ImmutableList.of("this", "start", "end"), 3, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast, Environment env)
-        throws EvalException, ConversionException {
-      int left = Type.INTEGER.convert(args[1], "start operand");
-      int right = Type.INTEGER.convert(args[2], "end operand");
-
+  private static BuiltinFunction slice = new BuiltinFunction("$slice") {
+    public Object invoke(Object self, Integer left, Integer right,
+        Location loc, Environment env) throws EvalException, ConversionException {
       // Substring
-      if (args[0] instanceof String) {
-        String thiz = Type.STRING.convert(args[0], "substring operand");
-        return pythonSubstring(thiz, left, right, "");
+      if (self instanceof String) {
+        return pythonSubstring((String) self, left, right, "");
       }
 
       // List slice
-      List<Object> list = Type.OBJECT_LIST.convert(args[0], "list operand");
+      List<Object> list = Type.OBJECT_LIST.convert(self, "list operand");
       left = clampIndex(left, list.size());
       right = clampIndex(right, list.size());
-
-      List<Object> result = Lists.newArrayList();
-      for (int i = left; i < right; i++) {
-        result.add(list.get(i));
+      if (left > right) {
+        left = right;
       }
-      return convert(result, env, ast.getLocation());
+      return convert(list.subList(left, right), env, loc);
     }
   };
 
@@ -508,18 +426,16 @@
       mandatoryPositionals = {
         @Param(name = "self", type = HackHackEitherList.class, doc = "This list.")},
         useLocation = true, useEnvironment = true)
-  private static Function sorted = new MixedModeFunction("sorted",
-      ImmutableList.of("self"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast, Environment env)
+  private static BuiltinFunction sorted = new BuiltinFunction("sorted") {
+    public Object invoke(Object self, Location loc, Environment env)
         throws EvalException, ConversionException {
-      List<Object> self = Type.OBJECT_LIST.convert(args[0], "'sorted' operand");
+      List<Object> list = Type.OBJECT_LIST.convert(self, "'sorted' operand");
       try {
-        self = Ordering.from(EvalUtils.SKYLARK_COMPARATOR).sortedCopy(self);
+        list = Ordering.from(EvalUtils.SKYLARK_COMPARATOR).sortedCopy(list);
       } catch (EvalUtils.ComparisonException e) {
-        throw new EvalException(ast.getLocation(), e);
+        throw new EvalException(loc, e);
       }
-      return convert(self, env, ast.getLocation());
+      return convert(list, env, loc);
     }
   };
 
@@ -531,13 +447,10 @@
         @Param(name = "self", type = List.class, doc = "This list."),
         @Param(name = "item", type = Object.class, doc = "Item to add at the end.")},
         useLocation = true, useEnvironment = true)
-  private static Function append = new MixedModeFunction("append",
-      ImmutableList.of("this", "x"), 2, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException,
-        ConversionException {
-      List<Object> thiz = Type.OBJECT_LIST.convert(args[0], "'append' operand");
-      thiz.add(args[1]);
+  private static BuiltinFunction append = new BuiltinFunction("append") {
+    public Environment.NoneType invoke(List<Object> self, Object item,
+        Location loc, Environment env) throws EvalException, ConversionException {
+      self.add(item);
       return Environment.NONE;
     }
   };
@@ -550,14 +463,10 @@
         @Param(name = "self", type = List.class, doc = "This list."),
         @Param(name = "items", type = List.class, doc = "Items to add at the end.")},
         useLocation = true, useEnvironment = true)
-  private static Function extend = new MixedModeFunction("extend",
-          ImmutableList.of("this", "x"), 2, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException,
-        ConversionException {
-      List<Object> thiz = Type.OBJECT_LIST.convert(args[0], "'extend' operand");
-      List<Object> l = Type.OBJECT_LIST.convert(args[1], "'extend' argument");
-      thiz.addAll(l);
+  private static BuiltinFunction extend = new BuiltinFunction("extend") {
+    public Environment.NoneType invoke(List<Object> self, List<Object> items,
+        Location loc, Environment env) throws EvalException, ConversionException {
+      self.addAll(items);
       return Environment.NONE;
     }
   };
@@ -570,50 +479,43 @@
         @Param(name = "self", type = Object.class, doc = "This object."),
         @Param(name = "key", type = Object.class, doc = "The index or key to access.")},
       useLocation = true)
-  private static Function indexOperator = new MixedModeFunction("$index",
-      ImmutableList.of("this", "index"), 2, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException,
-        ConversionException {
-      Object collectionCandidate = args[0];
-      Object key = args[1];
+  private static BuiltinFunction indexOperator = new BuiltinFunction("$index") {
+    public Object invoke(Object self, Object key,
+        Location loc) throws EvalException, ConversionException {
+      if (self instanceof SkylarkList) {
+        SkylarkList list = (SkylarkList) self;
+        if (list.isEmpty()) {
+          throw new EvalException(loc, "List is empty");
+        }
+        int index = getListIndex(key, list.size(), loc);
+        return list.get(index);
 
-      if (collectionCandidate instanceof Map<?, ?>) {
-        Map<?, ?> dictionary = (Map<?, ?>) collectionCandidate;
+      } else if (self instanceof Map<?, ?>) {
+        Map<?, ?> dictionary = (Map<?, ?>) self;
         if (!dictionary.containsKey(key)) {
-          throw new EvalException(ast.getLocation(),
-              "Key " + EvalUtils.prettyPrintValue(key) + " not found in dictionary");
+          throw new EvalException(loc, String.format("Key %s not found in dictionary",
+                  EvalUtils.prettyPrintValue(key)));
         }
         return dictionary.get(key);
-      } else if (collectionCandidate instanceof List<?>) {
 
-        List<Object> list = Type.OBJECT_LIST.convert(collectionCandidate, "index operand");
-
-        if (!list.isEmpty()) {
-          int index = getListIndex(key, list.size(), ast);
-          return list.get(index);
+      } else if (self instanceof List<?>) {
+        List<Object> list = Type.OBJECT_LIST.convert(self, "index operand");
+        if (list.isEmpty()) {
+          throw new EvalException(loc, "List is empty");
         }
+        int index = getListIndex(key, list.size(), loc);
+        return list.get(index);
 
-        throw new EvalException(ast.getLocation(), "List is empty");
-      } else if (collectionCandidate instanceof SkylarkList) {
-        SkylarkList list = (SkylarkList) collectionCandidate;
-
-        if (!list.isEmpty()) {
-          int index = getListIndex(key, list.size(), ast);
-          return list.get(index);
-        }
-
-        throw new EvalException(ast.getLocation(), "List is empty");
-      } else if (collectionCandidate instanceof String) {
-        String str = (String) collectionCandidate;
-        int index = getListIndex(key, str.length(), ast);
+      } else if (self instanceof String) {
+        String str = (String) self;
+        int index = getListIndex(key, str.length(), loc);
         return str.substring(index, index + 1);
 
       } else {
         // TODO(bazel-team): This is dead code, get rid of it.
-        throw new EvalException(ast.getLocation(), String.format(
+        throw new EvalException(loc, String.format(
             "Unsupported datatype (%s) for indexing, only works for dict and list",
-            EvalUtils.getDataTypeName(collectionCandidate)));
+            EvalUtils.getDataTypeName(self)));
       }
     }
   };
@@ -625,13 +527,12 @@
           + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"c\", \"a\", \"b\"]</pre>\n",
       mandatoryPositionals = {@Param(name = "self", type = Map.class, doc = "This dict.")},
       useLocation = true, useEnvironment = true)
-  private static Function values = new NoArgFunction("values") {
-    @Override
-    public Object call(Object self, FuncallExpression ast, Environment env)
-        throws EvalException, InterruptedException {
+  private static BuiltinFunction values = new BuiltinFunction("values") {
+    public Object invoke(Map<?, ?> self,
+        Location loc, Environment env) throws EvalException, ConversionException {
       // Use a TreeMap to ensure consistent ordering.
-      Map<?, ?> dict = new TreeMap<>((Map<?, ?>) self);
-      return convert(dict.values(), env, ast.getLocation());
+      Map<?, ?> dict = new TreeMap<>(self);
+      return convert(dict.values(), env, loc);
     }
   };
 
@@ -644,20 +545,19 @@
       mandatoryPositionals = {
         @Param(name = "self", type = Map.class, doc = "This dict.")},
       useLocation = true, useEnvironment = true)
-  private static Function items = new NoArgFunction("items") {
-    @Override
-    public Object call(Object self, FuncallExpression ast, Environment env)
-        throws EvalException, InterruptedException {
+  private static BuiltinFunction items = new BuiltinFunction("items") {
+    public Object invoke(Map<?, ?> self,
+        Location loc, Environment env) throws EvalException, ConversionException {
       // Use a TreeMap to ensure consistent ordering.
-      Map<?, ?> dict = new TreeMap<>((Map<?, ?>) self);
+      Map<?, ?> dict = new TreeMap<>(self);
       List<Object> list = Lists.newArrayListWithCapacity(dict.size());
       for (Map.Entry<?, ?> entries : dict.entrySet()) {
         List<?> item = ImmutableList.of(entries.getKey(), entries.getValue());
         list.add(env.isSkylarkEnabled() ? SkylarkList.tuple(item) : item);
       }
-      return convert(list, env, ast.getLocation());
-    }
-  };
+        return convert(list, env, loc);
+      }
+    };
 
   @SkylarkSignature(name = "keys", objectType = DictModule.class,
       returnType = HackHackEitherList.class,
@@ -667,14 +567,12 @@
       mandatoryPositionals = {
         @Param(name = "self", type = Map.class, doc = "This dict.")},
       useLocation = true, useEnvironment = true)
-  private static Function keys = new NoArgFunction("keys") {
-    @Override
-    @SuppressWarnings("unchecked") // Skylark will only call this on a dict; and
-    // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception.
-    public Object call(Object self, FuncallExpression ast, Environment env)
-        throws EvalException, InterruptedException {
-      Map<Comparable<?>, Object> dict = (Map<Comparable<?>, Object>) self;
-      return convert(Ordering.natural().sortedCopy(dict.keySet()), env, ast.getLocation());
+  // Skylark will only call this on a dict; and
+  // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception.
+  private static BuiltinFunction keys = new BuiltinFunction("keys") {
+    public Object invoke(Map<Comparable<?>, ?> dict,
+        Location loc, Environment env) throws EvalException {
+      return convert(Ordering.natural().sortedCopy(dict.keySet()), env, loc);
     }
   };
 
@@ -688,19 +586,12 @@
       optionalPositionals = {
         @Param(name = "default", defaultValue = "None",
             doc = "The default value to use (instead of None) if the key is not found.")})
-  private static Function get =
-      new MixedModeFunction("get", ImmutableList.of("this", "key", "default"), 2, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
-      Map<?, ?> dict = (Map<?, ?>) args[0];
-      Object key = args[1];
-      if (dict.containsKey(key)) {
-        return dict.get(key);
+  private static BuiltinFunction get = new BuiltinFunction("get") {
+    public Object invoke(Map<?, ?> self, Object key, Object defaultValue) {
+      if (self.containsKey(key)) {
+        return self.get(key);
       }
-      if (args[2] == null) {
-        return Environment.NONE;
-      }
-      return args[2];
+      return defaultValue;
     }
   };
 
@@ -716,13 +607,12 @@
   }
 
   // unary minus
-  @SkylarkSignature(name = "-", documented = false, doc = "Unary minus operator.",
+  @SkylarkSignature(name = "-", returnType = Integer.class,
+      documented = false, doc = "Unary minus operator.",
       mandatoryPositionals = {
         @Param(name = "num", type = Integer.class, doc = "The number to negate.")})
-  private static Function minus = new MixedModeFunction("-", ImmutableList.of("this"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
-      int num = Type.INTEGER.convert(args[0], "'unary minus' argument");
+  private static BuiltinFunction minus = new BuiltinFunction("-") {
+    public Integer invoke(Integer num) throws ConversionException {
       return -num;
     }
   };
@@ -734,12 +624,9 @@
         + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [2, 4, 5]</pre>",
       mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")},
       useLocation = true)
-    private static Function list = new MixedModeFunction("list",
-        ImmutableList.of("list"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException {
-      Location loc = ast.getLocation();
-      return SkylarkList.list(EvalUtils.toCollection(args[0], loc), loc);
+  private static BuiltinFunction list = new BuiltinFunction("list") {
+    public SkylarkList invoke(Object x, Location loc) throws EvalException {
+      return SkylarkList.list(EvalUtils.toCollection(x, loc), loc);
     }
   };
 
@@ -747,16 +634,11 @@
       "Returns the length of a string, list, tuple, set, or dictionary.",
       mandatoryPositionals = {@Param(name = "x", doc = "The object to check length of.")},
       useLocation = true)
-  private static Function len = new MixedModeFunction("len",
-        ImmutableList.of("list"), 1, false) {
-
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException {
-      Object arg = args[0];
-      int l = EvalUtils.size(arg);
+  private static BuiltinFunction len = new BuiltinFunction("len") {
+    public Integer invoke(Object x, Location loc) throws EvalException {
+      int l = EvalUtils.size(x);
       if (l == -1) {
-        throw new EvalException(ast.getLocation(),
-            EvalUtils.getDataTypeName(arg) + " is not iterable");
+        throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable");
       }
       return l;
     }
@@ -765,10 +647,9 @@
   @SkylarkSignature(name = "str", returnType = String.class, doc =
       "Converts any object to string. This is useful for debugging.",
       mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")})
-    private static Function str = new MixedModeFunction("str", ImmutableList.of("this"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException {
-      return EvalUtils.printValue(args[0]);
+  private static BuiltinFunction str = new BuiltinFunction("str") {
+    public String invoke(Object x) throws EvalException {
+      return EvalUtils.printValue(x);
     }
   };
 
@@ -778,11 +659,9 @@
       + "empty collection. Otherwise, it returns True. Similarly to Python <code>bool</code> "
       + "is also a type.",
       mandatoryPositionals = {@Param(name = "x", doc = "The variable to convert.")})
-  private static Function bool = new MixedModeFunction("bool",
-          ImmutableList.of("this"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException {
-      return EvalUtils.toBoolean(args[0]);
+  private static BuiltinFunction bool = new BuiltinFunction("bool") {
+    public Boolean invoke(Object x) throws EvalException {
+      return EvalUtils.toBoolean(x);
     }
   };
 
@@ -792,20 +671,16 @@
       mandatoryPositionals = {
         @Param(name = "x", type = String.class, doc = "The string to convert.")},
       useLocation = true)
-  private static Function int_ =
-      new MixedModeFunction("int", ImmutableList.of("x"), 1, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast)
-            throws EvalException, ConversionException {
-          String str = Type.STRING.convert(args[0], "'int' operand");
-          try {
-            return Integer.parseInt(str);
-          } catch (NumberFormatException e) {
-            throw new EvalException(ast.getLocation(),
-                "invalid literal for int(): " + EvalUtils.prettyPrintValue(str));
-          }
-        }
-      };
+  private static BuiltinFunction int_ = new BuiltinFunction("int") {
+    public Integer invoke(String x, Location loc) throws EvalException {
+      try {
+        return Integer.parseInt(x);
+      } catch (NumberFormatException e) {
+        throw new EvalException(loc,
+            "invalid literal for int(): " + EvalUtils.prettyPrintValue(x));
+      }
+    }
+  };
 
   @SkylarkSignature(name = "struct", returnType = SkylarkClassObject.class, doc =
       "Creates an immutable struct using the keyword arguments as fields. It is used to group "
@@ -815,14 +690,11 @@
       extraKeywords = {
         @Param(name = "kwarg", doc = "the struct fields")},
       useLocation = true)
-  private static Function struct = new AbstractFunction("struct") {
-    @Override
-    public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast,
-        Environment env) throws EvalException, InterruptedException {
-      if (!args.isEmpty()) {
-        throw new EvalException(ast.getLocation(), "struct only supports keyword arguments");
-      }
-      return new SkylarkClassObject(kwargs, ast.getLocation());
+  private static BuiltinFunction struct = new BuiltinFunction("struct") {
+      @SuppressWarnings("unchecked")
+    public SkylarkClassObject invoke(Map<String, Object> kwargs, Location loc)
+        throws EvalException, InterruptedException {
+      return new SkylarkClassObject(kwargs, loc);
     }
   };
 
@@ -841,16 +713,10 @@
             + "possible values are: <code>stable</code> (default), <code>compile</code>, "
             + "<code>link</code> or <code>naive_link</code>.")},
       useLocation = true)
-  private static final Function set =
-    new MixedModeFunction("set", ImmutableList.of("items", "order"), 0, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException,
-        ConversionException {
-      Order order = SkylarkNestedSet.parseOrder((String) args[1], ast.getLocation());
-      if (args[0] == null) {
-        return new SkylarkNestedSet(order, SkylarkList.EMPTY_LIST, ast.getLocation());
-      }
-      return new SkylarkNestedSet(order, args[0], ast.getLocation());
+  private static final BuiltinFunction set = new BuiltinFunction("set") {
+    public SkylarkNestedSet invoke(Object items, String order,
+        Location loc) throws EvalException, ConversionException {
+      return new SkylarkNestedSet(SkylarkNestedSet.parseOrder(order, loc), items, loc);
     }
   };
 
@@ -860,21 +726,16 @@
           + "enumerate([24, 21, 84]) == [[0, 24], [1, 21], [2, 84]]</pre>\n",
       mandatoryPositionals = {@Param(name = "list", type = SkylarkList.class, doc = "input list")},
       useLocation = true)
-  private static Function enumerate = new MixedModeFunction("enumerate",
-      ImmutableList.of("list"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException,
-        ConversionException {
-      // Note that enumerate is only available in Skylark.
-      SkylarkList input = cast(
-          args[0], SkylarkList.class, "enumerate operand", ast.getLocation());
-      List<SkylarkList> result = Lists.newArrayList();
+  private static BuiltinFunction enumerate = new BuiltinFunction("enumerate") {
+    public SkylarkList invoke(SkylarkList input, Location loc)
+        throws EvalException, ConversionException, InterruptedException {
       int count = 0;
+      List<SkylarkList> result = Lists.newArrayList();
       for (Object obj : input) {
-        result.add(SkylarkList.tuple(Lists.newArrayList(count, obj)));
+        result.add(SkylarkList.tuple(count, obj));
         count++;
       }
-      return SkylarkList.list(result, ast.getLocation());
+      return SkylarkList.list(result, loc);
     }
   };
 
@@ -886,34 +747,31 @@
           + "range(3, 9, 2) == [3, 5, 7]\n"
           + "range(3, 0, -1) == [3, 2, 1]</pre>",
       mandatoryPositionals = {
-        @Param(name = "start", type = Integer.class,
-            doc = "Value of the first element if stop is provided, "
+        @Param(name = "start_or_stop", type = Integer.class,
+            doc = "Value of the start element if stop is provided, "
             + "otherwise value of stop and the actual start is 0"),
       },
       optionalPositionals = {
-        @Param(name = "stop", type = Integer.class, noneable = true, defaultValue = "None",
+        @Param(name = "stop_or_none", type = Integer.class, noneable = true, 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."),
         @Param(name = "step", type = Integer.class, defaultValue = "1",
             doc = "The increment (default is 1). It may be negative.")},
       useLocation = true)
-  private static final Function range =
-    new MixedModeFunction("range", ImmutableList.of("start", "stop", "step"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException,
-        ConversionException {
+  private static final BuiltinFunction range = new BuiltinFunction("range") {
+    public SkylarkList invoke(Integer startOrStop, Object stopOrNone, Integer step, Location loc)
+        throws EvalException, ConversionException, InterruptedException {
       int start;
       int stop;
-      if (args[1] == null) {
+      if (stopOrNone == Environment.NONE) {
         start = 0;
-        stop = Type.INTEGER.convert(args[0], "stop");
+        stop = startOrStop;
       } else {
-        start = Type.INTEGER.convert(args[0], "start");
-        stop = Type.INTEGER.convert(args[1], "stop");
+        start = startOrStop;
+        stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'");
       }
-      int step = args[2] == null ? 1 : Type.INTEGER.convert(args[2], "step");
       if (step == 0) {
-        throw new EvalException(ast.getLocation(), "step cannot be 0");
+        throw new EvalException(loc, "step cannot be 0");
       }
       List<Integer> result = Lists.newArrayList();
       if (step > 0) {
@@ -939,19 +797,11 @@
       doc = "Creates a SelectorValue from the dict parameter.",
       mandatoryPositionals = {
         @Param(name = "x", type = Map.class, doc = "The parameter to convert.")})
-  private static final Function select = new MixedModeFunction("select",
-      ImmutableList.of("x"), 1, false) {
-      @Override
-      public Object call(Object[] args, FuncallExpression ast)
-          throws EvalException, ConversionException {
-        Object dict = args[0];
-        if (!(dict instanceof Map<?, ?>)) {
-          throw new EvalException(ast.getLocation(),
-              "select({...}) argument isn't a dictionary");
-        }
-        return SelectorList.of(new SelectorValue((Map<?, ?>) dict));
-      }
-    };
+  private static final BuiltinFunction select = new BuiltinFunction("select") {
+    public Object invoke(Map<?, ?> dict) throws EvalException, InterruptedException {
+      return SelectorList.of(new SelectorValue(dict));
+    }
+  };
 
   /**
    * Returns true if the object has a field of the given name, otherwise false.
@@ -964,18 +814,12 @@
         @Param(name = "object", doc = "The object to check."),
         @Param(name = "name", type = String.class, doc = "The name of the field.")},
       useLocation = true, useEnvironment = true)
-  private static final Function hasattr =
-      new MixedModeFunction("hasattr", ImmutableList.of("object", "name"), 2, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast, Environment env)
-        throws EvalException, ConversionException {
-      Object obj = args[0];
-      String name = cast(args[1], String.class, "name", ast.getLocation());
-
+  private static final BuiltinFunction hasattr = new BuiltinFunction("hasattr") {
+    public Boolean invoke(Object obj, String name,
+        Location loc, Environment env) throws EvalException, ConversionException {
       if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) {
         return true;
       }
-
       if (env.getFunctionNames(obj.getClass()).contains(name)) {
         return true;
       }
@@ -984,7 +828,7 @@
         return FuncallExpression.getMethodNames(obj.getClass()).contains(name);
       } catch (ExecutionException e) {
         // This shouldn't happen
-        throw new EvalException(ast.getLocation(), e.getMessage());
+        throw new EvalException(loc, e.getMessage());
       }
     }
   };
@@ -1004,20 +848,15 @@
             doc = "The default value to return in case the struct "
             + "doesn't have a field of the given name.")},
       useLocation = true)
-  private static final Function getattr = new MixedModeFunction(
-      "getattr", ImmutableList.of("object", "name", "default"), 2, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast, Environment env)
-        throws EvalException {
-      Object obj = args[0];
-      String name = cast(args[1], String.class, "name", ast.getLocation());
-      Object result = DotExpression.eval(obj, name, ast.getLocation());
+  private static final BuiltinFunction getattr = new BuiltinFunction("getattr") {
+    public Object invoke(Object obj, String name, Object defaultValue,
+        Location loc) throws EvalException, ConversionException {
+      Object result = DotExpression.eval(obj, name, loc);
       if (result == null) {
-        if (args[2] != null) {
-          return args[2];
+        if (defaultValue != Environment.NONE) {
+          return defaultValue;
         } else {
-          throw new EvalException(ast.getLocation(),
-              String.format("Object of type '%s' has no field %s",
+          throw new EvalException(loc, String.format("Object of type '%s' has no field %s",
                   EvalUtils.getDataTypeName(obj), EvalUtils.prettyPrintValue(name)));
         }
       }
@@ -1030,23 +869,20 @@
           + "methods of the parameter object.",
       mandatoryPositionals = {@Param(name = "object", doc = "The object to check.")},
       useLocation = true, useEnvironment = true)
-  private static final Function dir = new MixedModeFunction(
-      "dir", ImmutableList.of("object"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast, Environment env)
-        throws EvalException {
-      Object obj = args[0];
+  private static final BuiltinFunction dir = new BuiltinFunction("dir") {
+    public SkylarkList invoke(Object object,
+        Location loc, Environment env) throws EvalException, ConversionException {
       // Order the fields alphabetically.
       Set<String> fields = new TreeSet<>();
-      if (obj instanceof ClassObject) {
-        fields.addAll(((ClassObject) obj).getKeys());
+      if (object instanceof ClassObject) {
+        fields.addAll(((ClassObject) object).getKeys());
       }
-      fields.addAll(env.getFunctionNames(obj.getClass()));
+      fields.addAll(env.getFunctionNames(object.getClass()));
       try {
-        fields.addAll(FuncallExpression.getMethodNames(obj.getClass()));
+        fields.addAll(FuncallExpression.getMethodNames(object.getClass()));
       } catch (ExecutionException e) {
         // This shouldn't happen
-        throw new EvalException(ast.getLocation(), e.getMessage());
+        throw new EvalException(loc, e.getMessage());
       }
       return SkylarkList.list(fields, String.class);
     }
@@ -1055,12 +891,10 @@
   @SkylarkSignature(name = "type", returnType = String.class,
       doc = "Returns the type name of its argument.",
       mandatoryPositionals = {@Param(name = "object", doc = "The object to check type of.")})
-  private static final Function type = new MixedModeFunction("type",
-      ImmutableList.of("object"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast) throws EvalException {
+  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(args[0], false);
+      return EvalUtils.getDataTypeName(object, false);
     }
   };
 
@@ -1074,17 +908,13 @@
             defaultValue = "None",
             doc = "The name of the attribute that caused the error")},
       useLocation = true)
-  private static final Function fail = new MixedModeFunction(
-      "fail", ImmutableList.of("msg", "attr"), 1, false) {
-    @Override
-    public Object call(Object[] args, FuncallExpression ast, Environment env)
-        throws EvalException {
-      String msg = cast(args[0], String.class, "msg", ast.getLocation());
-      if (args[1] != null) {
-        msg = "attribute " + cast(args[1], String.class, "attr", ast.getLocation())
-            + ": " + msg;
+  private static final BuiltinFunction fail = new BuiltinFunction("fail") {
+    public Environment.NoneType invoke(String msg, Object attr,
+        Location loc) throws EvalException, ConversionException {
+      if (attr != Environment.NONE) {
+        msg = String.format("attribute %s: %s", attr, msg);
       }
-      throw new EvalException(ast.getLocation(), msg);
+      throw new EvalException(loc, msg);
     }
   };
 
@@ -1096,30 +926,16 @@
       // NB: as compared to Python3, we're missing optional named-only arguments 'end' and 'file'
       extraPositionals = {@Param(name = "args", doc = "The objects to print.")},
       useLocation = true, useEnvironment = true)
-  private static final Function print = new AbstractFunction("print") {
-    @Override
-    public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast,
-        Environment env) throws EvalException, InterruptedException {
-      String sep = " ";
-      int count = 0;
-      if (kwargs.containsKey("sep")) {
-        sep = cast(kwargs.get("sep"), String.class, "sep", ast.getLocation());
-        count = 1;
-      }
-      if (kwargs.size() > count) {
-        kwargs = new HashMap<>(kwargs);
-        kwargs.remove("sep");
-        List<String> bad = Ordering.natural().sortedCopy(kwargs.keySet());
-        throw new EvalException(ast.getLocation(), "unexpected keywords: '" + bad + "'");
-      }
-      String msg = Joiner.on(sep).join(Iterables.transform(args,
-          new com.google.common.base.Function<Object, String>() {
-        @Override
-        public String apply(Object input) {
-          return EvalUtils.printValue(input);
-        }
-      }));
-      ((SkylarkEnvironment) env).handleEvent(Event.warn(ast.getLocation(), msg));
+  private static final BuiltinFunction print = new BuiltinFunction("print") {
+    public Environment.NoneType invoke(String sep, SkylarkList starargs,
+        Location loc, SkylarkEnvironment env) throws EvalException {
+      String msg = Joiner.on(sep).join(Iterables.transform(starargs,
+              new com.google.common.base.Function<Object, String>() {
+                @Override
+                public String apply(Object input) {
+                  return EvalUtils.printValue(input);
+                }}));
+      env.handleEvent(Event.warn(loc, msg));
       return Environment.NONE;
     }
   };
@@ -1136,13 +952,12 @@
           + "zip([1, 2], [3, 4, 5])  # == [(1, 3), (2, 4)]</pre>",
       extraPositionals = {@Param(name = "args", doc = "lists to zip")},
       returnType = SkylarkList.class, useLocation = true)
-  private static final Function zip = new AbstractFunction("zip") {
-    @Override
-    public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast,
-        Environment env) throws EvalException, InterruptedException {
+  private static final BuiltinFunction zip = new BuiltinFunction("zip") {
+    public SkylarkList invoke(SkylarkList args, Location loc)
+        throws EvalException, InterruptedException {
       Iterator<?>[] iterators = new Iterator<?>[args.size()];
       for (int i = 0; i < args.size(); i++) {
-        iterators[i] = EvalUtils.toIterable(args.get(i), ast.getLocation()).iterator();
+        iterators[i] = EvalUtils.toIterable(args.get(i), loc).iterator();
       }
       List<SkylarkList> result = new ArrayList<SkylarkList>();
       boolean allHasNext;
@@ -1160,7 +975,7 @@
           result.add(SkylarkList.tuple(elem));
         }
       } while (allHasNext);
-      return SkylarkList.list(result, ast.getLocation());
+      return SkylarkList.list(result, loc);
     }
   };
 
@@ -1209,18 +1024,21 @@
       + "</pre>")
   public static final class DictModule {}
 
-  public static final List<Function> stringFunctions = ImmutableList.of(
+  public static final List<Function> stringFunctions = ImmutableList.<Function>of(
       count, endswith, find, index, join, lower, replace, rfind,
       rindex, slice, split, startswith, strip, upper);
 
-  public static final List<Function> listPureFunctions = ImmutableList.of(slice);
+  public static final List<Function> listPureFunctions = ImmutableList.<Function>of(
+      slice);
 
-  public static final List<Function> listFunctions = ImmutableList.of(append, extend);
+  public static final List<Function> listFunctions = ImmutableList.<Function>of(
+      append, extend);
 
-  public static final List<Function> dictFunctions = ImmutableList.of(items, get, keys, values);
+  public static final List<Function> dictFunctions = ImmutableList.<Function>of(
+      items, get, keys, values);
 
-  private static final List<Function> pureGlobalFunctions =
-      ImmutableList.of(bool, int_, len, minus, select, sorted, str);
+  private static final List<Function> pureGlobalFunctions = ImmutableList.<Function>of(
+      bool, int_, len, minus, select, sorted, str);
 
   private static final List<Function> skylarkGlobalFunctions = ImmutableList
       .<Function>builder()
@@ -1273,4 +1091,8 @@
       builtIn.add(function.getName());
     }
   }
+
+  static {
+    SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class);
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index ac669e6..b4d4a6d 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -30,21 +30,27 @@
 import com.google.devtools.build.lib.packages.GlobCache.BadGlobException;
 import com.google.devtools.build.lib.packages.License.DistributionType;
 import com.google.devtools.build.lib.packages.Type.ConversionException;
-import com.google.devtools.build.lib.syntax.AbstractFunction;
 import com.google.devtools.build.lib.syntax.AssignmentStatement;
+import com.google.devtools.build.lib.syntax.BaseFunction;
 import com.google.devtools.build.lib.syntax.BuildFileAST;
+import com.google.devtools.build.lib.syntax.BuiltinFunction;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.Environment.NoSuchVariableException;
 import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.Expression;
 import com.google.devtools.build.lib.syntax.FuncallExpression;
 import com.google.devtools.build.lib.syntax.Function;
+import com.google.devtools.build.lib.syntax.FunctionSignature;
 import com.google.devtools.build.lib.syntax.GlobList;
 import com.google.devtools.build.lib.syntax.Ident;
 import com.google.devtools.build.lib.syntax.Label;
-import com.google.devtools.build.lib.syntax.MixedModeFunction;
 import com.google.devtools.build.lib.syntax.ParserInputSource;
 import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
+import com.google.devtools.build.lib.syntax.SkylarkSignature;
+import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
+import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
+import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList;
 import com.google.devtools.build.lib.syntax.Statement;
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.vfs.Path;
@@ -52,7 +58,6 @@
 import com.google.devtools.build.lib.vfs.UnixGlob;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -149,8 +154,6 @@
     Iterable<PackageArgument<?>> getPackageArguments();
   }
 
-  private static final int EXCLUDE_DIR_DEFAULT = 1;
-
   private static class DefaultVisibility extends PackageArgument<List<Label>> {
     private DefaultVisibility() {
       super("default_visibility", Type.LABEL_LIST);
@@ -452,21 +455,38 @@
    * @param async if true, start globs in the background but don't block on their completion.
    *        Only use this for heuristic preloading.
    */
-  private static Function newGlobFunction(
-      final PackageContext originalContext, final boolean async) {
-    List<String> params = ImmutableList.of("include", "exclude", "exclude_directories");
-    return new MixedModeFunction("glob", params, 1, false) {
-        @Override
-        public Object call(Object[] namedArguments, FuncallExpression ast, Environment env)
+  @SkylarkSignature(name = "glob", objectType = Object.class, returnType = GlobList.class,
+      doc = "Returns a list of files that match glob search pattern",
+      mandatoryPositionals = {
+        @Param(name = "include", type = HackHackEitherList.class, generic1 = String.class,
+            doc = "a list of strings specifying patterns of files to include.")},
+      optionalPositionals = {
+        @Param(name = "exclude", type = HackHackEitherList.class, generic1 = String.class,
+            defaultValue = "[]",
+            doc = "a list of strings specifying patterns of files to exclude."),
+        // TODO(bazel-team): migrate all existing code to use boolean instead?
+        @Param(name = "exclude_directories", type = Integer.class, defaultValue = "1",
+            doc = "a integer that if non-zero indicates directories should not be matched.")},
+      documented = false, useAst = true, useEnvironment = true)
+  private static final BuiltinFunction.Factory newGlobFunction =
+      new BuiltinFunction.Factory("glob") {
+        public BuiltinFunction create(final PackageContext originalContext, final boolean async) {
+          return new BuiltinFunction("glob", this) {
+            public GlobList<String> invoke(
+                Object include, Object exclude, Integer excludeDirectories,
+                FuncallExpression ast, Environment env)
                 throws EvalException, ConversionException, InterruptedException {
-          return callGlob(originalContext, async, ast, env, namedArguments);
+              return callGlob(
+                  originalContext, async, include, exclude, excludeDirectories != 0, ast, env);
+            }
+          };
         }
       };
-  }
 
-  static GlobList<String> callGlob(@Nullable PackageContext originalContext, boolean async,
-      FuncallExpression ast, Environment env, Object[] namedArguments)
-          throws EvalException, ConversionException, InterruptedException {
+  protected static GlobList<String> callGlob(@Nullable PackageContext originalContext,
+      boolean async, Object include, Object exclude, boolean excludeDirs,
+      FuncallExpression ast, Environment env)
+      throws EvalException, ConversionException, InterruptedException {
     // Skylark build extensions need to get the PackageContext from the Environment;
     // async glob functions cannot do the same because the Environment is not thread safe.
     PackageContext context;
@@ -477,23 +497,18 @@
       context = originalContext;
     }
 
-    List<String> includes = Type.STRING_LIST.convert(namedArguments[0], "'glob' argument");
-    List<String> excludes = namedArguments[1] == null
-        ? Collections.<String>emptyList()
-        : Type.STRING_LIST.convert(namedArguments[1], "'glob' argument");
-    int excludeDirs = namedArguments[2] == null
-      ? EXCLUDE_DIR_DEFAULT
-      : Type.INTEGER.convert(namedArguments[2], "'glob' argument");
+    List<String> includes = Type.STRING_LIST.convert(include, "'glob' argument");
+    List<String> excludes = Type.STRING_LIST.convert(exclude, "'glob' argument");
 
     if (async) {
       try {
-        context.globber.runAsync(includes, excludes, excludeDirs != 0);
+        context.globber.runAsync(includes, excludes, excludeDirs);
       } catch (GlobCache.BadGlobException e) {
         // Ignore: errors will appear during the actual evaluation of the package.
       }
       return GlobList.captureResults(includes, excludes, ImmutableList.<String>of());
     } else {
-      return handleGlob(includes, excludes, excludeDirs != 0, context, ast);
+      return handleGlob(includes, excludes, excludeDirs, context, ast);
     }
   }
 
@@ -531,44 +546,57 @@
    * seen by the parser, because the presence of "subinclude" triggers
    * preprocessing.)
    */
-  private static Function newMockSubincludeFunction(final PackageContext context) {
-    return new MixedModeFunction("mocksubinclude", ImmutableList.of("label", "path"), 2, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast)
-            throws ConversionException {
-          Label label = Type.LABEL.convert(args[0], "'mocksubinclude' argument",
-                                           context.pkgBuilder.getBuildFileLabel());
-          String pathString = Type.STRING.convert(args[1], "'mocksubinclude' argument");
-          Path path = pathString.isEmpty()
-              ? null
-              : context.pkgBuilder.getFilename().getRelative(pathString);
-          // A subinclude within a package counts as a file declaration.
-          if (label.getPackageIdentifier().equals(context.pkgBuilder.getPackageIdentifier())) {
-            Location location = ast.getLocation();
-            if (location == null) {
-              location = Location.fromFile(context.pkgBuilder.getFilename());
-            }
-            context.pkgBuilder.createInputFileMaybe(label, location);
-          }
+  @SkylarkSignature(name = "mocksubinclude", returnType = Environment.NoneType.class,
+      doc = "implement the mocksubinclude function emitted by the PythonPreprocessor",
+      mandatoryPositionals = {
+        @Param(name = "label", type = Object.class,
+            doc = "a label designator."),
+        @Param(name = "path", type = String.class,
+            doc = "a path.")},
+      documented = false, useLocation = true)
+  protected static final BuiltinFunction.Factory newMockSubincludeFunction =
+      new BuiltinFunction.Factory("mocksubinclude") {
+        public BuiltinFunction create(final PackageContext context) {
+          return new BuiltinFunction("mocksubinclude", this) {
+            public Environment.NoneType invoke(Object labelO, String pathString,
+                Location loc) throws ConversionException {
+              Label label = Type.LABEL.convert(labelO, "'mocksubinclude' argument",
+                  context.pkgBuilder.getBuildFileLabel());
+              Path path = pathString.isEmpty()
+                  ? null : context.pkgBuilder.getFilename().getRelative(pathString);
+              // A subinclude within a package counts as a file declaration.
+              if (label.getPackageIdentifier().equals(context.pkgBuilder.getPackageIdentifier())) {
+                if (loc == null) {
+                  loc = Location.fromFile(context.pkgBuilder.getFilename());
+                }
+                context.pkgBuilder.createInputFileMaybe(label, loc);
+              }
 
-          context.pkgBuilder.addSubinclude(label, path);
-          return Environment.NONE;
+              context.pkgBuilder.addSubinclude(label, path);
+              return Environment.NONE;
+            }
+          };
         }
       };
-  }
 
   /**
    * Fake function: subinclude calls are ignored
    * They will disappear after the Python preprocessing.
    */
-  private static Function newSubincludeFunction() {
-    return new MixedModeFunction("subinclude", ImmutableList.of("file"), 1, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast) {
-          return Environment.NONE;
+  @SkylarkSignature(name = "subinclude", returnType = Environment.NoneType.class,
+      mandatoryPositionals = {@Param(name = "file", doc = "(ignored)")},
+      doc = "fake function to skip over subinclude statements",
+      documented = false)
+  private static BuiltinFunction.Factory newSubincludeFunction =
+      new BuiltinFunction.Factory("subinclude") {
+        public BuiltinFunction create() {
+          return new BuiltinFunction("subinclude", this) {
+            public Environment.NoneType invoke(Object file) {
+              return Environment.NONE;
+            }
+          };
         }
       };
-  }
 
   /**
    * Returns a function value implementing "environment_group" in the specified package context.
@@ -585,64 +613,90 @@
    * <p>Where ":env1", "env2", ... are all environment rules declared in the same package. All
    * parameters are mandatory.
    */
-  private static Function newEnvironmentGroupFunction(final PackageContext context) {
-    List<String> params = ImmutableList.of("name", "environments", "defaults");
-    return new MixedModeFunction("environment_group", params, params.size(), true) {
-        @Override
-        public Object call(Object[] namedArgs, FuncallExpression ast)
-            throws EvalException, ConversionException {
-          Preconditions.checkState(namedArgs[0] != null);
-          String name = Type.STRING.convert(namedArgs[0], "'environment_group' argument");
-          Preconditions.checkState(namedArgs[1] != null);
-          List<Label> environments = Type.LABEL_LIST.convert(
-              namedArgs[1], "'environment_group argument'", context.pkgBuilder.getBuildFileLabel());
-          Preconditions.checkState(namedArgs[2] != null);
-          List<Label> defaults = Type.LABEL_LIST.convert(
-              namedArgs[2], "'environment_group argument'", context.pkgBuilder.getBuildFileLabel());
+  @SkylarkSignature(name = "environment_group", returnType = Environment.NoneType.class,
+      doc = "Defines a cc_library, by wrapping around the usual library "
+      + "and also defining a headers target.",
+      mandatoryNamedOnly = {
+        @Param(name = "name", type = String.class,
+            doc = "The name of the rule."),
+        // Both parameter below are lists of label designators
+        @Param(name = "environments", type = HackHackEitherList.class, generic1 = Object.class,
+            doc = "A list of Labels for the environments to be grouped, from the same package."),
+        @Param(name = "defaults", type = HackHackEitherList.class, generic1 = Object.class,
+            doc = "A list of Labels.")}, // TODO(bazel-team): document what that is
+      documented = false, useLocation = true)
+  protected static final BuiltinFunction.Factory newEnvironmentGroupFunction =
+      new BuiltinFunction.Factory("environment_group") {
+        public BuiltinFunction create(final PackageContext context) {
+          return new BuiltinFunction("environment_group", this) {
+            public Environment.NoneType invoke(String name, Object environmentsO, Object defaultsO,
+                Location loc) throws EvalException, ConversionException {
+              List<Label> environments = Type.LABEL_LIST.convert(environmentsO,
+                  "'environment_group argument'", context.pkgBuilder.getBuildFileLabel());
+              List<Label> defaults = Type.LABEL_LIST.convert(defaultsO,
+                  "'environment_group argument'", context.pkgBuilder.getBuildFileLabel());
 
-          try {
-            context.pkgBuilder.addEnvironmentGroup(name, environments, defaults,
-                context.eventHandler, ast.getLocation());
-            return Environment.NONE;
-          } catch (Label.SyntaxException e) {
-            throw new EvalException(ast.getLocation(),
-                "environment group has invalid name: " + name + ": " + e.getMessage());
-          } catch (Package.NameConflictException e) {
-            throw new EvalException(ast.getLocation(), e.getMessage());
-          }
+              try {
+                context.pkgBuilder.addEnvironmentGroup(name, environments, defaults,
+                    context.eventHandler, loc);
+                return Environment.NONE;
+              } catch (Label.SyntaxException e) {
+                throw new EvalException(loc,
+                    "environment group has invalid name: " + name + ": " + e.getMessage());
+              } catch (Package.NameConflictException e) {
+                throw new EvalException(loc, e.getMessage());
+              }
+            }
+          };
         }
       };
-  }
 
   /**
    * Returns a function-value implementing "exports_files" in the specified
    * package context.
    */
-  private static Function newExportsFilesFunction() {
-    List<String> params = ImmutableList.of("srcs", "visibility", "licenses");
-    return new MixedModeFunction("exports_files", params, 1, false) {
-      @Override
-      public Object call(Object[] namedArgs, FuncallExpression ast, Environment env)
-          throws EvalException, ConversionException {
-        return callExportsFiles(ast, env, namedArgs);
-      }
-    };
-  }
+  @SkylarkSignature(name = "exports_files", returnType = Environment.NoneType.class,
+      doc = "Declare a set of files as exported",
+      mandatoryPositionals = {
+        @Param(name = "srcs", type = HackHackEitherList.class, generic1 = String.class,
+            doc = "A list of strings, the names of the files to export.")},
+      optionalPositionals = {
+        // TODO(blaze-team): make it possible to express a list of label designators,
+        // i.e. a java List or Skylark list of Label or String.
+        @Param(name = "visibility", type = HackHackEitherList.class, noneable = true,
+            defaultValue = "None",
+            doc = "A list of Labels specifying the visibility of the exported files "
+            + "(defaults to public)"),
+        @Param(name = "licenses", type = HackHackEitherList.class, generic1 = String.class,
+            noneable = true, defaultValue = "None",
+            doc = "A list of strings specifying the licenses used in the exported code.")},
+      documented = false, useAst = true, useEnvironment = true)
+  protected static final BuiltinFunction.Factory newExportsFilesFunction =
+      new BuiltinFunction.Factory("exports_files") {
+        public BuiltinFunction create () {
+          return new BuiltinFunction("exports_files", this) {
+            public Environment.NoneType invoke(Object srcs, Object visibility, Object licenses,
+                FuncallExpression ast, Environment env)
+                throws EvalException, ConversionException {
+              return callExportsFiles(srcs, visibility, licenses, ast, env);
+            }
+          };
+        }
+      };
 
-  static Object callExportsFiles(FuncallExpression ast, Environment env, Object[] namedArgs)
-      throws EvalException, ConversionException {
+  static Environment.NoneType callExportsFiles(Object srcs, Object visibilityO, Object licensesO,
+      FuncallExpression ast, Environment env) throws EvalException, ConversionException {
     Package.LegacyBuilder pkgBuilder = getContext(env, ast).pkgBuilder;
-    List<String> files = Type.STRING_LIST.convert(namedArgs[0], "'exports_files' operand");
+    List<String> files = Type.STRING_LIST.convert(srcs, "'exports_files' operand");
 
-    RuleVisibility visibility = namedArgs[1] == null
+    RuleVisibility visibility = EvalUtils.isNullOrNone(visibilityO)
         ? ConstantRuleVisibility.PUBLIC
         : getVisibility(Type.LABEL_LIST.convert(
-            namedArgs[1],
-            "'exports_files' operand",
-            pkgBuilder.getBuildFileLabel()));
-    License license = namedArgs[2] == null
-        ? null
-        : Type.LICENSE.convert(namedArgs[2], "'exports_files' operand");
+              visibilityO,
+              "'exports_files' operand",
+              pkgBuilder.getBuildFileLabel()));
+    // TODO(bazel-team): is licenses plural or singular?
+    License license = Type.LICENSE.convertOptional(licensesO, "'exports_files' operand");
 
     for (String file : files) {
       String errorMessage = LabelValidator.validateTargetName(file);
@@ -683,67 +737,94 @@
    * context.
    * TODO(bazel-team): Remove in favor of package.licenses.
    */
-  private static Function newLicensesFunction(final PackageContext context) {
-    return new MixedModeFunction("licenses", ImmutableList.of("object"), 1, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast) {
-          try {
-            License license = Type.LICENSE.convert(args[0], "'licenses' operand");
-            context.pkgBuilder.setDefaultLicense(license);
-          } catch (ConversionException e) {
-            context.eventHandler.handle(Event.error(ast.getLocation(), e.getMessage()));
-            context.pkgBuilder.setContainsErrors();
-          }
-          return Environment.NONE;
+  @SkylarkSignature(name = "licenses", returnType = Environment.NoneType.class,
+      doc = "Declare the license(s) for the code in the current package.",
+      mandatoryPositionals = {
+        @Param(name = "license_strings", type = HackHackEitherList.class, generic1 = String.class,
+            doc = "A list of strings, the names of the licenses used.")},
+      documented = false, useLocation = true)
+  protected static final BuiltinFunction.Factory newLicensesFunction =
+      new BuiltinFunction.Factory("licenses") {
+        public BuiltinFunction create(final PackageContext context) {
+          return new BuiltinFunction("licenses", this) {
+            public Environment.NoneType invoke(Object licensesO, Location loc) {
+              try {
+                License license = Type.LICENSE.convert(licensesO, "'licenses' operand");
+                context.pkgBuilder.setDefaultLicense(license);
+              } catch (ConversionException e) {
+                context.eventHandler.handle(Event.error(loc, e.getMessage()));
+                context.pkgBuilder.setContainsErrors();
+              }
+              return Environment.NONE;
+            }
+          };
         }
       };
-  }
 
   /**
    * Returns a function-value implementing "distribs" in the specified package
    * context.
    * TODO(bazel-team): Remove in favor of package.distribs.
    */
-  private static Function newDistribsFunction(final PackageContext context) {
-    return new MixedModeFunction("distribs", ImmutableList.of("object"), 1, false) {
-        @Override
-        public Object call(Object[] args, FuncallExpression ast) {
-          try {
-            Set<DistributionType> distribs = Type.DISTRIBUTIONS.convert(args[0],
-                "'distribs' operand");
-            context.pkgBuilder.setDefaultDistribs(distribs);
-          } catch (ConversionException e) {
-            context.eventHandler.handle(Event.error(ast.getLocation(), e.getMessage()));
-            context.pkgBuilder.setContainsErrors();
-          }
-          return Environment.NONE;
+  @SkylarkSignature(name = "distribs", returnType = Environment.NoneType.class,
+      doc = "Declare the distribution(s) for the code in the current package.",
+      mandatoryPositionals = {
+        @Param(name = "distribution_strings", type = Object.class,
+            doc = "The distributions.")},
+      documented = false, useLocation = true)
+  protected static final BuiltinFunction.Factory newDistribsFunction =
+      new BuiltinFunction.Factory("distribs") {
+        public BuiltinFunction create(final PackageContext context) {
+          return new BuiltinFunction("distribs", this) {
+            public Environment.NoneType invoke(Object object, Location loc) {
+              try {
+                Set<DistributionType> distribs = Type.DISTRIBUTIONS.convert(object,
+                    "'distribs' operand");
+                context.pkgBuilder.setDefaultDistribs(distribs);
+              } catch (ConversionException e) {
+                context.eventHandler.handle(Event.error(loc, e.getMessage()));
+                context.pkgBuilder.setContainsErrors();
+              }
+              return Environment.NONE;
+            }
+          };
         }
       };
-  }
 
-  private static Function newPackageGroupFunction() {
-    List<String> params = ImmutableList.of("name", "packages", "includes");
-    return new MixedModeFunction("package_group", params, 1, true) {
-        @Override
-        public Object call(Object[] namedArgs, FuncallExpression ast, Environment env)
-            throws EvalException, ConversionException {
-          return callPackageFunction(ast, env, namedArgs);
+  @SkylarkSignature(name = "package_group", returnType = Environment.NoneType.class,
+      doc = "Declare a set of files as exported",
+      mandatoryNamedOnly = {
+        @Param(name = "name", type = String.class,
+            doc = "The name of the rule.")},
+      optionalNamedOnly = {
+        @Param(name = "packages", type = HackHackEitherList.class, generic1 = String.class,
+            defaultValue = "[]",
+            doc = "A list of Strings specifying the packages grouped."),
+        // java list or list of label designators: Label or String
+        @Param(name = "includes", type = HackHackEitherList.class, generic1 = Object.class,
+            defaultValue = "[]",
+            doc = "A list of Label specifiers for the files to include.")},
+      documented = false, useAst = true, useEnvironment = true)
+  protected static final BuiltinFunction.Factory newPackageGroupFunction =
+      new BuiltinFunction.Factory("package_group") {
+        public BuiltinFunction create() {
+          return new BuiltinFunction("package_group", this) {
+            public Environment.NoneType invoke(String name, Object packages, Object includes,
+                FuncallExpression ast, Environment env) throws EvalException, ConversionException {
+              return callPackageFunction(name, packages, includes, ast, env);
+            }
+          };
         }
       };
-  }
 
-  static Object callPackageFunction(FuncallExpression ast, Environment env, Object[] namedArgs)
-      throws EvalException, ConversionException {
+  static Environment.NoneType callPackageFunction(String name, Object packagesO, Object includesO,
+      FuncallExpression ast, Environment env) throws EvalException, ConversionException {
     PackageContext context = getContext(env, ast);
-    Preconditions.checkState(namedArgs[0] != null);
-    String name = Type.STRING.convert(namedArgs[0], "'package_group' argument");
-    List<String> packages = namedArgs[1] == null
-        ? Collections.<String>emptyList()
-        : Type.STRING_LIST.convert(namedArgs[1], "'package_group' argument");
-    List<Label> includes = namedArgs[2] == null
-        ? Collections.<Label>emptyList()
-        : Type.LABEL_LIST.convert(namedArgs[2], "'package_group argument'",
-                                  context.pkgBuilder.getBuildFileLabel());
+
+    List<String> packages = Type.STRING_LIST.convert(
+        packagesO, "'package_group.packages argument'");
+    List<Label> includes = Type.LABEL_LIST.convert(includesO,
+        "'package_group.includes argument'", context.pkgBuilder.getBuildFileLabel());
 
     try {
       context.pkgBuilder.addPackageGroup(name, packages, includes, context.eventHandler,
@@ -774,10 +855,23 @@
    * context.
    */
   private static Function newPackageFunction(
-      final Map<String, PackageArgument<?>> packageArguments) {
-    return new MixedModeFunction("package", packageArguments.keySet(), 0, true) {
+      final ImmutableMap<String, PackageArgument<?>> packageArguments) {
+    // Flatten the map of argument name of PackageArgument specifier in two co-indexed arrays:
+    // one for the argument names, to create a FunctionSignature when we create the function,
+    // one of the PackageArgument specifiers, over which to iterate at every function invocation
+    // at the same time that we iterate over the function arguments.
+    final int numArgs = packageArguments.size();
+    final String[] argumentNames = new String[numArgs];
+    final PackageArgument<?>[] argumentSpecifiers = new PackageArgument<?>[numArgs];
+    int i = 0;
+    for (Map.Entry<String, PackageArgument<?>> entry : packageArguments.entrySet()) {
+      argumentNames[i] = entry.getKey();
+      argumentSpecifiers[i++] = entry.getValue();
+    }
+
+    return new BaseFunction("package", FunctionSignature.namedOnly(0, argumentNames)) {
       @Override
-      public Object call(Object[] namedArguments, FuncallExpression ast, Environment env)
+      public Object call(Object[] arguments, FuncallExpression ast, Environment env)
           throws EvalException, ConversionException {
 
         Package.LegacyBuilder pkgBuilder = getContext(env, ast).pkgBuilder;
@@ -792,16 +886,12 @@
         // Parse params
         boolean foundParameter = false;
 
-        int argNumber = 0;
-        for (Map.Entry<String, PackageArgument<?>> entry : packageArguments.entrySet()) {
-          Object arg = namedArguments[argNumber];
-          argNumber += 1;
-          if (arg == null) {
-            continue;
+        for (int i = 0; i < numArgs; i++) {
+          Object value = arguments[i];
+          if (value != null) {
+            foundParameter = true;
+            argumentSpecifiers[i].convertAndProcess(pkgBuilder, ast.getLocation(), value);
           }
-
-          foundParameter = true;
-          entry.getValue().convertAndProcess(pkgBuilder, ast.getLocation(), arg);
         }
 
         if (!foundParameter) {
@@ -852,18 +942,13 @@
    * Returns a function-value implementing the build rule "ruleClass" (e.g. cc_library) in the
    * specified package context.
    */
-  private static Function newRuleFunction(final RuleFactory ruleFactory,
-                                          final String ruleClass) {
-    return new AbstractFunction(ruleClass) {
-      @Override
-      public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast,
-          Environment env)
+  private static BuiltinFunction newRuleFunction(
+      final RuleFactory ruleFactory, final String ruleClass) {
+    return new BuiltinFunction(ruleClass, FunctionSignature.KWARGS, BuiltinFunction.USE_AST_ENV) {
+      @SuppressWarnings("unchecked")
+      public Environment.NoneType invoke(Map<String, Object> kwargs,
+          FuncallExpression ast, Environment env)
           throws EvalException {
-        if (!args.isEmpty()) {
-          throw new EvalException(ast.getLocation(),
-              "build rules do not accept positional parameters");
-        }
-
         try {
           addRule(ruleFactory, ruleClass, getContext(env, ast), kwargs, ast);
         } catch (RuleFactory.InvalidRuleException | Package.NameConflictException e) {
@@ -1117,15 +1202,15 @@
 
   private void buildPkgEnv(Environment pkgEnv, String packageName,
       PackageContext context, RuleFactory ruleFactory) {
-    pkgEnv.update("distribs", newDistribsFunction(context));
-    pkgEnv.update("glob", newGlobFunction(context, /*async=*/false));
-    pkgEnv.update("mocksubinclude", newMockSubincludeFunction(context));
-    pkgEnv.update("licenses", newLicensesFunction(context));
-    pkgEnv.update("exports_files", newExportsFilesFunction());
-    pkgEnv.update("package_group", newPackageGroupFunction());
+    pkgEnv.update("distribs", newDistribsFunction.apply(context));
+    pkgEnv.update("glob", newGlobFunction.apply(context, /*async=*/false));
+    pkgEnv.update("mocksubinclude", newMockSubincludeFunction.apply(context));
+    pkgEnv.update("licenses", newLicensesFunction.apply(context));
+    pkgEnv.update("exports_files", newExportsFilesFunction.apply());
+    pkgEnv.update("package_group", newPackageGroupFunction.apply());
     pkgEnv.update("package", newPackageFunction(packageArguments));
-    pkgEnv.update("subinclude", newSubincludeFunction());
-    pkgEnv.update("environment_group", newEnvironmentGroupFunction(context));
+    pkgEnv.update("subinclude", newSubincludeFunction.apply());
+    pkgEnv.update("environment_group", newEnvironmentGroupFunction.apply(context));
 
     pkgEnv.update("PACKAGE_NAME", packageName);
 
@@ -1251,7 +1336,7 @@
     // Stuff that closes over the package context:
     PackageContext context = new PackageContext(pkgBuilder, globber, NullEventHandler.INSTANCE);
     buildPkgEnv(pkgEnv, packageId.toString(), context, ruleFactory);
-    pkgEnv.update("glob", newGlobFunction(context, /*async=*/true));
+    pkgEnv.update("glob", newGlobFunction.apply(context, /*async=*/true));
     // The Fileset function is heavyweight in that it can run glob(). Avoid this during the
     // preloading phase.
     pkgEnv.remove("FilesetEntry");
@@ -1301,4 +1386,8 @@
     }
     return true;
   }
+
+  static {
+    SkylarkSignatureProcessor.configureSkylarkFunctions(PackageFactory.class);
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index b4273c5..c604fff 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -31,11 +31,11 @@
 import com.google.devtools.build.lib.syntax.Argument;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.FuncallExpression;
+import com.google.devtools.build.lib.syntax.Function;
 import com.google.devtools.build.lib.syntax.GlobList;
 import com.google.devtools.build.lib.syntax.Label;
 import com.google.devtools.build.lib.syntax.Label.SyntaxException;
 import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
-import com.google.devtools.build.lib.syntax.UserDefinedFunction;
 import com.google.devtools.build.lib.util.StringUtil;
 import com.google.devtools.build.lib.vfs.PathFragment;
 
@@ -427,7 +427,7 @@
         PredicatesWithMessage.<Rule>alwaysTrue();
     private Predicate<String> preferredDependencyPredicate = Predicates.alwaysFalse();
     private List<Class<?>> advertisedProviders = new ArrayList<>();
-    private UserDefinedFunction configuredTargetFunction = null;
+    private Function configuredTargetFunction = null;
     private SkylarkEnvironment ruleDefinitionEnvironment = null;
     private Set<Class<?>> configurationFragments = new LinkedHashSet<>();
     private boolean failIfMissingConfigurationFragment;
@@ -681,7 +681,7 @@
     /**
      * Sets the rule implementation function. Meant for Skylark usage.
      */
-    public Builder setConfiguredTargetFunction(UserDefinedFunction func) {
+    public Builder setConfiguredTargetFunction(Function func) {
       this.configuredTargetFunction = func;
       return this;
     }
@@ -834,7 +834,7 @@
   /**
    * The Skylark rule implementation of this RuleClass. Null for non Skylark executable RuleClasses.
    */
-  @Nullable private final UserDefinedFunction configuredTargetFunction;
+  @Nullable private final Function configuredTargetFunction;
 
   /**
    * The Skylark rule definition environment of this RuleClass.
@@ -895,7 +895,7 @@
       ConfiguredTargetFactory<?, ?> configuredTargetFactory,
       PredicateWithMessage<Rule> validityPredicate, Predicate<String> preferredDependencyPredicate,
       ImmutableSet<Class<?>> advertisedProviders,
-      @Nullable UserDefinedFunction configuredTargetFunction,
+      @Nullable Function configuredTargetFunction,
       @Nullable SkylarkEnvironment ruleDefinitionEnvironment,
       Set<Class<?>> allowedConfigurationFragments, boolean failIfMissingConfigurationFragment,
       boolean supportsConstraintChecking,
@@ -1503,7 +1503,7 @@
   /**
    * Returns this RuleClass's custom Skylark rule implementation.
    */
-  @Nullable public UserDefinedFunction getConfiguredTargetFunction() {
+  @Nullable public Function getConfiguredTargetFunction() {
     return configuredTargetFunction;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
index a5b438c..1965b9d 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
@@ -15,17 +15,16 @@
 package com.google.devtools.build.lib.packages;
 
 import com.google.devtools.build.lib.packages.Type.ConversionException;
+import com.google.devtools.build.lib.syntax.BuiltinFunction;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.FuncallExpression;
 import com.google.devtools.build.lib.syntax.GlobList;
-import com.google.devtools.build.lib.syntax.SkylarkFunction;
 import com.google.devtools.build.lib.syntax.SkylarkList;
 import com.google.devtools.build.lib.syntax.SkylarkModule;
 import com.google.devtools.build.lib.syntax.SkylarkSignature;
 import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
-
-import java.util.Map;
+import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
 
 /**
  * A class for the Skylark native module.
@@ -37,6 +36,7 @@
     + "Extra helper functions:")
 public class SkylarkNativeModule {
 
+  // TODO(bazel-team): shouldn't we return a SkylarkList instead?
   @SkylarkSignature(name = "glob", objectType = SkylarkNativeModule.class,
       returnType = GlobList.class,
       doc = "Glob returns a list of every file in the current package that:<ul>\n"
@@ -55,17 +55,15 @@
       @Param(name = "exclude_directories", type = Integer.class, defaultValue = "1",
           doc = "A flag whether to exclude directories or not.")},
       useAst = true, useEnvironment = true)
-  private static final SkylarkFunction glob = new SkylarkFunction("glob") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
+  private static final BuiltinFunction glob = new BuiltinFunction("glob") {
+      public GlobList<String> invoke(
+          SkylarkList includes, SkylarkList excludes, Integer excludeDirectories,
+          FuncallExpression ast, Environment env)
           throws EvalException, ConversionException, InterruptedException {
-        return PackageFactory.callGlob(null, false, ast, env, new Object[] {
-              kwargs.get("includes"),
-              kwargs.get("excludes"),
-              kwargs.get("exclude_directories")
-            });
-      }
-    };
+        return PackageFactory.callGlob(
+            null, false, includes, excludes, excludeDirectories != 0, ast, env);
+    }
+  };
 
   @SkylarkSignature(name = "package_group", objectType = SkylarkNativeModule.class,
       returnType = Environment.NoneType.class,
@@ -82,17 +80,12 @@
           defaultValue = "[]",
           doc = "Other package groups that are included in this one.")},
       useAst = true, useEnvironment = true)
-  private static final SkylarkFunction packageGroup = new SkylarkFunction("package_group") {
-    @Override
-    public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-        throws EvalException, ConversionException {
-      return PackageFactory.callPackageFunction(ast, env, new Object[] {
-          kwargs.get("name"),
-          kwargs.get("packages"),
-          kwargs.get("includes")
-      });
-    }
-  };
+  private static final BuiltinFunction packageGroup = new BuiltinFunction("package_group") {
+      public Environment.NoneType invoke(String name, SkylarkList packages, SkylarkList includes,
+                FuncallExpression ast, Environment env) throws EvalException, ConversionException {
+        return PackageFactory.callPackageFunction(name, packages, includes, ast, env);
+      }
+    };
 
   @SkylarkSignature(name = "exports_files", objectType = SkylarkNativeModule.class,
       returnType = Environment.NoneType.class,
@@ -111,17 +104,17 @@
       @Param(name = "licenses", type = SkylarkList.class, generic1 = String.class, noneable = true,
           doc = "Licenses to be specified.")},
       useAst = true, useEnvironment = true)
-  private static final SkylarkFunction exportsFiles = new SkylarkFunction("exports_files") {
-    @Override
-    public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-        throws EvalException, ConversionException {
-      return PackageFactory.callExportsFiles(ast, env, new Object[] {
-          kwargs.get("srcs"),
-          kwargs.get("visibility"),
-          kwargs.get("licenses")
-      });
-    }
-  };
+  private static final BuiltinFunction exportsFiles = new BuiltinFunction("exports_files") {
+      public Environment.NoneType invoke(SkylarkList srcs, Object visibility, Object licenses,
+          FuncallExpression ast, Environment env)
+          throws EvalException, ConversionException {
+        return PackageFactory.callExportsFiles(srcs, visibility, licenses, ast, env);
+      }
+    };
 
   public static final SkylarkNativeModule NATIVE_MODULE = new SkylarkNativeModule();
+
+  static {
+    SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkNativeModule.class);
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java
index 7d39e0d..46d31e22 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java
@@ -21,6 +21,7 @@
 import com.google.devtools.build.lib.packages.Attribute.SkylarkLateBound;
 import com.google.devtools.build.lib.packages.Type;
 import com.google.devtools.build.lib.packages.Type.ConversionException;
+import com.google.devtools.build.lib.syntax.BuiltinFunction;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalUtils;
@@ -28,11 +29,11 @@
 import com.google.devtools.build.lib.syntax.Label;
 import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction;
 import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
-import com.google.devtools.build.lib.syntax.SkylarkFunction;
 import com.google.devtools.build.lib.syntax.SkylarkList;
 import com.google.devtools.build.lib.syntax.SkylarkModule;
 import com.google.devtools.build.lib.syntax.SkylarkSignature;
 import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
+import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
 import com.google.devtools.build.lib.syntax.UserDefinedFunction;
 import com.google.devtools.build.lib.util.FileTypeSet;
 
@@ -171,11 +172,14 @@
         @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true,
             defaultValue = "None", doc = CONFIGURATION_DOC)},
       useAst = true, useEnvironment = true)
-  private static SkylarkFunction integer = new SkylarkFunction("int") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-          throws EvalException {
-        return createAttribute(kwargs, Type.INTEGER, ast, env);
+  private static BuiltinFunction integer = new BuiltinFunction("int") {
+      public Attribute.Builder<?> invoke(Integer defaultInt,
+          SkylarkList flags, Boolean mandatory, Object cfg,
+          FuncallExpression ast, Environment env) throws EvalException {
+        return createAttribute(
+            EvalUtils.optionMap(
+                "default", defaultInt, "flags", flags, "mandatory", mandatory, "cfg", cfg),
+            Type.INTEGER, ast, env);
       }
     };
 
@@ -193,11 +197,14 @@
         @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true,
             defaultValue = "None", doc = CONFIGURATION_DOC)},
       useAst = true, useEnvironment = true)
-  private static SkylarkFunction string = new SkylarkFunction("string") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-          throws EvalException {
-        return createAttribute(kwargs, Type.STRING, ast, env);
+  private static BuiltinFunction string = new BuiltinFunction("string") {
+      public Attribute.Builder<?> invoke(String defaultString,
+          SkylarkList flags, Boolean mandatory, Object cfg,
+          FuncallExpression ast, Environment env) throws EvalException {
+        return createAttribute(
+            EvalUtils.optionMap(
+                "default", defaultString, "flags", flags, "mandatory", mandatory, "cfg", cfg),
+            Type.STRING, ast, env);
       }
     };
 
@@ -230,18 +237,31 @@
         @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true,
             defaultValue = "None", doc = CONFIGURATION_DOC)},
       useAst = true, useEnvironment = true)
-  private static SkylarkFunction label = new SkylarkFunction("label") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-          throws EvalException {
-        return createAttribute(kwargs, Type.LABEL, ast, env);
+  private static BuiltinFunction label = new BuiltinFunction("label") {
+      public Attribute.Builder<?> invoke(
+          Object defaultO,
+          Boolean executable,
+          SkylarkList flags,
+          Object allowFiles,
+          Boolean mandatory,
+          SkylarkList providers,
+          Object allowRules,
+          Boolean singleFile,
+          Object cfg,
+          FuncallExpression ast, Environment env) throws EvalException {
+        return createAttribute(
+            EvalUtils.optionMap(
+                "default", defaultO, "executable", executable, "flags", flags,
+                "allow_files", allowFiles, "mandatory", mandatory, "providers", providers,
+                "allow_rules", allowRules, "single_file", singleFile, "cfg", cfg),
+            Type.LABEL, ast, env);
       }
     };
 
   @SkylarkSignature(name = "string_list", doc =
       "Creates an attribute of type list of strings",
       objectType = SkylarkAttr.class,
-      returnType = Attribute.class,
+      returnType = Attribute.Builder.class,
       optionalPositionals = {
         @Param(name = "default", type = SkylarkList.class, generic1 = String.class,
             defaultValue = "[]",
@@ -255,11 +275,19 @@
         @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true,
             defaultValue = "None", doc = CONFIGURATION_DOC)},
       useAst = true, useEnvironment = true)
-  private static SkylarkFunction stringList = new SkylarkFunction("string_list") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-          throws EvalException {
-        return createAttribute(kwargs, Type.STRING_LIST, ast, env);
+  private static BuiltinFunction stringList = new BuiltinFunction("string_list") {
+      public Attribute.Builder<?> invoke(
+          SkylarkList defaultList,
+          SkylarkList flags,
+          Boolean mandatory,
+          Boolean nonEmpty,
+          Object cfg,
+          FuncallExpression ast, Environment env) throws EvalException {
+        return createAttribute(
+            EvalUtils.optionMap(
+                "default", defaultList,
+                "flags", flags, "mandatory", mandatory, "non_empty", nonEmpty, "cfg", cfg),
+            Type.STRING_LIST, ast, env);
       }
     };
 
@@ -288,11 +316,22 @@
         @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true,
             defaultValue = "None", doc = CONFIGURATION_DOC)},
       useAst = true, useEnvironment = true)
-  private static SkylarkFunction labelList = new SkylarkFunction("label_list") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-          throws EvalException {
-        return createAttribute(kwargs, Type.LABEL_LIST, ast, env);
+  private static BuiltinFunction labelList = new BuiltinFunction("label_list") {
+      public Attribute.Builder<?> invoke(
+          Object defaultList,
+          Object allowFiles,
+          Object allowRules,
+          SkylarkList providers,
+          SkylarkList flags,
+          Boolean mandatory,
+          Boolean nonEmpty,
+          Object cfg,
+          FuncallExpression ast, Environment env) throws EvalException {
+        return createAttribute(
+            EvalUtils.optionMap("default", defaultList,
+                "allow_files", allowFiles, "allow_rules", allowRules, "providers", providers,
+                "flags", flags, "mandatory", mandatory, "non_empty", nonEmpty, "cfg", cfg),
+            Type.LABEL_LIST, ast, env);
       }
     };
 
@@ -310,11 +349,14 @@
         @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true,
             defaultValue = "None", doc = CONFIGURATION_DOC)},
       useAst = true, useEnvironment = true)
-  private static SkylarkFunction bool = new SkylarkFunction("bool") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-          throws EvalException {
-        return createAttribute(kwargs, Type.BOOLEAN, ast, env);
+  private static BuiltinFunction bool = new BuiltinFunction("bool") {
+      public Attribute.Builder<?> invoke(Boolean defaultBool,
+          SkylarkList flags, Boolean mandatory, Object cfg,
+          FuncallExpression ast, Environment env) throws EvalException {
+        return createAttribute(
+            EvalUtils.optionMap(
+                "default", defaultBool, "flags", flags, "mandatory", mandatory, "cfg", cfg),
+            Type.BOOLEAN, ast, env);
       }
     };
 
@@ -334,11 +376,14 @@
         @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true,
             defaultValue = "None", doc = CONFIGURATION_DOC)},
       useAst = true, useEnvironment = true)
-  private static SkylarkFunction output = new SkylarkFunction("output") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-          throws EvalException {
-        return createAttribute(kwargs, Type.OUTPUT, ast, env);
+  private static BuiltinFunction output = new BuiltinFunction("output") {
+      public Attribute.Builder<?> invoke(Object defaultO,
+          SkylarkList flags, Boolean mandatory, Object cfg,
+          FuncallExpression ast, Environment env) throws EvalException {
+        return createAttribute(
+            EvalUtils.optionMap(
+                "default", defaultO, "flags", flags, "mandatory", mandatory, "cfg", cfg),
+            Type.OUTPUT, ast, env);
       }
     };
 
@@ -359,11 +404,14 @@
         @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true,
             defaultValue = "None", doc = CONFIGURATION_DOC)},
       useAst = true, useEnvironment = true)
-  private static SkylarkFunction outputList = new SkylarkFunction("output_list") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-          throws EvalException {
-        return createAttribute(kwargs, Type.OUTPUT_LIST, ast, env);
+  private static BuiltinFunction outputList = new BuiltinFunction("output_list") {
+      public Attribute.Builder<?> invoke(SkylarkList defaultList,
+          SkylarkList flags, Boolean mandatory, Boolean nonEmpty, Object cfg,
+          FuncallExpression ast, Environment env) throws EvalException {
+        return createAttribute(
+            EvalUtils.optionMap("default", defaultList,
+                "flags", flags, "mandatory", mandatory, "non_empty", nonEmpty, "cfg", cfg),
+            Type.OUTPUT_LIST, ast, env);
       }
     };
 
@@ -384,11 +432,14 @@
         @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true,
             defaultValue = "None", doc = CONFIGURATION_DOC)},
       useAst = true, useEnvironment = true)
-  private static SkylarkFunction stringDict = new SkylarkFunction("string_dict") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-          throws EvalException {
-        return createAttribute(kwargs, Type.STRING_DICT, ast, env);
+  private static BuiltinFunction stringDict = new BuiltinFunction("string_dict") {
+      public Attribute.Builder<?> invoke(Map<?, ?> defaultO,
+          SkylarkList flags, Boolean mandatory, Boolean nonEmpty, Object cfg,
+          FuncallExpression ast, Environment env) throws EvalException {
+        return createAttribute(
+            EvalUtils.optionMap("default", defaultO,
+                "flags", flags, "mandatory", mandatory, "non_empty", nonEmpty, "cfg", cfg),
+            Type.STRING_DICT, ast, env);
       }
     };
 
@@ -408,11 +459,18 @@
         @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true,
             defaultValue = "None", doc = CONFIGURATION_DOC)},
       useAst = true, useEnvironment = true)
-  private static SkylarkFunction license = new SkylarkFunction("license") {
-      @Override
-      public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
-          throws EvalException {
-        return createAttribute(kwargs, Type.LICENSE, ast, env);
+  private static BuiltinFunction license = new BuiltinFunction("license") {
+      public Attribute.Builder<?> invoke(Object defaultO,
+          SkylarkList flags, Boolean mandatory, Object cfg,
+          FuncallExpression ast, Environment env) throws EvalException {
+        return createAttribute(
+            EvalUtils.optionMap(
+                "default", defaultO, "flags", flags, "mandatory", mandatory, "cfg", cfg),
+            Type.LICENSE, ast, env);
       }
     };
+
+  static {
+    SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkAttr.class);
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java
index 260c7e4..a262486 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java
@@ -18,16 +18,13 @@
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
-import com.google.devtools.build.lib.events.Location;
-import com.google.devtools.build.lib.syntax.EvalException;
-import com.google.devtools.build.lib.syntax.SkylarkFunction.SimpleSkylarkFunction;
+import com.google.devtools.build.lib.syntax.BuiltinFunction;
 import com.google.devtools.build.lib.syntax.SkylarkList;
 import com.google.devtools.build.lib.syntax.SkylarkModule;
 import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
 import com.google.devtools.build.lib.syntax.SkylarkSignature;
 import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
-
-import java.util.Map;
+import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
 
 /**
  * A Skylark module class to create memory efficient command lines.
@@ -36,23 +33,17 @@
     doc = "Module for creating memory efficient command lines.")
 public class SkylarkCommandLine {
 
-  @SkylarkSignature(name = "join_paths",
+  @SkylarkSignature(name = "join_paths", objectType = SkylarkCommandLine.class,
+      returnType = String.class,
       doc = "Creates a single command line argument joining the paths of a set "
           + "of files on the separator string.",
-      objectType = SkylarkCommandLine.class,
-      returnType = String.class,
       mandatoryPositionals = {
       @Param(name = "separator", type = String.class, doc = "the separator string to join on"),
       @Param(name = "files", type = SkylarkNestedSet.class, generic1 = Artifact.class,
              doc = "the files to concatenate")})
-  private static SimpleSkylarkFunction joinPaths =
-      new SimpleSkylarkFunction("join_paths") {
-    @Override
-    public Object call(Map<String, Object> params, Location loc)
-        throws EvalException {
-      final String separator = (String) params.get("separator");
-      final NestedSet<Artifact> artifacts =
-          ((SkylarkNestedSet) params.get("files")).getSet(Artifact.class);
+  private static BuiltinFunction joinPaths = new BuiltinFunction("join_paths") {
+    public String invoke(String separator, SkylarkNestedSet files) {
+      NestedSet<Artifact> artifacts = files.getSet(Artifact.class);
       // TODO(bazel-team): lazy evaluate
       return Artifact.joinExecPaths(separator, artifacts);
     }
@@ -70,12 +61,8 @@
           doc = "The template to use for the transformation, <code>%{path}</code> and "
               + "<code>%{short_path}</code> being substituted with the corresponding fields of each"
               + " file.")})
-  private static SimpleSkylarkFunction template = new SimpleSkylarkFunction("template") {
-    @Override
-    public Object call(Map<String, Object> params, Location loc)
-        throws EvalException {
-      final String template = (String) params.get("template");
-      SkylarkNestedSet items = (SkylarkNestedSet) params.get("items");
+  private static BuiltinFunction template = new BuiltinFunction("template") {
+    public SkylarkList invoke(final SkylarkNestedSet items, final String template) {
       return SkylarkList.lazyList(Iterables.transform(items, new Function<Object, String>() {
         @Override
         public String apply(Object input) {
@@ -87,4 +74,8 @@
       }), String.class);
     }
   };
+
+  static {
+    SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkCommandLine.class);
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java
index 226ceeb..fdb69a3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java
@@ -21,11 +21,10 @@
 import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.packages.MethodLibrary;
 import com.google.devtools.build.lib.packages.SkylarkNativeModule;
+import com.google.devtools.build.lib.syntax.BaseFunction;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvaluationContext;
-import com.google.devtools.build.lib.syntax.Function;
 import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
-import com.google.devtools.build.lib.syntax.SkylarkFunction;
 import com.google.devtools.build.lib.syntax.SkylarkModule;
 import com.google.devtools.build.lib.syntax.SkylarkSignature;
 import com.google.devtools.build.lib.syntax.ValidationEnvironment;
@@ -43,9 +42,11 @@
 public class SkylarkModules {
 
   /**
-   * The list of built in Skylark modules. Documentation is generated automatically for all these
-   * modules. They are also registered with the {@link ValidationEnvironment} and the
-   * {@link SkylarkEnvironment}. Note that only {@link SkylarkFunction}s are handled properly.
+   * The list of built in Skylark modules.
+   * Documentation is generated automatically for all these modules.
+   * They are also registered with the {@link ValidationEnvironment}
+   * and the {@link SkylarkEnvironment}.
+   * Note that only functions with a {@link SkylarkSignature} annotations are handled properly.
    */
   // TODO(bazel-team): find a more general, more automated way of registering classes and building
   // initial environments. And don't give syntax.Environment and packages.MethodLibrary a special
@@ -57,19 +58,20 @@
       SkylarkRuleClassFunctions.class,
       SkylarkRuleImplementationFunctions.class);
 
-  private static final ImmutableMap<Class<?>, ImmutableList<Function>> FUNCTION_MAP;
+  private static final ImmutableMap<Class<?>, ImmutableList<BaseFunction>> FUNCTION_MAP;
   private static final ImmutableMap<String, Object> OBJECTS;
 
   static {
     try {
-      ImmutableMap.Builder<Class<?>, ImmutableList<Function>> functionMap = ImmutableMap.builder();
+      ImmutableMap.Builder<Class<?>, ImmutableList<BaseFunction>> functionMap =
+          ImmutableMap.builder();
       ImmutableMap.Builder<String, Object> objects = ImmutableMap.builder();
       for (Class<?> moduleClass : MODULES) {
         if (moduleClass.isAnnotationPresent(SkylarkModule.class)) {
           objects.put(moduleClass.getAnnotation(SkylarkModule.class).name(),
               moduleClass.newInstance());
         }
-        ImmutableList.Builder<Function> functions = ImmutableList.builder();
+        ImmutableList.Builder<BaseFunction> functions = ImmutableList.builder();
         collectSkylarkFunctionsAndObjectsFromFields(moduleClass, functions, objects);
         functionMap.put(moduleClass, functions.build());
       }
@@ -97,8 +99,8 @@
 
   private static void setupEnvironment(Environment env) {
     MethodLibrary.setupMethodEnvironment(env);
-    for (Map.Entry<Class<?>, ImmutableList<Function>> entry : FUNCTION_MAP.entrySet()) {
-      for (Function function : entry.getValue()) {
+    for (Map.Entry<Class<?>, ImmutableList<BaseFunction>> entry : FUNCTION_MAP.entrySet()) {
+      for (BaseFunction function : entry.getValue()) {
         if (function.getObjectType() != null) {
           env.registerFunction(function.getObjectType(), function.getName(), function);
         } else {
@@ -143,11 +145,11 @@
   }
 
   /**
-   * Collects the Functions from the fields of the class of the object parameter
+   * Collects the BaseFunctions from the fields of the class of the object parameter
    * and adds them into the builder.
    */
   private static void collectSkylarkFunctionsAndObjectsFromFields(Class<?> type,
-      ImmutableList.Builder<Function> functions, ImmutableMap.Builder<String, Object> objects) {
+      ImmutableList.Builder<BaseFunction> functions, ImmutableMap.Builder<String, Object> objects) {
     try {
       for (Field field : type.getDeclaredFields()) {
         if (field.isAnnotationPresent(SkylarkSignature.class)) {
@@ -156,12 +158,8 @@
           field.setAccessible(true);
           SkylarkSignature annotation = field.getAnnotation(SkylarkSignature.class);
           Object value = field.get(null);
-          if (SkylarkFunction.class.isAssignableFrom(field.getType())) {
-            SkylarkFunction function = (SkylarkFunction) value;
-            if (!function.isConfigured()) {
-              function.configure(annotation);
-            }
-            functions.add(function);
+          if (BaseFunction.class.isAssignableFrom(field.getType())) {
+            functions.add((BaseFunction) value);
           } else {
             objects.put(annotation.name(), value);
           }
@@ -174,14 +172,14 @@
   }
 
   /**
-   * Collects the Functions from the fields of the class of the object parameter
+   * Collects the BaseFunctions from the fields of the class of the object parameter
    * and adds their class and their corresponding return value to the builder.
    */
   private static void collectSkylarkTypesFromFields(Class<?> classObject, Set<String> builtIn) {
     for (Field field : classObject.getDeclaredFields()) {
       if (field.isAnnotationPresent(SkylarkSignature.class)) {
         SkylarkSignature annotation = field.getAnnotation(SkylarkSignature.class);
-        if (SkylarkFunction.class.isAssignableFrom(field.getType())) {
+        if (BaseFunction.class.isAssignableFrom(field.getType())) {
           // Ignore non-global values.
           if (annotation.objectType().equals(Object.class)) {
             builtIn.add(annotation.name());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
index 3cb4e28..14bdcd7 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
@@ -54,8 +54,8 @@
 import com.google.devtools.build.lib.packages.TestSize;
 import com.google.devtools.build.lib.packages.Type;
 import com.google.devtools.build.lib.packages.Type.ConversionException;
-import com.google.devtools.build.lib.syntax.AbstractFunction;
 import com.google.devtools.build.lib.syntax.BaseFunction;
+import com.google.devtools.build.lib.syntax.BuiltinFunction;
 import com.google.devtools.build.lib.syntax.ClassObject;
 import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
 import com.google.devtools.build.lib.syntax.Environment;
@@ -64,17 +64,15 @@
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.FuncallExpression;
 import com.google.devtools.build.lib.syntax.Function;
+import com.google.devtools.build.lib.syntax.FunctionSignature;
 import com.google.devtools.build.lib.syntax.Label;
 import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction;
 import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
-import com.google.devtools.build.lib.syntax.SkylarkFunction;
-import com.google.devtools.build.lib.syntax.SkylarkFunction.SimpleSkylarkFunction;
 import com.google.devtools.build.lib.syntax.SkylarkList;
 import com.google.devtools.build.lib.syntax.SkylarkSignature;
 import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
-import com.google.devtools.build.lib.syntax.UserDefinedFunction;
+import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
 
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 
@@ -182,7 +180,7 @@
       "Creates a new rule. Store it in a global value, so that it can be loaded and called "
       + "from BUILD files.",
       onlyLoadingPhase = true,
-      returnType = BaseFunction.class,
+      returnType = Function.class,
       mandatoryPositionals = {
         @Param(name = "implementation", type = Function.class,
             doc = "the function implementing this rule, has to have exactly one parameter: "
@@ -214,85 +212,85 @@
             doc = "If true, the files will be generated in the genfiles directory instead of the "
             + "bin directory. This is used for compatibility with existing rules.")},
       useAst = true, useEnvironment = true)
-  private static final SkylarkFunction rule = new SkylarkFunction("rule") {
+  private static final BuiltinFunction rule = new BuiltinFunction("rule") {
+      @SuppressWarnings({"rawtypes", "unchecked"}) // castMap produces
+      // an Attribute.Builder instead of a Attribute.Builder<?> but it's OK.
+      public Function invoke(Function implementation, Boolean test,
+          Object attrs, Object implicitOutputs, Boolean executable, Boolean outputToGenfiles,
+          FuncallExpression ast, Environment funcallEnv)
+           throws EvalException, ConversionException {
 
-        @Override
-        @SuppressWarnings({"rawtypes", "unchecked"}) // castMap produces
-        // an Attribute.Builder instead of a Attribute.Builder<?> but it's OK.
-        public Object call(Map<String, Object> arguments, FuncallExpression ast,
-            Environment funcallEnv) throws EvalException, ConversionException {
-          final Location loc = ast.getLocation();
+        RuleClassType type = test ? RuleClassType.TEST : RuleClassType.NORMAL;
 
-          RuleClassType type = RuleClassType.NORMAL;
-          if (arguments.containsKey("test") && EvalUtils.toBoolean(arguments.get("test"))) {
-            type = RuleClassType.TEST;
-          }
+        // We'll set the name later, pass the empty string for now.
+        RuleClass.Builder builder = test
+            ? new RuleClass.Builder("", type, true, testBaseRule)
+            : new RuleClass.Builder("", type, true, baseRule);
 
-          // We'll set the name later, pass the empty string for now.
-          final RuleClass.Builder builder = type == RuleClassType.TEST
-              ? new RuleClass.Builder("", type, true, testBaseRule)
-              : new RuleClass.Builder("", type, true, baseRule);
-
-          for (Map.Entry<String, Attribute.Builder> attr : castMap(arguments.get("attrs"),
-              String.class, Attribute.Builder.class, "attrs").entrySet()) {
-            Attribute.Builder<?> attrBuilder = attr.getValue();
-            String attrName = attributeToNative(attr.getKey(), loc,
+        if (attrs != Environment.NONE) {
+          for (Map.Entry<String, Attribute.Builder> attr : castMap(
+              attrs, String.class, Attribute.Builder.class, "attrs").entrySet()) {
+            Attribute.Builder<?> attrBuilder = (Attribute.Builder<?>) attr.getValue();
+            String attrName = attributeToNative(attr.getKey(), ast.getLocation(),
                 attrBuilder.hasLateBoundValue());
             builder.addOrOverrideAttribute(attrBuilder.build(attrName));
           }
-          if (arguments.containsKey("executable") && (Boolean) arguments.get("executable")) {
-            builder.addOrOverrideAttribute(
-                attr("$is_executable", BOOLEAN).value(true)
-                    .nonconfigurable("Called from RunCommand.isExecutable, which takes a Target")
-                    .build());
-            builder.setOutputsDefaultExecutable();
-          }
+        }
+        if (executable) {
+          builder.addOrOverrideAttribute(
+              attr("$is_executable", BOOLEAN).value(true)
+              .nonconfigurable("Called from RunCommand.isExecutable, which takes a Target")
+              .build());
+          builder.setOutputsDefaultExecutable();
+        }
 
-          if (arguments.containsKey("outputs")) {
-            final Object implicitOutputs = arguments.get("outputs");
-            if (implicitOutputs instanceof UserDefinedFunction) {
-              UserDefinedFunction func = (UserDefinedFunction) implicitOutputs;
-              final SkylarkCallbackFunction callback =
-                  new SkylarkCallbackFunction(func, ast, (SkylarkEnvironment) funcallEnv);
-              builder.setImplicitOutputsFunction(
-                  new SkylarkImplicitOutputsFunctionWithCallback(callback, loc));
-            } else {
-              builder.setImplicitOutputsFunction(new SkylarkImplicitOutputsFunctionWithMap(
-                  ImmutableMap.copyOf(castMap(arguments.get("outputs"), String.class, String.class,
-                      "implicit outputs of the rule class"))));
+        if (!(funcallEnv instanceof SkylarkEnvironment)) {
+          System.out.println("rule called from non-Skylark environment!");
+          // throw new EvaluationException("rule not accessible at the toplevel");
+        }
+
+        if (implicitOutputs != Environment.NONE) {
+          if (implicitOutputs instanceof Function) {
+            Function func = (Function) implicitOutputs;
+            final SkylarkCallbackFunction callback =
+                new SkylarkCallbackFunction(func, ast, (SkylarkEnvironment) funcallEnv);
+            builder.setImplicitOutputsFunction(
+                new SkylarkImplicitOutputsFunctionWithCallback(callback, ast.getLocation()));
+          } else {
+            builder.setImplicitOutputsFunction(new SkylarkImplicitOutputsFunctionWithMap(
+                ImmutableMap.copyOf(castMap(implicitOutputs, String.class, String.class,
+                        "implicit outputs of the rule class"))));
             }
-          }
+        }
 
-          if (arguments.containsKey("output_to_genfiles")
-              && (Boolean) arguments.get("output_to_genfiles")) {
-            builder.setOutputToGenfiles();
-          }
+        if (outputToGenfiles) {
+          builder.setOutputToGenfiles();
+        }
 
-          builder.setConfiguredTargetFunction(
-              (UserDefinedFunction) arguments.get("implementation"));
-          builder.setRuleDefinitionEnvironment((SkylarkEnvironment) funcallEnv);
-          return new RuleFunction(builder, type);
+        builder.setConfiguredTargetFunction(implementation);
+        builder.setRuleDefinitionEnvironment((SkylarkEnvironment) funcallEnv);
+        return new RuleFunction(builder, type);
         }
       };
 
   // This class is needed for testing
-  static final class RuleFunction extends AbstractFunction {
+  static final class RuleFunction extends BaseFunction {
     // Note that this means that we can reuse the same builder.
     // This is fine since we don't modify the builder from here.
     private final RuleClass.Builder builder;
     private final RuleClassType type;
 
     public RuleFunction(Builder builder, RuleClassType type) {
-      super("rule");
+      super("rule", FunctionSignature.KWARGS);
       this.builder = builder;
       this.type = type;
     }
 
+    @Override
     @SuppressWarnings("unchecked") // the magic hidden $pkg_context variable is guaranteed
     // to be a PackageContext
-    @Override
-    public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast,
-        Environment env) throws EvalException, InterruptedException {
+    public Object call(Object[] args, FuncallExpression ast, Environment env)
+        throws EvalException, InterruptedException, ConversionException {
       try {
         String ruleClassName = ast.getFunction().getName();
         if (ruleClassName.startsWith("_")) {
@@ -305,7 +303,8 @@
         }
         RuleClass ruleClass = builder.build(ruleClassName);
         PackageContext pkgContext = (PackageContext) env.lookup(PackageFactory.PKG_CONTEXT);
-        return RuleFactory.createAndAddRule(pkgContext, ruleClass, kwargs, ast);
+        return RuleFactory.createAndAddRule(
+            pkgContext, ruleClass, (Map<String, Object>) args[0], ast);
       } catch (InvalidRuleException | NameConflictException | NoSuchVariableException e) {
         throw new EvalException(ast.getLocation(), e.getMessage());
       }
@@ -324,33 +323,29 @@
       mandatoryPositionals = {@Param(name = "label_string", type = String.class,
             doc = "the label string")},
       useLocation = true)
-  private static final SkylarkFunction label = new SimpleSkylarkFunction("Label") {
-        @Override
-        public Object call(Map<String, Object> arguments, Location loc) throws EvalException,
-            ConversionException {
-          String labelString = (String) arguments.get("label_string");
+  private static final BuiltinFunction label = new BuiltinFunction("Label") {
+      public Label invoke(String labelString,
+          Location loc) throws EvalException, ConversionException {
           try {
             return labelCache.get(labelString);
           } catch (ExecutionException e) {
             throw new EvalException(loc, "Illegal absolute label syntax: " + labelString);
           }
-        }
-      };
+      }
+    };
 
   @SkylarkSignature(name = "FileType",
       doc = "Creates a file filter from a list of strings. For example, to match files ending "
       + "with .cc or .cpp, use: <pre class=language-python>FileType([\".cc\", \".cpp\"])</pre>",
       returnType = SkylarkFileType.class,
       mandatoryPositionals = {
-      @Param(name = "types", type = SkylarkList.class, generic1 = String.class,
+      @Param(name = "types", type = SkylarkList.class, generic1 = String.class, defaultValue = "[]",
           doc = "a list of the accepted file extensions")})
-  private static final SkylarkFunction fileType = new SimpleSkylarkFunction("FileType") {
-        @Override
-        public Object call(Map<String, Object> arguments, Location loc) throws EvalException,
-            ConversionException {
-          return SkylarkFileType.of(castList(arguments.get("types"), String.class));
-        }
-      };
+  private static final BuiltinFunction fileType = new BuiltinFunction("FileType") {
+      public SkylarkFileType invoke(SkylarkList types) throws ConversionException {
+        return SkylarkFileType.of(castList(types, String.class));
+      }
+    };
 
   @SkylarkSignature(name = "to_proto",
       doc = "Creates a text message from the struct parameter. This method only works if all "
@@ -373,68 +368,69 @@
         @Param(name = "self", type = SkylarkClassObject.class,
             doc = "this struct")},
       useLocation = true)
-  private static final SkylarkFunction toProto = new SimpleSkylarkFunction("to_proto") {
-    @Override
-    public Object call(Map<String, Object> arguments, Location loc) throws EvalException,
-        ConversionException {
-      ClassObject self = (ClassObject) arguments.get("self");
-      StringBuilder sb = new StringBuilder();
-      printTextMessage(self, sb, 0, loc);
-      return sb.toString();
-    }
-
-    private void printTextMessage(ClassObject object, StringBuilder sb,
-        int indent, Location loc) throws EvalException {
-      for (String key : object.getKeys()) {
-        printTextMessage(key, object.getValue(key), sb, indent, loc);
+  private static final BuiltinFunction toProto = new BuiltinFunction("to_proto") {
+      public String invoke(SkylarkClassObject self, Location loc) throws EvalException {
+        StringBuilder sb = new StringBuilder();
+        printTextMessage(self, sb, 0, loc);
+        return sb.toString();
       }
-    }
 
-    private void printSimpleTextMessage(String key, Object value, StringBuilder sb,
-        int indent, Location loc, String container) throws EvalException {
-      if (value instanceof ClassObject) {
-        print(sb, key + " {", indent);
-        printTextMessage((ClassObject) value, sb, indent + 1, loc);
-        print(sb, "}", indent);
-      } else if (value instanceof String) {
-        print(sb, key + ": \"" + escape((String) value) + "\"", indent);
-      } else if (value instanceof Integer) {
-        print(sb, key + ": " + value, indent);
-      } else if (value instanceof Boolean) {
-        // We're relying on the fact that Java converts Booleans to Strings in the same way
-        // as the protocol buffers do.
-        print(sb, key + ": " + value, indent);
-      } else {
-        throw new EvalException(loc,
-            "Invalid text format, expected a struct, a string, a bool, or an int but got a "
-            + EvalUtils.getDataTypeName(value) + " for " + container + " '" + key + "'");
-      }
-    }
-
-    private void printTextMessage(String key, Object value, StringBuilder sb,
-        int indent, Location loc) throws EvalException {
-      if (value instanceof SkylarkList) {
-        for (Object item : ((SkylarkList) value)) {
-          // TODO(bazel-team): There should be some constraint on the fields of the structs
-          // in the same list but we ignore that for now.
-          printSimpleTextMessage(key, item, sb, indent, loc, "list element in struct field");
+      private void printTextMessage(ClassObject object, StringBuilder sb,
+          int indent, Location loc) throws EvalException {
+        for (String key : object.getKeys()) {
+          printTextMessage(key, object.getValue(key), sb, indent, loc);
         }
-      } else {
+      }
+
+      private void printSimpleTextMessage(String key, Object value, StringBuilder sb,
+          int indent, Location loc, String container) throws EvalException {
+        if (value instanceof ClassObject) {
+          print(sb, key + " {", indent);
+          printTextMessage((ClassObject) value, sb, indent + 1, loc);
+          print(sb, "}", indent);
+        } else if (value instanceof String) {
+          print(sb, key + ": \"" + escape((String) value) + "\"", indent);
+        } else if (value instanceof Integer) {
+          print(sb, key + ": " + value, indent);
+        } else if (value instanceof Boolean) {
+          // We're relying on the fact that Java converts Booleans to Strings in the same way
+          // as the protocol buffers do.
+          print(sb, key + ": " + value, indent);
+        } else {
+          throw new EvalException(loc,
+              "Invalid text format, expected a struct, a string, a bool, or an int but got a "
+              + EvalUtils.getDataTypeName(value) + " for " + container + " '" + key + "'");
+        }
+      }
+
+      private void printTextMessage(String key, Object value, StringBuilder sb,
+          int indent, Location loc) throws EvalException {
+        if (value instanceof SkylarkList) {
+          for (Object item : ((SkylarkList) value)) {
+            // TODO(bazel-team): There should be some constraint on the fields of the structs
+            // in the same list but we ignore that for now.
+            printSimpleTextMessage(key, item, sb, indent, loc, "list element in struct field");
+          }
+        } else {
         printSimpleTextMessage(key, value, sb, indent, loc, "struct field");
+        }
       }
-    }
 
-    private String escape(String string) {
-      // TODO(bazel-team): use guava's SourceCodeEscapers when it's released.
-      return string.replace("\"", "\\\"").replace("\n", "\\n");
-    }
-
-    private void print(StringBuilder sb, String text, int indent) {
-      for (int i = 0; i < indent; i++) {
-        sb.append("  ");
+      private String escape(String string) {
+        // TODO(bazel-team): use guava's SourceCodeEscapers when it's released.
+        return string.replace("\"", "\\\"").replace("\n", "\\n");
       }
+
+      private void print(StringBuilder sb, String text, int indent) {
+        for (int i = 0; i < indent; i++) {
+          sb.append("  ");
+        }
       sb.append(text);
       sb.append("\n");
-    }
-  };
+      }
+    };
+
+  static {
+    SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkRuleClassFunctions.class);
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java
index 0019fe8..03e2bce 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java
@@ -32,18 +32,19 @@
 import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.packages.Type.ConversionException;
+import com.google.devtools.build.lib.syntax.BuiltinFunction;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.Label;
-import com.google.devtools.build.lib.syntax.SkylarkFunction;
-import com.google.devtools.build.lib.syntax.SkylarkFunction.SimpleSkylarkFunction;
 import com.google.devtools.build.lib.syntax.SkylarkList;
 import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
 import com.google.devtools.build.lib.syntax.SkylarkSignature;
 import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
+import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
 import com.google.devtools.build.lib.vfs.PathFragment;
 
+import java.util.Arrays;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 
@@ -106,75 +107,79 @@
             doc = "sets the map of input manifests files; "
             + "they are typicially generated by the command_helper")},
       useLocation = true)
-  private static final SkylarkFunction createSpawnAction =
-      new SimpleSkylarkFunction("action") {
-
-    @Override
-    public Object call(Map<String, Object> params, Location loc) throws EvalException,
-        ConversionException {
-      SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
+  private static final BuiltinFunction createSpawnAction = new BuiltinFunction("action") {
+    public Environment.NoneType invoke(
+        SkylarkRuleContext ctx,
+        SkylarkList outputs,
+        SkylarkList inputs,
+        Object executableO,
+        SkylarkList arguments,
+        Object mnemonicO,
+        Object commandO,
+        Object progressMessage,
+        Boolean useDefaultShellEnv,
+        Object envO,
+        Object executionRequirementsO,
+        Object inputManifestsO,
+        Location loc) throws EvalException, ConversionException {
       SpawnAction.Builder builder = new SpawnAction.Builder();
       // TODO(bazel-team): builder still makes unnecessary copies of inputs, outputs and args.
-      builder.addInputs(castList(params.get("inputs"), Artifact.class));
-      builder.addOutputs(castList(params.get("outputs"), Artifact.class));
-      builder.addArguments(castList(params.get("arguments"), String.class));
-      if (params.containsKey("executable")) {
-        Object exe = params.get("executable");
-        if (exe instanceof Artifact) {
-          Artifact executable = (Artifact) exe;
+      builder.addInputs(castList(inputs, Artifact.class));
+      builder.addOutputs(castList(outputs, Artifact.class));
+      builder.addArguments(castList(arguments, String.class));
+      if (executableO != Environment.NONE) {
+        if (executableO instanceof Artifact) {
+          Artifact executable = (Artifact) executableO;
           builder.addInput(executable);
           FilesToRunProvider provider = ctx.getExecutableRunfiles(executable);
           if (provider == null) {
-            builder.setExecutable((Artifact) exe);
+            builder.setExecutable(executable);
           } else {
             builder.setExecutable(provider);
           }
-        } else if (exe instanceof PathFragment) {
-          builder.setExecutable((PathFragment) exe);
+        } else if (executableO instanceof PathFragment) {
+          builder.setExecutable((PathFragment) executableO);
         } else {
           throw new EvalException(loc, "expected file or PathFragment for "
-              + "executable but got " + EvalUtils.getDataTypeName(exe) + " instead");
+              + "executable but got " + EvalUtils.getDataTypeName(executableO) + " instead");
         }
       }
-      if (params.containsKey("command") == params.containsKey("executable")) {
+      if ((commandO == Environment.NONE) == (executableO == Environment.NONE)) {
         throw new EvalException(loc, "You must specify either 'command' or 'executable' argument");
       }
-      if (params.containsKey("command")) {
-        Object command = params.get("command");
-        if (command instanceof String) {
-          builder.setShellCommand((String) command);
-        } else if (command instanceof SkylarkList) {
-          SkylarkList commandList = (SkylarkList) command;
+      if (commandO != Environment.NONE) {
+        if (commandO instanceof String) {
+          builder.setShellCommand((String) commandO);
+        } else if (commandO instanceof SkylarkList) {
+          SkylarkList commandList = (SkylarkList) commandO;
           if (commandList.size() < 3) {
             throw new EvalException(loc, "'command' list has to be of size at least 3");
           }
           builder.setShellCommand(castList(commandList, String.class, "command"));
         } else {
           throw new EvalException(loc, "expected string or list of strings for "
-              + "command instead of " + EvalUtils.getDataTypeName(command));
+              + "command instead of " + EvalUtils.getDataTypeName(commandO));
         }
       }
-      if (params.containsKey("mnemonic")) {
-        builder.setMnemonic((String) params.get("mnemonic"));
+      if (mnemonicO != Environment.NONE) {
+        builder.setMnemonic((String) mnemonicO);
       }
-      if (params.containsKey("env")) {
+      if (envO != Environment.NONE) {
         builder.setEnvironment(ImmutableMap.copyOf(
-            castMap(params.get("env"), String.class, String.class, "env")));
+            castMap(envO, String.class, String.class, "env")));
       }
-      if (params.containsKey("progress_message")) {
-        builder.setProgressMessage((String) params.get("progress_message"));
+      if (progressMessage != Environment.NONE) {
+        builder.setProgressMessage((String) progressMessage);
       }
-      if (params.containsKey("use_default_shell_env")
-          && EvalUtils.toBoolean(params.get("use_default_shell_env"))) {
+      if (EvalUtils.toBoolean(useDefaultShellEnv)) {
         builder.useDefaultShellEnvironment();
       }
-      if (params.containsKey("execution_requirements")) {
+      if (executionRequirementsO != Environment.NONE) {
         builder.setExecutionInfo(ImmutableMap.copyOf(castMap(
-            params.get("execution_requirements"),
-            String.class, String.class, "execution_requirements")));
+            executionRequirementsO, String.class, String.class, "execution_requirements")));
       }
-      if (params.containsKey("input_manifests")) {
-        for (Map.Entry<PathFragment, Artifact> entry : castMap(params.get("input_manifests"),
+      if (inputManifestsO != Environment.NONE) {
+        for (Map.Entry<PathFragment, Artifact> entry : castMap(inputManifestsO,
             PathFragment.class, Artifact.class, "input manifest file map").entrySet()) {
           builder.addInputManifest(entry.getValue(), entry.getKey());
         }
@@ -195,25 +200,19 @@
         @Param(name = "output", type = Artifact.class, doc = "the output file"),
         @Param(name = "content", type = String.class, doc = "the contents of the file")},
       optionalPositionals = {
-        @Param(name = "executable", type = Boolean.class,
+        @Param(name = "executable", type = Boolean.class, defaultValue = "False",
             doc = "whether the output file should be executable (default is False)")})
-  private static final SkylarkFunction createFileWriteAction =
-    new SimpleSkylarkFunction("file_action") {
-
-    @Override
-    public Object call(Map<String, Object> params, Location loc) throws EvalException,
-        ConversionException {
-      SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
-      boolean executable = params.containsKey("executable") && (Boolean) params.get("executable");
-      FileWriteAction action = new FileWriteAction(
-          ctx.getRuleContext().getActionOwner(),
-          (Artifact) params.get("output"),
-          (String) params.get("content"),
-          executable);
-      ctx.getRuleContext().registerAction(action);
-      return action;
-    }
-  };
+  private static final BuiltinFunction createFileWriteAction =
+      new BuiltinFunction("file_action") {
+        public FileWriteAction invoke(SkylarkRuleContext ctx,
+            Artifact output, String content, Boolean executable)
+            throws EvalException, ConversionException {
+          FileWriteAction action = new FileWriteAction(
+              ctx.getRuleContext().getActionOwner(), output, content, executable);
+          ctx.getRuleContext().registerAction(action);
+          return action;
+        }
+      };
 
   @SkylarkSignature(name = "template_action",
       doc = "Creates a template expansion action.",
@@ -231,30 +230,26 @@
       optionalNamedOnly = {
         @Param(name = "executable", type = Boolean.class,
             doc = "whether the output file should be executable (default is False)")})
-  private static final SkylarkFunction createTemplateAction =
-    new SimpleSkylarkFunction("template_action") {
-
-    @Override
-    public Object call(Map<String, Object> params, Location loc) throws EvalException,
-        ConversionException {
-      SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
-      ImmutableList.Builder<Substitution> substitutions = ImmutableList.builder();
-      for (Map.Entry<String, String> substitution : castMap(
-          params.get("substitutions"), String.class, String.class, "substitutions").entrySet()) {
-        substitutions.add(Substitution.of(substitution.getKey(), substitution.getValue()));
-      }
-
-      boolean executable = params.containsKey("executable") && (Boolean) params.get("executable");
-      TemplateExpansionAction action = new TemplateExpansionAction(
-          ctx.getRuleContext().getActionOwner(),
-          (Artifact) params.get("template"),
-          (Artifact) params.get("output"),
-          substitutions.build(),
-          executable);
-      ctx.getRuleContext().registerAction(action);
-      return action;
-    }
-  };
+  private static final BuiltinFunction createTemplateAction =
+      new BuiltinFunction("template_action", Arrays.<Object>asList(false)) {
+        public TemplateExpansionAction invoke(SkylarkRuleContext ctx,
+            Artifact template, Artifact output, Map<?, ?> substitutionsO, Boolean executable)
+            throws EvalException, ConversionException {
+          ImmutableList.Builder<Substitution> substitutions = ImmutableList.builder();
+          for (Map.Entry<String, String> substitution : castMap(
+              substitutionsO, String.class, String.class, "substitutions").entrySet()) {
+            substitutions.add(Substitution.of(substitution.getKey(), substitution.getValue()));
+          }
+          TemplateExpansionAction action = new TemplateExpansionAction(
+              ctx.getRuleContext().getActionOwner(),
+              template,
+              output,
+              substitutions.build(),
+              executable);
+          ctx.getRuleContext().registerAction(action);
+          return action;
+        }
+      };
 
   /**
    * A built in Skylark helper function to access the
@@ -267,11 +262,9 @@
             doc = "the configured target which provides the provider"),
         @Param(name = "type", type = String.class, doc = "the class type of the provider")},
       useLocation = true)
-  private static final SkylarkFunction provider = new SimpleSkylarkFunction("provider") {
-    @Override
-    public Object call(Map<String, Object> params, Location loc) throws EvalException {
-      TransitiveInfoCollection target = (TransitiveInfoCollection) params.get("target");
-      String type = (String) params.get("type");
+  private static final BuiltinFunction provider = new BuiltinFunction("provider") {
+      public Object invoke(TransitiveInfoCollection target, String type,
+          Location loc) throws EvalException {
       try {
         Class<?> classType = SkylarkRuleContext.classCache.get(type);
         Class<? extends TransitiveInfoProvider> convertedClass =
@@ -309,24 +302,22 @@
             doc = "Whether to collect the default "
             + "runfiles from the dependencies in srcs, data and deps attributes.")},
       useLocation = true)
-  private static final SkylarkFunction runfiles = new SimpleSkylarkFunction("runfiles") {
-    @Override
-    public Object call(Map<String, Object> params, Location loc) throws EvalException,
-        ConversionException {
-      SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
+  private static final BuiltinFunction runfiles = new BuiltinFunction("runfiles") {
+    public Runfiles invoke(SkylarkRuleContext ctx, SkylarkList files, Object transitiveFiles,
+        Boolean collectData, Boolean collectDefault,
+        Location loc) throws EvalException, ConversionException {
       Runfiles.Builder builder = new Runfiles.Builder();
-      if (params.containsKey("collect_data") && (Boolean) params.get("collect_data")) {
+      if (EvalUtils.toBoolean(collectData)) {
         builder.addRunfiles(ctx.getRuleContext(), RunfilesProvider.DATA_RUNFILES);
       }
-      if (params.containsKey("collect_default") && (Boolean) params.get("collect_default")) {
+      if (EvalUtils.toBoolean(collectDefault)) {
         builder.addRunfiles(ctx.getRuleContext(), RunfilesProvider.DEFAULT_RUNFILES);
       }
-      if (params.containsKey("files")) {
-        builder.addArtifacts(castList(params.get("files"), Artifact.class));
+      if (!files.isEmpty()) {
+        builder.addArtifacts(castList(files, Artifact.class));
       }
-      if (params.containsKey("transitive_files")) {
-        builder.addTransitiveArtifacts(((SkylarkNestedSet) params.get("transitive_files"))
-            .getSet(Artifact.class));
+      if (transitiveFiles != Environment.NONE) {
+        builder.addTransitiveArtifacts(((SkylarkNestedSet) transitiveFiles).getSet(Artifact.class));
       }
       return builder.build();
     }
@@ -342,20 +333,22 @@
         @Param(name = "label_dict", type = Map.class, defaultValue = "{}",
             doc = "dictionary of resolved labels and the corresponding list of Files "
             + "(a dict of Label : list of Files)")})
-  private static final SkylarkFunction createCommandHelper =
-      new SimpleSkylarkFunction("command_helper") {
-        @SuppressWarnings("unchecked")
-        @Override
-        protected Object call(Map<String, Object> params, Location loc)
-            throws ConversionException, EvalException {
-          SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self");
-          return new CommandHelper(ctx.getRuleContext(),
-              AnalysisUtils.getProviders(
-                  castList(params.get("tools"), TransitiveInfoCollection.class),
-                  FilesToRunProvider.class),
-              // TODO(bazel-team): this cast to Map is unchecked and is not safe.
-              // The best way to fix this probably is to convert CommandHelper to Skylark.
-              ImmutableMap.copyOf((Map<Label, Iterable<Artifact>>) params.get("label_dict")));
-        }
-      };
+  private static final BuiltinFunction createCommandHelper = new BuiltinFunction("command_helper") {
+      @SuppressWarnings("unchecked")
+      // TODO(bazel-team): this cast to Map is unchecked and is not safe.
+      // The best way to fix this probably is to convert CommandHelper to Skylark.
+      public CommandHelper invoke(
+          SkylarkRuleContext ctx, SkylarkList tools, Map<Label, Iterable<Artifact>> labelDict)
+          throws ConversionException, EvalException {
+        return new CommandHelper(ctx.getRuleContext(),
+            AnalysisUtils.getProviders(
+                castList(tools, TransitiveInfoCollection.class),
+                FilesToRunProvider.class),
+            ImmutableMap.copyOf(labelDict));
+      }
+    };
+
+  static {
+    SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkRuleImplementationFunctions.class);
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java
index 08ea8ee..af33276 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java
@@ -205,8 +205,8 @@
     Preconditions.checkState(!isConfigured()); // must not be configured yet
     enforcedArgumentTypes = new ArrayList<>();
     this.extraArgs = SkylarkSignatureProcessor.getExtraArgs(annotation);
-    super.configure(annotation);
     this.returnType = annotation.returnType();
+    super.configure(annotation);
   }
 
   // finds the method and makes it accessible (which is needed to find it, and later to use it)
@@ -273,7 +273,10 @@
       if (type == HackHackEitherList.class) {
         type = Object.class;
       }
-      Preconditions.checkArgument(type == invokeMethod.getReturnType());
+      Class<?> methodReturnType = invokeMethod.getReturnType();
+      Preconditions.checkArgument(type == methodReturnType,
+          "signature for function %s says it returns %s but its invoke method returns %s",
+          getName(), returnType, methodReturnType);
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java
index 2e94be8..bff06d5 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java
@@ -15,17 +15,16 @@
 
 import com.google.common.collect.ImmutableList;
 
-
 /**
  * A helper class for calling Skylark functions from Java.
  */
 public class SkylarkCallbackFunction {
 
-  private final UserDefinedFunction callback;
+  private final Function callback;
   private final FuncallExpression ast;
   private final SkylarkEnvironment funcallEnv;
 
-  public SkylarkCallbackFunction(UserDefinedFunction callback, FuncallExpression ast,
+  public SkylarkCallbackFunction(Function callback, FuncallExpression ast,
       SkylarkEnvironment funcallEnv) {
     this.callback = callback;
     this.ast = ast;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java
index b264dd6..11e3522 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java
@@ -205,8 +205,8 @@
     List<Class<?>> modulesToRemove = new ArrayList<>();
     for (Map.Entry<String, Object> entry : env.entrySet()) {
       Object object = entry.getValue();
-      if (object instanceof SkylarkFunction) {
-        if (((SkylarkFunction) object).isOnlyLoadingPhase()) {
+      if (object instanceof BaseFunction) {
+        if (((BaseFunction) object).isOnlyLoadingPhase()) {
           objectsToRemove.add(entry.getKey());
         }
       } else if (object.getClass().isAnnotationPresent(SkylarkModule.class)) {