Collect template instantiations.

PiperOrigin-RevId: 447450392
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index 7b59554..f891e0e 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -85,6 +85,7 @@
     srcs = ["generate_bindings_and_metadata.cc"],
     hdrs = ["generate_bindings_and_metadata.h"],
     deps = [
+        ":cc_collect_instantiations",
         ":cc_ir",
         ":cmdline",
         ":ir_from_cc",
@@ -99,8 +100,9 @@
     deps = [
         ":cmdline",
         ":generate_bindings_and_metadata",
-        "//common:file_io",
+        "//common:test_utils",
         "//testing/base/public:gunit_main",
+        "@absl//strings",
         "@rust//support:rust_okay_here",
     ],
 )
@@ -231,6 +233,7 @@
         ":bazel_types",
         "//common:check",
         "//common:strong_int",
+        "@absl//container:flat_hash_map",
         "@absl//strings",
         "@llvm///clang:ast",
         "@llvm///llvm:Support",
diff --git a/rs_bindings_from_cc/generate_bindings_and_metadata.cc b/rs_bindings_from_cc/generate_bindings_and_metadata.cc
index 4f6b3fa..777d6c6 100644
--- a/rs_bindings_from_cc/generate_bindings_and_metadata.cc
+++ b/rs_bindings_from_cc/generate_bindings_and_metadata.cc
@@ -5,6 +5,7 @@
 #include "rs_bindings_from_cc/generate_bindings_and_metadata.h"
 
 #include "common/status_macros.h"
+#include "rs_bindings_from_cc/collect_instantiations.h"
 #include "rs_bindings_from_cc/ir_from_cc.h"
 #include "rs_bindings_from_cc/src_code_gen.h"
 
@@ -17,11 +18,16 @@
                          clang_args.end());
 
   CRUBIT_ASSIGN_OR_RETURN(
-      IR ir, crubit::IrFromCc(
-                 /* extra_source_code= */ "", cmdline.current_target(),
-                 cmdline.public_headers(),
-                 /* virtual_headers_contents= */ {},
-                 cmdline.headers_to_targets(), clang_args_view));
+      std::vector<std::string> requested_instantiations,
+      crubit::CollectInstantiations(cmdline.rust_sources()));
+
+  CRUBIT_ASSIGN_OR_RETURN(
+      IR ir,
+      crubit::IrFromCc(
+          /* extra_source_code= */ "", cmdline.current_target(),
+          cmdline.public_headers(),
+          /* virtual_headers_contents= */ {}, cmdline.headers_to_targets(),
+          clang_args_view, requested_instantiations));
 
   crubit::Bindings bindings = crubit::GenerateBindings(
       ir, cmdline.crubit_support_path(), cmdline.rustfmt_config_path());
diff --git a/rs_bindings_from_cc/generate_bindings_and_metadata_test.cc b/rs_bindings_from_cc/generate_bindings_and_metadata_test.cc
index e0b7dd3..927cba5 100644
--- a/rs_bindings_from_cc/generate_bindings_and_metadata_test.cc
+++ b/rs_bindings_from_cc/generate_bindings_and_metadata_test.cc
@@ -6,19 +6,20 @@
 
 #include "testing/base/public/gmock.h"
 #include "testing/base/public/gunit.h"
