Migrate SkylarkNativeModule methods to use @SkylarkCallable instead of @SkylarkSignature

Most notably, this involves introduction of a new function abstraction, BuiltinMethod, which can wrap a {objc, SkylarkCallable} pair into a BaseFunction for later calling. (This is required due to the current layer of indirection on the end "native" module)

RELNOTES: None.
PiperOrigin-RevId: 191642467
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 0456b44..750ccf2 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
@@ -1360,7 +1360,7 @@
     }
     byte[] buildFileBytes = maybeGetBuildFileBytes(buildFile, eventHandler);
     if (buildFileBytes == null) {
-      throw new BuildFileContainsErrorsException(packageId, "IOException occured");
+      throw new BuildFileContainsErrorsException(packageId, "IOException occurred");
     }
 
     Globber globber = createLegacyGlobber(buildFile.getParentDirectory(), packageId, locator);
@@ -1507,9 +1507,10 @@
    */
   private ClassObject newNativeModule() {
     ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
-    Runtime.BuiltinRegistry builtins = Runtime.getBuiltinRegistry();
-    for (String nativeFunction : builtins.getFunctionNames(SkylarkNativeModule.class)) {
-      builder.put(nativeFunction, builtins.getFunction(SkylarkNativeModule.class, nativeFunction));
+    SkylarkNativeModule nativeModuleInstance = new SkylarkNativeModule();
+    for (String nativeFunction : FuncallExpression.getMethodNames(SkylarkNativeModule.class)) {
+      builder.put(nativeFunction,
+          FuncallExpression.getBuiltinCallable(nativeModuleInstance, nativeFunction));
     }
     builder.putAll(ruleFunctions);
     builder.put("package", newPackageFunction(packageArguments));
@@ -1529,6 +1530,17 @@
     // or if not possible, at least make them straight copies from the native module variant.
     // or better, use a common Environment.Frame for these common bindings
     // (that shares a backing ImmutableMap for the bindings?)
+    Object packageNameFunction;
+    Object repositoryNameFunction;
+    try {
+      packageNameFunction = nativeModule.getValue("package_name");
+      repositoryNameFunction = nativeModule.getValue("repository_name");
+    } catch (EvalException exception) {
+      // This should not occur, as nativeModule.getValue should never throw an exception.
+      throw new IllegalStateException(
+          "error getting package_name or repository_name functions from the native module",
+          exception);
+    }
     pkgEnv
         .setup("native", nativeModule)
         .setup("distribs", newDistribsFunction.apply(context))
@@ -1537,8 +1549,8 @@
         .setup("exports_files", newExportsFilesFunction.apply())
         .setup("package_group", newPackageGroupFunction.apply())
         .setup("package", newPackageFunction(packageArguments))
-        .setup("package_name", SkylarkNativeModule.packageName)
-        .setup("repository_name", SkylarkNativeModule.repositoryName)
+        .setup("package_name", packageNameFunction)
+        .setup("repository_name", repositoryNameFunction)
         .setup("environment_group", newEnvironmentGroupFunction.apply(context));
 
     for (Entry<String, BuiltinRuleFunction> entry : ruleFunctions.entrySet()) {
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 0f23a59..4322032 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
@@ -17,17 +17,15 @@
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.skylarkinterface.Param;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
-import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
-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.Runtime;
 import com.google.devtools.build.lib.syntax.SkylarkDict;
 import com.google.devtools.build.lib.syntax.SkylarkList;
-import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
 import com.google.devtools.build.lib.syntax.Type.ConversionException;
 
 /**
@@ -36,7 +34,6 @@
  */
 @SkylarkModule(
   name = "native",
-  namespace = true,
   category = SkylarkModuleCategory.BUILTIN,
   doc =
       "A built-in module to support native rules and other package helper functions. "
@@ -49,10 +46,8 @@
 )
 public class SkylarkNativeModule {
 
-  @SkylarkSignature(
+  @SkylarkCallable(
     name = "glob",
-    objectType = SkylarkNativeModule.class,
-    returnType = SkylarkList.class,
     doc =
         "Glob returns a list of every file in the current package that:<ul>\n"
             + "<li>Matches at least one pattern in <code>include</code>.</li>\n"
@@ -86,24 +81,19 @@
     useAst = true,
     useEnvironment = true
   )
-  private static final BuiltinFunction glob =
-      new BuiltinFunction("glob") {
-        public SkylarkList invoke(
-            SkylarkList include,
-            SkylarkList exclude,
-            Integer excludeDirectories,
-            FuncallExpression ast,
-            Environment env)
-            throws EvalException, ConversionException, InterruptedException {
-          env.checkLoadingPhase("native.glob", ast.getLocation());
-          return PackageFactory.callGlob(null, include, exclude, excludeDirectories != 0, ast, env);
-        }
-      };
+  public SkylarkList<?> glob(
+      SkylarkList<?> include,
+      SkylarkList<?> exclude,
+      Integer excludeDirectories,
+      FuncallExpression ast,
+      Environment env)
+      throws EvalException, ConversionException, InterruptedException {
+    env.checkLoadingPhase("native.glob", ast.getLocation());
+    return PackageFactory.callGlob(null, include, exclude, excludeDirectories != 0, ast, env);
+  }
 
-  @SkylarkSignature(
+  @SkylarkCallable(
     name = "existing_rule",
-    objectType = SkylarkNativeModule.class,
-    returnType = Object.class,
     doc =
         "Returns a dictionary representing the attributes of a previously defined rule, "
             + "or None if the rule does not exist.",
@@ -113,48 +103,38 @@
     useAst = true,
     useEnvironment = true
   )
-  private static final BuiltinFunction existingRule =
-      new BuiltinFunction("existing_rule") {
-        public Object invoke(String name, FuncallExpression ast, Environment env)
-            throws EvalException, InterruptedException {
-          env.checkLoadingOrWorkspacePhase("native.existing_rule", ast.getLocation());
-          SkylarkDict<String, Object> rule = PackageFactory.callGetRuleFunction(name, ast, env);
-          if (rule != null) {
-            return rule;
-          }
+  public Object existingRule(String name, FuncallExpression ast, Environment env)
+      throws EvalException, InterruptedException {
+    env.checkLoadingOrWorkspacePhase("native.existing_rule", ast.getLocation());
+    SkylarkDict<String, Object> rule = PackageFactory.callGetRuleFunction(name, ast, env);
+    if (rule != null) {
+      return rule;
+    }
 
-          return Runtime.NONE;
-        }
-      };
+    return Runtime.NONE;
+  }
 
   /*
     If necessary, we could allow filtering by tag (anytag, alltags), name (regexp?), kind ?
     For now, we ignore this, since users can implement it in Skylark.
   */
-  @SkylarkSignature(
+  @SkylarkCallable(
     name = "existing_rules",
-    objectType = SkylarkNativeModule.class,
-    returnType = SkylarkDict.class,
     doc =
         "Returns a dict containing all the rules instantiated so far. "
             + "The map key is the name of the rule. The map value is equivalent to the "
             + "existing_rule output for that rule.",
-    parameters = {},
     useAst = true,
     useEnvironment = true
   )
-  private static final BuiltinFunction existingRules =
-      new BuiltinFunction("existing_rules") {
-        public SkylarkDict<String, SkylarkDict<String, Object>> invoke(
-            FuncallExpression ast, Environment env)
-            throws EvalException, InterruptedException {
-          env.checkLoadingOrWorkspacePhase("native.existing_rules", ast.getLocation());
-          return PackageFactory.callGetRulesFunction(ast, env);
-        }
-      };
+  public SkylarkDict<String, SkylarkDict<String, Object>> existingRules(
+      FuncallExpression ast, Environment env)
+      throws EvalException, InterruptedException {
+    env.checkLoadingOrWorkspacePhase("native.existing_rules", ast.getLocation());
+    return PackageFactory.callGetRulesFunction(ast, env);
+  }
 
-  @SkylarkSignature(name = "package_group", objectType = SkylarkNativeModule.class,
-      returnType = Runtime.NoneType.class,
+  @SkylarkCallable(name = "package_group",
       doc = "This function defines a set of packages and assigns a label to the group. "
           + "The label can be referenced in <code>visibility</code> attributes.",
       parameters = {
@@ -167,19 +147,17 @@
           defaultValue = "[]", named = true, positional = false,
           doc = "Other package groups that are included in this one.")},
       useAst = true, useEnvironment = true)
-  private static final BuiltinFunction packageGroup = new BuiltinFunction("package_group") {
-      public Runtime.NoneType invoke(String name, SkylarkList packages, SkylarkList includes,
-                FuncallExpression ast, Environment env) throws EvalException, ConversionException {
-        env.checkLoadingPhase("native.package_group", ast.getLocation());
-        return PackageFactory.callPackageFunction(name, packages, includes, ast, env);
-      }
-    };
+  public Runtime.NoneType packageGroup(String name, SkylarkList<?> packages,
+      SkylarkList<?> includes,
+      FuncallExpression ast, Environment env) throws EvalException {
+    env.checkLoadingPhase("native.package_group", ast.getLocation());
+    return PackageFactory.callPackageFunction(name, packages, includes, ast, env);
+  }
 
-  @SkylarkSignature(name = "exports_files", objectType = SkylarkNativeModule.class,
-      returnType = Runtime.NoneType.class,
-      doc = "Specifies a list of files belonging to this package that are exported to other "
-          + "packages but not otherwise mentioned.",
-      parameters = {
+  @SkylarkCallable(name = "exports_files",
+    doc = "Specifies a list of files belonging to this package that are exported to other "
+        + "packages but not otherwise mentioned.",
+    parameters = {
       @Param(name = "srcs", type = SkylarkList.class, generic1 = String.class,
           doc = "The list of files to export."),
       // TODO(bazel-team): make it possible to express the precise type ListOf(LabelDesignator)
@@ -189,20 +167,16 @@
               + "every package."),
       @Param(name = "licenses", type = SkylarkList.class, generic1 = String.class, noneable = true,
           defaultValue = "None", doc = "Licenses to be specified.")},
-      useAst = true, useEnvironment = true)
-  private static final BuiltinFunction exportsFiles = new BuiltinFunction("exports_files") {
-      public Runtime.NoneType invoke(SkylarkList srcs, Object visibility, Object licenses,
-          FuncallExpression ast, Environment env)
-          throws EvalException, ConversionException {
-        env.checkLoadingPhase("native.exports_files", ast.getLocation());
-        return PackageFactory.callExportsFiles(srcs, visibility, licenses, ast, env);
-      }
-    };
+    useAst = true, useEnvironment = true)
+  public Runtime.NoneType exportsFiles(SkylarkList<?> srcs, Object visibility, Object licenses,
+      FuncallExpression ast, Environment env)
+      throws EvalException {
+    env.checkLoadingPhase("native.exports_files", ast.getLocation());
+    return PackageFactory.callExportsFiles(srcs, visibility, licenses, ast, env);
+  }
 
-  @SkylarkSignature(
+  @SkylarkCallable(
     name = "package_name",
-    objectType = SkylarkNativeModule.class,
-    returnType = String.class,
     doc =
         "The name of the package being evaluated. "
             + "For example, in the BUILD file <code>some/package/BUILD</code>, its value "
@@ -214,21 +188,16 @@
     useAst = true,
     useEnvironment = true
   )
-  static final BuiltinFunction packageName =
-      new BuiltinFunction("package_name") {
-        public String invoke(FuncallExpression ast, Environment env)
-            throws EvalException, ConversionException {
-          env.checkLoadingPhase("native.package_name", ast.getLocation());
-          PackageIdentifier packageId =
-              PackageFactory.getContext(env, ast.getLocation()).getBuilder().getPackageIdentifier();
-          return packageId.getPackageFragment().getPathString();
-        }
-      };
+  public String packageName(FuncallExpression ast, Environment env)
+      throws EvalException {
+    env.checkLoadingPhase("native.package_name", ast.getLocation());
+    PackageIdentifier packageId =
+        PackageFactory.getContext(env, ast.getLocation()).getBuilder().getPackageIdentifier();
+    return packageId.getPackageFragment().getPathString();
+  }
 
-  @SkylarkSignature(
+  @SkylarkCallable(
     name = "repository_name",
-    objectType = SkylarkNativeModule.class,
-    returnType = String.class,
     doc =
         "The name of the repository the rule or build extension is called from. "
             + "For example, in packages that are called into existence by the WORKSPACE stanza "
@@ -240,20 +209,11 @@
     useLocation = true,
     useEnvironment = true
   )
-  static final BuiltinFunction repositoryName =
-      new BuiltinFunction("repository_name") {
-        public String invoke(Location location, Environment env)
-            throws EvalException, ConversionException {
-          env.checkLoadingPhase("native.repository_name", location);
-          PackageIdentifier packageId =
-              PackageFactory.getContext(env, location).getBuilder().getPackageIdentifier();
-          return packageId.getRepository().toString();
-        }
-      };
-
-  public static final SkylarkNativeModule NATIVE_MODULE = new SkylarkNativeModule();
-
-  static {
-    SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkNativeModule.class);
+  public String repositoryName(Location location, Environment env)
+      throws EvalException {
+    env.checkLoadingPhase("native.repository_name", location);
+    PackageIdentifier packageId =
+        PackageFactory.getContext(env, location).getBuilder().getPackageIdentifier();
+    return packageId.getRepository().toString();
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
index 4e7af5f..6868a98 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
@@ -46,7 +46,6 @@
 import com.google.devtools.build.lib.syntax.FunctionSignature;
 import com.google.devtools.build.lib.syntax.Mutability;
 import com.google.devtools.build.lib.syntax.ParserInputSource;
-import com.google.devtools.build.lib.syntax.Runtime;
 import com.google.devtools.build.lib.syntax.Runtime.NoneType;
 import com.google.devtools.build.lib.syntax.SkylarkList;
 import com.google.devtools.build.lib.syntax.SkylarkSemantics;
@@ -551,9 +550,10 @@
   private static ClassObject newNativeModule(
       ImmutableMap<String, BaseFunction> workspaceFunctions, String version) {
     ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
-    Runtime.BuiltinRegistry builtins = Runtime.getBuiltinRegistry();
-    for (String nativeFunction : builtins.getFunctionNames(SkylarkNativeModule.class)) {
-      builder.put(nativeFunction, builtins.getFunction(SkylarkNativeModule.class, nativeFunction));
+    SkylarkNativeModule nativeModuleInstance = new SkylarkNativeModule();
+    for (String nativeFunction : FuncallExpression.getMethodNames(SkylarkNativeModule.class)) {
+      builder.put(nativeFunction,
+          FuncallExpression.getBuiltinCallable(nativeModuleInstance, nativeFunction));
     }
     for (Map.Entry<String, BaseFunction> function : workspaceFunctions.entrySet()) {
       builder.put(function.getKey(), function.getValue());
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
index e445e46..bad903c3 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
@@ -487,7 +487,7 @@
     Preconditions.checkState(!isConfigured()); // must not be configured yet
 
     this.paramDoc = new ArrayList<>();
-    this.signature = SkylarkSignatureProcessor.getSignature(
+    this.signature = SkylarkSignatureProcessor.getSignatureForCallable(
         getName(), annotation, unconfiguredDefaultValues, paramDoc, getEnforcedArgumentTypes());
     this.objectType = annotation.objectType().equals(Object.class)
         ? null : annotation.objectType();
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java
new file mode 100644
index 0000000..8419e7f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java
@@ -0,0 +1,137 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.syntax;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.profiler.Profiler;
+import com.google.devtools.build.lib.profiler.ProfilerTask;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
+import com.google.devtools.build.lib.syntax.Environment.LexicalFrame;
+import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * A function-object abstraction on object methods exposed to skylark using {@link SkylarkCallable}.
+ */
+public class BuiltinCallable extends BaseFunction {
+
+  /** Represents a required interpreter parameter as dictated by {@link SkylarkCallable} */
+  public enum ExtraArgKind {
+    LOCATION, // SkylarkCallable.useLocation
+    SYNTAX_TREE, // SkylarkCallable.useAst
+    ENVIRONMENT, // SkylarkCallable.useEnvironment
+    SEMANTICS; // SkylarkCallable.useSemantics
+  }
+
+  // Builtins cannot create or modify variable bindings. So it's sufficient to use a shared
+  // instance.
+  private static final LexicalFrame SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS =
+      LexicalFrame.create(Mutability.IMMUTABLE);
+
+  private final Object obj;
+  private final MethodDescriptor descriptor;
+  private final List<ExtraArgKind> extraArgs;
+  private int innerArgumentCount;
+
+  public BuiltinCallable(String name, Object obj, MethodDescriptor descriptor) {
+    super(name);
+    this.obj = obj;
+    this.descriptor = descriptor;
+    this.extraArgs = getExtraArgs(descriptor.getAnnotation());
+    configure(obj, descriptor);
+  }
+
+  @Override
+  protected int getArgArraySize () {
+    return innerArgumentCount;
+  }
+
+  private static List<ExtraArgKind> getExtraArgs(SkylarkCallable annotation) {
+    ImmutableList.Builder<ExtraArgKind> extraArgs = ImmutableList.builder();
+    if (annotation.useLocation()) {
+      extraArgs.add(ExtraArgKind.LOCATION);
+    }
+    if (annotation.useAst()) {
+      extraArgs.add(ExtraArgKind.SYNTAX_TREE);
+    }
+    if (annotation.useEnvironment()) {
+      extraArgs.add(ExtraArgKind.ENVIRONMENT);
+    }
+    if (annotation.useSkylarkSemantics()) {
+      extraArgs.add(ExtraArgKind.SEMANTICS);
+    }
+    return extraArgs.build();
+  }
+
+  /** Configure a BaseFunction from a @SkylarkCallable-annotated method */
+  private void configure(Object obj, MethodDescriptor descriptor) {
+    Preconditions.checkState(!isConfigured()); // must not be configured yet
+
+    this.paramDoc = new ArrayList<>();
+    this.signature = SkylarkSignatureProcessor.getSignatureForCallable(
+        getName(), descriptor, paramDoc, getEnforcedArgumentTypes());
+    this.objectType = obj.getClass();
+    this.innerArgumentCount = signature.getSignature().getShape().getArguments() + extraArgs.size();
+    configure();
+  }
+
+  @Override
+  @Nullable
+  public Object call(Object[] args,
+      FuncallExpression ast, Environment env)
+      throws EvalException, InterruptedException {
+    Preconditions.checkNotNull(env);
+
+    // ast is null when called from Java (as there's no Skylark call site).
+    Location loc = ast == null ? Location.BUILTIN : ast.getLocation();
+
+    // Add extra arguments, if needed
+    int index = args.length - extraArgs.size();
+    for (ExtraArgKind extraArg : extraArgs) {
+      switch(extraArg) {
+        case LOCATION:
+          args[index] = loc;
+          break;
+
+        case SYNTAX_TREE:
+          args[index] = ast;
+          break;
+
+        case ENVIRONMENT:
+          args[index] = env;
+          break;
+
+        case SEMANTICS:
+          args[index] = env.getSemantics();
+          break;
+      }
+      index++;
+    }
+
+    Profiler.instance().startTask(ProfilerTask.SKYLARK_BUILTIN_FN, getName());
+
+    try {
+      env.enterScope(this, SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS, ast, env.getGlobals());
+      return FuncallExpression.callMethod(
+          descriptor, getName(), obj, args, ast.getLocation(), env);
+    } finally {
+      Profiler.instance().completeTask(ProfilerTask.SKYLARK_BUILTIN_FN);
+      env.exitScope();
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
index 7a807f5..597508f 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
@@ -318,6 +318,21 @@
   }
 
   /**
+   * Returns a {@link BuiltinCallable} representing a {@link SkylarkCallable}-annotated instance
+   * method of a given object with the given method name.
+   */
+  public static BuiltinCallable getBuiltinCallable(Object obj, String methodName) {
+    Class<?> objClass = obj.getClass();
+    List<MethodDescriptor> methodDescriptors = getMethods(objClass, methodName);
+    if (methodDescriptors.size() != 1) {
+      throw new IllegalStateException(String.format(
+          "Expected exactly 1 method named '%s' in %s, but found %s",
+          methodName, objClass, methodDescriptors.size()));
+    }
+    return new BuiltinCallable(methodName, obj, methodDescriptors.get(0));
+  }
+
+  /**
    * Invokes the given structField=true method and returns the result.
    *
    * @param methodDescriptor the descriptor of the method to invoke
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
index 6424a47..a2dc39a 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
@@ -14,10 +14,13 @@
 package com.google.devtools.build.lib.syntax;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.primitives.Booleans;
 import com.google.devtools.build.lib.skylarkinterface.Param;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
 import com.google.devtools.build.lib.syntax.BuiltinFunction.ExtraArgKind;
+import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -31,8 +34,50 @@
  * to configure a given field.
  */
 public class SkylarkSignatureProcessor {
+
   /**
-   * Extracts a {@code FunctionSignature.WithValues<Object, SkylarkType>} from an annotation
+   * Extracts a {@code FunctionSignature.WithValues<Object, SkylarkType>} from a
+   * {@link SkylarkCallable}-annotated method.
+   *
+   * @param name the name of the function
+   * @param descriptor the method descriptor
+   * @param paramDoc an optional list into which to store documentation strings
+   * @param enforcedTypesList an optional list into which to store effective types to enforce
+   */
+  public static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForCallable(
+      String name, MethodDescriptor descriptor,
+      @Nullable List<String> paramDoc, @Nullable List<SkylarkType> enforcedTypesList) {
+
+    SkylarkCallable annotation = descriptor.getAnnotation();
+
+    // TODO(cparsons): Validate these properties with the annotation processor instead.
+    Preconditions.checkArgument(annotation.name().isEmpty() || name.equals(annotation.name()),
+        "%s != %s", name, annotation.name());
+    boolean documented = annotation.documented();
+    if (annotation.doc().isEmpty() && documented) {
+      throw new RuntimeException(String.format("function %s is undocumented", name));
+    }
+    ImmutableList.Builder<Parameter<Object, SkylarkType>> parameters = ImmutableList.builder();
+
+    Class<?>[] javaMethodSignatureParams = descriptor.getMethod().getParameterTypes();
+
+    for (int paramIndex = 0; paramIndex < annotation.mandatoryPositionals(); paramIndex++) {
+      Parameter<Object, SkylarkType> parameter =
+          new Parameter.Mandatory<Object, SkylarkType>("arg" + paramIndex,
+              SkylarkType.of(javaMethodSignatureParams[paramIndex]));
+      parameters.add(parameter);
+    }
+
+    return getSignatureForCallable(name, documented, parameters.build(), annotation.parameters(),
+        /*extraPositionals=*/null,
+        /*extraKeywords=*/null, /*defaultValues=*/null, paramDoc, enforcedTypesList);
+  }
+
+
+  /**
+   * Extracts a {@code FunctionSignature.WithValues<Object, SkylarkType>} from a
+   * {@link SkylarkSignature} annotation.
+   *
    * @param name the name of the function
    * @param annotation the annotation
    * @param defaultValues an optional list of default values
@@ -42,35 +87,50 @@
   // NB: the two arguments paramDoc and enforcedTypesList are used to "return" extra values via
   // side-effects, and that's ugly
   // TODO(bazel-team): use AutoValue to declare a value type to use as return value?
-  public static FunctionSignature.WithValues<Object, SkylarkType> getSignature(
+  public static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForCallable(
       String name, SkylarkSignature annotation,
       @Nullable Iterable<Object> defaultValues,
       @Nullable List<String> paramDoc, @Nullable List<SkylarkType> enforcedTypesList) {
 
     Preconditions.checkArgument(name.equals(annotation.name()),
         "%s != %s", name, annotation.name());
-    ArrayList<Parameter<Object, SkylarkType>> paramList = new ArrayList<>();
-    HashMap<String, SkylarkType> enforcedTypes =
-        enforcedTypesList == null ? null : new HashMap<>();
-
-    HashMap<String, String> doc = new HashMap<>();
     boolean documented = annotation.documented();
     if (annotation.doc().isEmpty() && documented) {
       throw new RuntimeException(String.format("function %s is undocumented", name));
     }
+    return getSignatureForCallable(name, documented,
+        /*mandatoryPositionals=*/ImmutableList.<Parameter<Object, SkylarkType>>of(),
+        annotation.parameters(),
+        annotation.extraPositionals(),
+        annotation.extraKeywords(), defaultValues, paramDoc, enforcedTypesList);
+  }
+
+  private static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForCallable(
+      String name, boolean documented,
+      ImmutableList<Parameter<Object, SkylarkType>> mandatoryPositionals,
+      Param[] parameters,
+      @Nullable Param extraPositionals, @Nullable Param extraKeywords,
+      @Nullable Iterable<Object> defaultValues,
+      @Nullable List<String> paramDoc, @Nullable List<SkylarkType> enforcedTypesList) {
+    ArrayList<Parameter<Object, SkylarkType>> paramList = new ArrayList<>();
+    paramList.addAll(mandatoryPositionals);
+    HashMap<String, SkylarkType> enforcedTypes =
+        enforcedTypesList == null ? null : new HashMap<>();
+
+    HashMap<String, String> doc = new HashMap<>();
 
     Iterator<Object> defaultValuesIterator = defaultValues == null
         ? null : defaultValues.iterator();
     try {
       boolean named = false;
-      for (Param param : annotation.parameters()) {
+      for (Param param : parameters) {
         boolean mandatory = param.defaultValue() != null && param.defaultValue().isEmpty();
         Object defaultValue = mandatory ? null : getDefaultValue(param, defaultValuesIterator);
         if (param.named() && !param.positional() && !named) {
           named = true;
           @Nullable Param starParam = null;
-          if (!annotation.extraPositionals().name().isEmpty()) {
-            starParam = annotation.extraPositionals();
+          if (extraPositionals != null && !extraPositionals.name().isEmpty()) {
+            starParam = extraPositionals;
           }
           paramList.add(getParameter(name, starParam, enforcedTypes, doc, documented,
                 /*mandatory=*/false, /*star=*/true, /*starStar=*/false, /*defaultValue=*/null));
@@ -78,14 +138,14 @@
         paramList.add(getParameter(name, param, enforcedTypes, doc, documented,
                 mandatory, /*star=*/false, /*starStar=*/false, defaultValue));
       }
-      if (!annotation.extraPositionals().name().isEmpty() && !named) {
-        paramList.add(getParameter(name, annotation.extraPositionals(), enforcedTypes, doc,
+      if (extraPositionals != null && !extraPositionals.name().isEmpty() && !named) {
+        paramList.add(getParameter(name, extraPositionals, enforcedTypes, doc,
             documented, /*mandatory=*/false, /*star=*/true, /*starStar=*/false,
             /*defaultValue=*/null));
       }
-      if (!annotation.extraKeywords().name().isEmpty()) {
+      if (extraKeywords != null && !extraKeywords.name().isEmpty()) {
         paramList.add(
-            getParameter(name, annotation.extraKeywords(), enforcedTypes, doc, documented,
+            getParameter(name, extraKeywords, enforcedTypes, doc, documented,
                 /*mandatory=*/false, /*star=*/false, /*starStar=*/true, /*defaultValue=*/null));
       }
       FunctionSignature.WithValues<Object, SkylarkType> signature =
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index ed8fb91..ee6fc42 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -163,6 +163,7 @@
       return ImmutableMap.of("a", ImmutableList.of("b", "c"));
     }
 
+    
     @SkylarkCallable(
       name = "with_params",
       documented = false,
@@ -352,6 +353,18 @@
           + ")";
     }
 
+    @SkylarkCallable(name = "proxy_methods_object",
+        doc = "Returns a struct containing all callable method objects of this mock",
+        allowReturnNones = true)
+    public ClassObject proxyMethodsObject() {
+      ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
+      for (String nativeFunction : FuncallExpression.getMethodNames(Mock.class)) {
+        builder.put(nativeFunction,
+            FuncallExpression.getBuiltinCallable(this, nativeFunction));
+      }
+      return NativeProvider.STRUCT.create(builder.build(), "no native callable '%s'");
+    }
+
     @Override
     public String toString() {
       return "<mock>";
@@ -921,6 +934,16 @@
   }
 
   @Test
+  public void testProxyMethodsObject() throws Exception {
+    new SkylarkTest()
+        .update("mock", new Mock())
+        .setUp(
+            "m = mock.proxy_methods_object()",
+            "b = m.with_params(1, True, named=True)")
+        .testLookup("b", "with_params(1, true, false, true, false, a)");
+  }
+
+  @Test
   public void testJavaCallWithPositionalAndKwargs() throws Exception {
     new SkylarkTest()
         .update("mock", new Mock())
@@ -1527,6 +1550,7 @@
             "is_empty",
             "nullfunc_failing",
             "nullfunc_working",
+            "proxy_methods_object",
             "return_bad",
             "string",
             "string_list",