Migrate Info's skylark methods to @SkylarkCallable

RELNOTES: None
PiperOrigin-RevId: 192337555
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
index 5ad5007..28ae658 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
@@ -77,7 +77,6 @@
 import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
 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.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalUtils;
@@ -93,10 +92,6 @@
 import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.syntax.Type.ConversionException;
 import com.google.devtools.build.lib.util.Pair;
-import com.google.protobuf.TextFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 
@@ -1108,200 +1103,6 @@
       documented = false)
   private static final BuiltinFunction globalFileType = fileType;
 
-  @SkylarkSignature(
-    name = "to_proto",
-    doc =
-        "Creates a text message from the struct parameter. This method only works if all "
-            + "struct elements (recursively) are strings, ints, booleans, other structs or a "
-            + "list of these types. Quotes and new lines in strings are escaped. "
-            + "Keys are iterated in the sorted order. "
-            + "Examples:<br><pre class=language-python>"
-            + "struct(key=123).to_proto()\n# key: 123\n\n"
-            + "struct(key=True).to_proto()\n# key: true\n\n"
-            + "struct(key=[1, 2, 3]).to_proto()\n# key: 1\n# key: 2\n# key: 3\n\n"
-            + "struct(key='text').to_proto()\n# key: \"text\"\n\n"
-            + "struct(key=struct(inner_key='text')).to_proto()\n"
-            + "# key {\n#   inner_key: \"text\"\n# }\n\n"
-            + "struct(key=[struct(inner_key=1), struct(inner_key=2)]).to_proto()\n"
-            + "# key {\n#   inner_key: 1\n# }\n# key {\n#   inner_key: 2\n# }\n\n"
-            + "struct(key=struct(inner_key=struct(inner_inner_key='text'))).to_proto()\n"
-            + "# key {\n#    inner_key {\n#     inner_inner_key: \"text\"\n#   }\n# }\n</pre>",
-    objectType = Info.class,
-    returnType = String.class,
-    parameters = {
-      // TODO(bazel-team): shouldn't we accept any ClassObject?
-      @Param(name = "self", type = Info.class, doc = "this struct.")
-    },
-    useLocation = true
-  )
-  private static final BuiltinFunction toProto =
-      new BuiltinFunction("to_proto") {
-        public String invoke(Info self, Location loc) throws EvalException {
-          StringBuilder sb = new StringBuilder();
-          printProtoTextMessage(self, sb, 0, loc);
-          return sb.toString();
-        }
-
-        private void printProtoTextMessage(
-            ClassObject object, StringBuilder sb, int indent, Location loc) throws EvalException {
-          // For determinism sort the fields alphabetically.
-          List<String> fields = new ArrayList<>(object.getFieldNames());
-          Collections.sort(fields);
-          for (String field : fields) {
-            printProtoTextMessage(field, object.getValue(field), sb, indent, loc);
-          }
-        }
-
-        private void printProtoTextMessage(
-            String key, Object value, StringBuilder sb, int indent, Location loc, String container)
-            throws EvalException {
-          if (value instanceof ClassObject) {
-            print(sb, key + " {", indent);
-            printProtoTextMessage((ClassObject) value, sb, indent + 1, loc);
-            print(sb, "}", indent);
-          } else if (value instanceof String) {
-            print(
-                sb,
-                key + ": \"" + escapeDoubleQuotesAndBackslashesAndNewlines((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 printProtoTextMessage(
-            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.
-              printProtoTextMessage(key, item, sb, indent, loc, "list element in struct field");
-            }
-          } else {
-            printProtoTextMessage(key, value, sb, indent, loc, "struct field");
-          }
-        }
-
-        private void print(StringBuilder sb, String text, int indent) {
-          for (int i = 0; i < indent; i++) {
-            sb.append("  ");
-          }
-          sb.append(text);
-          sb.append("\n");
-        }
-      };
-
-  /**
-   * Escapes the given string for use in proto/JSON string.
-   *
-   * <p>This escapes double quotes, backslashes, and newlines.
-   */
-  private static String escapeDoubleQuotesAndBackslashesAndNewlines(String string) {
-    return TextFormat.escapeDoubleQuotesAndBackslashes(string).replace("\n", "\\n");
-  }
-
-  @SkylarkSignature(
-    name = "to_json",
-    doc =
-        "Creates a JSON string from the struct parameter. This method only works if all "
-            + "struct elements (recursively) are strings, ints, booleans, other structs or a "
-            + "list of these types. Quotes and new lines in strings are escaped. "
-            + "Examples:<br><pre class=language-python>"
-            + "struct(key=123).to_json()\n# {\"key\":123}\n\n"
-            + "struct(key=True).to_json()\n# {\"key\":true}\n\n"
-            + "struct(key=[1, 2, 3]).to_json()\n# {\"key\":[1,2,3]}\n\n"
-            + "struct(key='text').to_json()\n# {\"key\":\"text\"}\n\n"
-            + "struct(key=struct(inner_key='text')).to_json()\n"
-            + "# {\"key\":{\"inner_key\":\"text\"}}\n\n"
-            + "struct(key=[struct(inner_key=1), struct(inner_key=2)]).to_json()\n"
-            + "# {\"key\":[{\"inner_key\":1},{\"inner_key\":2}]}\n\n"
-            + "struct(key=struct(inner_key=struct(inner_inner_key='text'))).to_json()\n"
-            + "# {\"key\":{\"inner_key\":{\"inner_inner_key\":\"text\"}}}\n</pre>",
-    objectType = Info.class,
-    returnType = String.class,
-    parameters = {
-      // TODO(bazel-team): shouldn't we accept any ClassObject?
-      @Param(name = "self", type = Info.class, doc = "this struct.")
-    },
-    useLocation = true
-  )
-  private static final BuiltinFunction toJson =
-      new BuiltinFunction("to_json") {
-        public String invoke(Info self, Location loc) throws EvalException {
-          StringBuilder sb = new StringBuilder();
-          printJson(self, sb, loc, "struct field", null);
-          return sb.toString();
-        }
-
-        private void printJson(
-            Object value, StringBuilder sb, Location loc, String container, String key)
-            throws EvalException {
-          if (value == Runtime.NONE) {
-            sb.append("null");
-          } else if (value instanceof ClassObject) {
-            sb.append("{");
-
-            String join = "";
-            for (String field : ((ClassObject) value).getFieldNames()) {
-              sb.append(join);
-              join = ",";
-              sb.append("\"");
-              sb.append(field);
-              sb.append("\":");
-              printJson(((ClassObject) value).getValue(field), sb, loc, "struct field", field);
-            }
-            sb.append("}");
-          } else if (value instanceof List) {
-            sb.append("[");
-            String join = "";
-            for (Object item : ((List) value)) {
-              sb.append(join);
-              join = ",";
-              printJson(item, sb, loc, "list element in struct field", key);
-            }
-            sb.append("]");
-          } else if (value instanceof String) {
-            sb.append("\"");
-            sb.append(jsonEscapeString((String) value));
-            sb.append("\"");
-          } else if (value instanceof Integer || value instanceof Boolean) {
-            sb.append(value);
-          } else {
-            String errorMessage =
-                "Invalid text format, expected a struct, a string, a bool, or an int "
-                    + "but got a "
-                    + EvalUtils.getDataTypeName(value)
-                    + " for "
-                    + container;
-            if (key != null) {
-              errorMessage += " '" + key + "'";
-            }
-            throw new EvalException(loc, errorMessage);
-          }
-        }
-
-        private String jsonEscapeString(String string) {
-          return escapeDoubleQuotesAndBackslashesAndNewlines(string)
-              .replace("\r", "\\r")
-              .replace("\t", "\\t");
-        }
-      };
-
   static {
     SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkRuleClassFunctions.class);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Info.java b/src/main/java/com/google/devtools/build/lib/packages/Info.java
index e50ab26..f1d74ac 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Info.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Info.java
@@ -22,6 +22,7 @@
 import com.google.common.collect.Ordering;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
+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.SkylarkPrinter;
@@ -29,8 +30,12 @@
 import com.google.devtools.build.lib.syntax.ClassObject;
 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.Printer;
+import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.SkylarkList;
 import com.google.devtools.build.lib.syntax.SkylarkType;
+import com.google.protobuf.TextFormat;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -218,6 +223,182 @@
     printer.append(")");
   }
 
+  @SkylarkCallable(
+      name = "to_proto",
+      doc =
+          "Creates a text message from the struct parameter. This method only works if all "
+              + "struct elements (recursively) are strings, ints, booleans, other structs or a "
+              + "list of these types. Quotes and new lines in strings are escaped. "
+              + "Keys are iterated in the sorted order. "
+              + "Examples:<br><pre class=language-python>"
+              + "struct(key=123).to_proto()\n# key: 123\n\n"
+              + "struct(key=True).to_proto()\n# key: true\n\n"
+              + "struct(key=[1, 2, 3]).to_proto()\n# key: 1\n# key: 2\n# key: 3\n\n"
+              + "struct(key='text').to_proto()\n# key: \"text\"\n\n"
+              + "struct(key=struct(inner_key='text')).to_proto()\n"
+              + "# key {\n#   inner_key: \"text\"\n# }\n\n"
+              + "struct(key=[struct(inner_key=1), struct(inner_key=2)]).to_proto()\n"
+              + "# key {\n#   inner_key: 1\n# }\n# key {\n#   inner_key: 2\n# }\n\n"
+              + "struct(key=struct(inner_key=struct(inner_inner_key='text'))).to_proto()\n"
+              + "# key {\n#    inner_key {\n#     inner_inner_key: \"text\"\n#   }\n# }\n</pre>",
+      useLocation = true
+  )
+  public String toProto(Location loc) throws EvalException {
+    StringBuilder sb = new StringBuilder();
+    printProtoTextMessage(this, sb, 0, loc);
+    return sb.toString();
+  }
+
+  private void printProtoTextMessage(
+      ClassObject object, StringBuilder sb, int indent, Location loc) throws EvalException {
+    // For determinism sort the fields alphabetically.
+    List<String> fields = new ArrayList<>(object.getFieldNames());
+    Collections.sort(fields);
+    for (String field : fields) {
+      printProtoTextMessage(field, object.getValue(field), sb, indent, loc);
+    }
+  }
+
+  private void printProtoTextMessage(
+      String key, Object value, StringBuilder sb, int indent, Location loc, String container)
+      throws EvalException {
+    if (value instanceof ClassObject) {
+      print(sb, key + " {", indent);
+      printProtoTextMessage((ClassObject) value, sb, indent + 1, loc);
+      print(sb, "}", indent);
+    } else if (value instanceof String) {
+      print(
+          sb,
+          key + ": \"" + escapeDoubleQuotesAndBackslashesAndNewlines((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 printProtoTextMessage(
+      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.
+        printProtoTextMessage(key, item, sb, indent, loc, "list element in struct field");
+      }
+    } else {
+      printProtoTextMessage(key, value, sb, indent, loc, "struct field");
+    }
+  }
+
+  private void print(StringBuilder sb, String text, int indent) {
+    for (int i = 0; i < indent; i++) {
+      sb.append("  ");
+    }
+    sb.append(text);
+    sb.append("\n");
+  }
+
+  /**
+   * Escapes the given string for use in proto/JSON string.
+   *
+   * <p>This escapes double quotes, backslashes, and newlines.
+   */
+  private static String escapeDoubleQuotesAndBackslashesAndNewlines(String string) {
+    return TextFormat.escapeDoubleQuotesAndBackslashes(string).replace("\n", "\\n");
+  }
+
+  @SkylarkCallable(
+      name = "to_json",
+      doc =
+          "Creates a JSON string from the struct parameter. This method only works if all "
+              + "struct elements (recursively) are strings, ints, booleans, other structs or a "
+              + "list of these types. Quotes and new lines in strings are escaped. "
+              + "Examples:<br><pre class=language-python>"
+              + "struct(key=123).to_json()\n# {\"key\":123}\n\n"
+              + "struct(key=True).to_json()\n# {\"key\":true}\n\n"
+              + "struct(key=[1, 2, 3]).to_json()\n# {\"key\":[1,2,3]}\n\n"
+              + "struct(key='text').to_json()\n# {\"key\":\"text\"}\n\n"
+              + "struct(key=struct(inner_key='text')).to_json()\n"
+              + "# {\"key\":{\"inner_key\":\"text\"}}\n\n"
+              + "struct(key=[struct(inner_key=1), struct(inner_key=2)]).to_json()\n"
+              + "# {\"key\":[{\"inner_key\":1},{\"inner_key\":2}]}\n\n"
+              + "struct(key=struct(inner_key=struct(inner_inner_key='text'))).to_json()\n"
+              + "# {\"key\":{\"inner_key\":{\"inner_inner_key\":\"text\"}}}\n</pre>",
+      useLocation = true
+  )
+  public String toJson(Location loc) throws EvalException {
+    StringBuilder sb = new StringBuilder();
+    printJson(this, sb, loc, "struct field", null);
+    return sb.toString();
+  }
+
+  private void printJson(
+      Object value, StringBuilder sb, Location loc, String container, String key)
+      throws EvalException {
+    if (value == Runtime.NONE) {
+      sb.append("null");
+    } else if (value instanceof ClassObject) {
+      sb.append("{");
+
+      String join = "";
+      for (String field : ((ClassObject) value).getFieldNames()) {
+        sb.append(join);
+        join = ",";
+        sb.append("\"");
+        sb.append(field);
+        sb.append("\":");
+        printJson(((ClassObject) value).getValue(field), sb, loc, "struct field", field);
+      }
+      sb.append("}");
+    } else if (value instanceof List) {
+      sb.append("[");
+      String join = "";
+      for (Object item : ((List) value)) {
+        sb.append(join);
+        join = ",";
+        printJson(item, sb, loc, "list element in struct field", key);
+      }
+      sb.append("]");
+    } else if (value instanceof String) {
+      sb.append("\"");
+      sb.append(jsonEscapeString((String) value));
+      sb.append("\"");
+    } else if (value instanceof Integer || value instanceof Boolean) {
+      sb.append(value);
+    } else {
+      String errorMessage =
+          "Invalid text format, expected a struct, a string, a bool, or an int "
+              + "but got a "
+              + EvalUtils.getDataTypeName(value)
+              + " for "
+              + container;
+      if (key != null) {
+        errorMessage += " '" + key + "'";
+      }
+      throw new EvalException(loc, errorMessage);
+    }
+  }
+
+  private String jsonEscapeString(String string) {
+    return escapeDoubleQuotesAndBackslashesAndNewlines(string)
+        .replace("\r", "\\r")
+        .replace("\t", "\\t");
+  }
+
   @Override
   public String toString() {
     return Printer.repr(this);
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 7b86d6c..ab2245b 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
@@ -1793,8 +1793,8 @@
         .update("mock", new NativeInfoMock())
         .testEval(
             "dir(mock)",
-            "['callable_string', 'struct_field_callable', "
-                + "'struct_field_none', 'struct_field_string']");
+            "['callable_string', 'struct_field_callable', 'struct_field_none', "
+                + "'struct_field_string', 'to_json', 'to_proto']");
   }
 
   @Test