-#include "common/file_io.h"
+#include "common/test_utils.h"
 #include "rs_bindings_from_cc/cmdline.h"
 
 namespace crubit {
 namespace {
 
+using ::testing::StrEq;
+
 TEST(GenerateBindingsAndMetadataTest, GeneratingIR) {
   constexpr absl::string_view kTargetsAndHeaders = R"([
     {"t": "target1", "h": ["a.h"]}
   ])";
 
-  std::string tmpdir = absl::GetFlag(FLAGS_test_tmpdir);
-  ASSERT_OK(SetFileContents(absl::StrCat(tmpdir, "/a.h"), "// empty header"));
+  WriteFileForCurrentTest("a.h", "//empty header");
   ASSERT_OK_AND_ASSIGN(
       Cmdline cmdline,
       Cmdline::CreateForTesting(
@@ -31,12 +32,58 @@
 
   ASSERT_OK_AND_ASSIGN(
       BindingsAndMetadata result,
-      GenerateBindingsAndMetadata(
-          cmdline, /* clang_args= */ {std::string("-I"), std::move(tmpdir)}));
+      GenerateBindingsAndMetadata(cmdline, DefaultClangArgs()));
 
   ASSERT_EQ(result.ir.used_headers.size(), 1);
   ASSERT_EQ(result.ir.used_headers.front().IncludePath(), "a.h");
 }
 
+TEST(GenerateBindingsAndMetadataTest, InstantiationsAreEmptyInNormalMode) {
+  constexpr absl::string_view kTargetsAndHeaders = R"([
+    {"t": "target1", "h": ["a.h"]}
+  ])";
+  WriteFileForCurrentTest("a.h", "// empty header");
+  ASSERT_OK_AND_ASSIGN(
+      Cmdline cmdline,
+      Cmdline::CreateForTesting(
+          "cc_out", "rs_out", "ir_out", "crubit_support_path",
+          "external/rustfmt/rustfmt.toml",
+          /* do_nothing= */ false,
+          /* public_headers= */ {"a.h"}, std::string(kTargetsAndHeaders),
+          /* rust_sources= */ {},
+          /* instantiations_out= */ ""));
+
+  ASSERT_OK_AND_ASSIGN(
+      BindingsAndMetadata result,
+      GenerateBindingsAndMetadata(cmdline, DefaultClangArgs()));
+
+  ASSERT_THAT(InstantiationsAsJson(result.ir), StrEq("{}"));
+}
+
+TEST(GenerateBindingsAndMetadataTest, InstantiationsJsonGenerated) {
+  constexpr absl::string_view kTargetsAndHeaders = R"([
+    {"t": "target1", "h": ["a.h"]}
+  ])";
+  WriteFileForCurrentTest("a.h", "// empty header");
+  std::string a_rs_path =
+      WriteFileForCurrentTest("a.rs", "cc_template!(MyTemplate<bool>);");
+  ASSERT_OK_AND_ASSIGN(
+      Cmdline cmdline,
+      Cmdline::CreateForTesting(
+          "cc_out", "rs_out", "ir_out", "crubit_support_path",
+          "external/rustfmt/rustfmt.toml",
+          /* do_nothing= */ false,
+          /* public_headers= */ {"a.h"}, std::string(kTargetsAndHeaders),
+          /* rust_sources= */ {a_rs_path}, "instantiations_out"));
+
+  ASSERT_OK_AND_ASSIGN(
+      BindingsAndMetadata result,
+      GenerateBindingsAndMetadata(cmdline, DefaultClangArgs()));
+
+  // TODO(b/440066049): Actually populate the instantiations map once
+  // cl/430823388 is submitted.
+  ASSERT_THAT(InstantiationsAsJson(result.ir), StrEq("{}"));
+}
+
 }  // namespace
 }  // namespace crubit
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index 97cb11b..f0fcbe9 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -21,6 +21,7 @@
 #include <variant>
 #include <vector>
 
+#include "absl/container/flat_hash_map.h"
 #include "absl/strings/string_view.h"
 #include "common/check.h"
 #include "common/strong_int.h"
@@ -714,12 +715,22 @@
                             UnsupportedItem, Comment, Namespace>;
   std::vector<Item> items;
   std::vector<ItemId> top_level_item_ids;
+  absl::flat_hash_map<std::string, std::string> instantiations;
 };
 
 inline std::string IrToJson(const IR& ir) {
   return std::string(llvm::formatv("{0:2}", ir.ToJson()));
 }
 
+// Instantiations from the `IR` serialized as JSON.
+inline std::string InstantiationsAsJson(const IR& ir) {
+  llvm::json::Object obj;
+  for (const auto& entry : ir.instantiations) {
+    obj[entry.first] = entry.second;
+  }
+  return std::string(llvm::formatv("{0:2}", llvm::json::Value(std::move(obj))));
+}
+
 inline std::ostream& operator<<(std::ostream& o, const IR& ir) {
   return o << IrToJson(ir);
 }
diff --git a/rs_bindings_from_cc/ir_from_cc.cc b/rs_bindings_from_cc/ir_from_cc.cc
index 2c573bb..4f2be3f 100644
--- a/rs_bindings_from_cc/ir_from_cc.cc
+++ b/rs_bindings_from_cc/ir_from_cc.cc
@@ -18,10 +18,7 @@
 #include "common/check.h"
 #include "rs_bindings_from_cc/bazel_types.h"
 #include "rs_bindings_from_cc/frontend_action.h"
-#include "rs_bindings_from_cc/importer.h"
 #include "rs_bindings_from_cc/ir.h"
-#include "clang/Basic/FileManager.h"
-#include "clang/Basic/FileSystemOptions.h"
 #include "clang/Frontend/FrontendAction.h"
 #include "clang/Tooling/Tooling.h"
 
@@ -38,10 +35,15 @@
     absl::flat_hash_map<const HeaderName, const std::string>
         virtual_headers_contents,
     absl::flat_hash_map<const HeaderName, const BazelLabel> headers_to_targets,
-    absl::Span<const absl::string_view> args) {
+    absl::Span<const absl::string_view> args,
+    absl::Span<const std::string> extra_instantiations) {
   // Caller should verify that the inputs are not empty.
-  CRUBIT_CHECK(!extra_source_code.empty() || !public_headers.empty());
-  CRUBIT_CHECK(!extra_source_code.empty() || !headers_to_targets.empty());
+  // TODO(b/440066049): Generate a source file for requested instantiations once
+  // cl/430823388 is submitted.
+  CRUBIT_CHECK(!extra_source_code.empty() || !public_headers.empty() ||
+               !extra_instantiations.empty());
+  CRUBIT_CHECK(!extra_source_code.empty() || !headers_to_targets.empty() ||
+               !extra_instantiations.empty());
 
   std::vector<HeaderName> entrypoint_headers(public_headers.begin(),
                                              public_headers.end());
diff --git a/rs_bindings_from_cc/ir_from_cc.h b/rs_bindings_from_cc/ir_from_cc.h
index c0bd4b8..73d84c8 100644
--- a/rs_bindings_from_cc/ir_from_cc.h
+++ b/rs_bindings_from_cc/ir_from_cc.h
@@ -33,6 +33,8 @@
 //   `//test:testing_target`. Headers from `virtual_headers_contents` are not
 //   added automatically.
 // * `args`: additional command line arguments for Clang
+// * `extra_instantiations`: names of full C++ class template specializations
+// to instantiate and generate bindings from.
 //
 absl::StatusOr<IR> IrFromCc(
     absl::string_view extra_source_code,
@@ -42,7 +44,8 @@
         virtual_headers_contents = {},
     absl::flat_hash_map<const HeaderName, const BazelLabel> headers_to_targets =
         {},
-    absl::Span<const absl::string_view> args = {});
+    absl::Span<const absl::string_view> args = {},
+    absl::Span<const std::string> extra_instantiations = {});
 
 }  // namespace crubit
 
diff --git a/rs_bindings_from_cc/rs_bindings_from_cc.cc b/rs_bindings_from_cc/rs_bindings_from_cc.cc
index 5e724a5..891add6 100644
--- a/rs_bindings_from_cc/rs_bindings_from_cc.cc
+++ b/rs_bindings_from_cc/rs_bindings_from_cc.cc
@@ -63,8 +63,9 @@
       SetFileContents(cmdline.cc_out(), bindings_and_metadata.rs_api_impl));
 
   if (!cmdline.instantiations_out().empty()) {
-    CRUBIT_RETURN_IF_ERROR(SetFileContents(cmdline.instantiations_out(),
-                                           "// not implemented yet"));
+    CRUBIT_RETURN_IF_ERROR(SetFileContents(
+        cmdline.instantiations_out(),
+        crubit::InstantiationsAsJson(bindings_and_metadata.ir)));
   }
 
   return absl::OkStatus();