Split `Importer` into individual classes for each decl type.

This is one step towards a more query-based importer architecture, by
encapsulating the individual import methods and extracting an interface for
their interaction with the importer.

The extracted interfaces of the `DeclImporter` and `ImportContext` are not meant
to be permanent. They merely extract the status quo and provide a basis for
future improvements.

I also plan on moving individual `DeclImporter`s into their own files. Maybe
also the `Invocation` and the `ImportContext`. I haven't done that in this CL to
make review easier.

PiperOrigin-RevId: 443642590
diff --git a/rs_bindings_from_cc/ast_consumer.h b/rs_bindings_from_cc/ast_consumer.h
index 581ad23..3e6b1a7 100644
--- a/rs_bindings_from_cc/ast_consumer.h
+++ b/rs_bindings_from_cc/ast_consumer.h
@@ -17,14 +17,14 @@
 class AstConsumer : public clang::ASTConsumer {
  public:
   explicit AstConsumer(clang::CompilerInstance& instance,
-                       Importer::Invocation& invocation)
+                       Invocation& invocation)
       : instance_(instance), invocation_(invocation) {}
 
   void HandleTranslationUnit(clang::ASTContext& context) override;
 
  private:
   clang::CompilerInstance& instance_;
-  Importer::Invocation& invocation_;
+  Invocation& invocation_;
 };  // class AstConsumer
 
 }  // namespace crubit
diff --git a/rs_bindings_from_cc/frontend_action.h b/rs_bindings_from_cc/frontend_action.h
index 2ffca73..db3fa17 100644
--- a/rs_bindings_from_cc/frontend_action.h
+++ b/rs_bindings_from_cc/frontend_action.h
@@ -18,14 +18,13 @@
 // (`IR`) into the invocation object.
 class FrontendAction : public clang::ASTFrontendAction {
  public:
-  explicit FrontendAction(Importer::Invocation& invocation)
-      : invocation_(invocation) {}
+  explicit FrontendAction(Invocation& invocation) : invocation_(invocation) {}
 
   std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
       clang::CompilerInstance& instance, llvm::StringRef) override;
 
  private:
-  Importer::Invocation& invocation_;
+  Invocation& invocation_;
 };
 
 }  // namespace crubit
diff --git a/rs_bindings_from_cc/importer.cc b/rs_bindings_from_cc/importer.cc
index cdee63b..97216c9 100644
--- a/rs_bindings_from_cc/importer.cc
+++ b/rs_bindings_from_cc/importer.cc
@@ -62,11 +62,13 @@
 constexpr absl::string_view kTypeStatusPayloadUrl =
     "type.googleapis.com/devtools.rust.cc_interop.rs_binding_from_cc.type";
 
+}
+
 // A mapping of C++ standard types to their equivalent Rust types.
 // To produce more idiomatic results, these types receive special handling
 // instead of using the generic type mapping mechanism.
-std::optional<absl::string_view> MapKnownCcTypeToRsType(
-    absl::string_view cc_type) {
+std::optional<absl::string_view> TypeMapper::MapKnownCcTypeToRsType(
+    absl::string_view cc_type) const {
   static const auto* const kWellKnownTypes =
       new absl::flat_hash_map<absl::string_view, absl::string_view>({
           {"ptrdiff_t", "isize"},
@@ -106,6 +108,8 @@
   return it->second;
 }
 
+namespace {
+
 ItemId GenerateItemId(const clang::Decl* decl) {
   return ItemId(reinterpret_cast<uintptr_t>(decl->getCanonicalDecl()));
 }
@@ -398,80 +402,71 @@
 }
 
 std::optional<IR::Item> Importer::ImportDecl(clang::Decl* decl) {
-  if (auto* namespace_decl = clang::dyn_cast<clang::NamespaceDecl>(decl)) {
-    return ImportNamespace(namespace_decl);
-  } else if (auto* function_decl = clang::dyn_cast<clang::FunctionDecl>(decl)) {
-    return ImportFunction(function_decl);
-  } else if (auto* function_template_decl =
-                 clang::dyn_cast<clang::FunctionTemplateDecl>(decl)) {
-    return ImportFunction(function_template_decl->getTemplatedDecl(),
-                          function_template_decl);
-  } else if (auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(decl)) {
-    auto result = ImportRecord(record_decl);
+  std::optional<IR::Item> result;
+  for (auto& importer : decl_importers_) {
+    if (importer->CanImport(decl)) {
+      result = importer->ImportDecl(decl);
+    }
+  }
+
+  if (auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(decl)) {
     // TODO(forster): Should we even visit the nested decl if we couldn't
     // import the parent? For now we have tests that check that we generate
     // error messages for those decls, so we're visiting.
     ImportDeclsFromDeclContext(record_decl);
-    return result;
-  } else if (auto* enum_decl = clang::dyn_cast<clang::EnumDecl>(decl)) {
-    return ImportEnum(enum_decl);
-  } else if (auto* typedef_name_decl =
-                 clang::dyn_cast<clang::TypedefNameDecl>(decl)) {
-    return ImportTypedefName(typedef_name_decl);
-  } else if (clang::isa<clang::ClassTemplateDecl>(decl)) {
-    return ImportUnsupportedItem(decl, "Class templates are not supported yet");
-  } else {
-    return std::nullopt;
   }
+
+  return result;
 }
 
-std::optional<IR::Item> Importer::ImportNamespace(
+std::optional<IR::Item> NamespaceDeclImporter::Import(
     clang::NamespaceDecl* namespace_decl) {
-  if (!IsFromCurrentTarget(namespace_decl)) return std::nullopt;
+  if (!ictx_.IsFromCurrentTarget(namespace_decl)) return std::nullopt;
 
   // TODO(rosica) In order to fully enable namespaces we first need to ensure
   // that each decl Item contains information on its namespace parents.
   if (!absl::StrContains(namespace_decl->getQualifiedNameAsString(),
                          "test_namespace_bindings")) {
-    return ImportUnsupportedItem(namespace_decl,
-                                 "Namespaces are not supported yet");
+    return ictx_.ImportUnsupportedItem(namespace_decl,
+                                       "Namespaces are not supported yet");
   }
 
   if (namespace_decl->isInline()) {
-    return ImportUnsupportedItem(namespace_decl,
-                                 "Inline namespaces are not supported yet");
+    return ictx_.ImportUnsupportedItem(
+        namespace_decl, "Inline namespaces are not supported yet");
   }
   if (namespace_decl->isAnonymousNamespace()) {
-    return ImportUnsupportedItem(namespace_decl,
-                                 "Anonymous namespaces are not supported yet");
+    return ictx_.ImportUnsupportedItem(
+        namespace_decl, "Anonymous namespaces are not supported yet");
   }
 
-  ImportDeclsFromDeclContext(namespace_decl);
-  auto identifier = GetTranslatedIdentifier(namespace_decl);
+  ictx_.ImportDeclsFromDeclContext(namespace_decl);
+  auto identifier = ictx_.GetTranslatedIdentifier(namespace_decl);
   CRUBIT_CHECK(identifier.has_value());
-  auto item_ids = GetItemIdsInSourceOrder(namespace_decl);
+  auto item_ids = ictx_.GetItemIdsInSourceOrder(namespace_decl);
   return Namespace{
       .name = *identifier,
       .id = GenerateItemId(namespace_decl),
-      .owning_target = GetOwningTarget(namespace_decl),
+      .owning_target = ictx_.GetOwningTarget(namespace_decl),
       .child_item_ids = std::move(item_ids),
   };
 }
 
-std::optional<IR::Item> Importer::ImportFunction(
-    clang::FunctionDecl* function_decl,
+std::optional<IR::Item> FunctionTemplateDeclImporter::Import(
     clang::FunctionTemplateDecl* function_template_decl) {
-  if (!IsFromCurrentTarget(function_decl)) return std::nullopt;
+  return ictx_.ImportUnsupportedItem(
+      function_template_decl, "Function templates are not supported yet");
+}
+
+std::optional<IR::Item> FunctionDeclImporter::Import(
+    clang::FunctionDecl* function_decl) {
+  if (!ictx_.IsFromCurrentTarget(function_decl)) return std::nullopt;
   if (function_decl->isDeleted()) return std::nullopt;
-  if (function_decl->isTemplated()) {
-    return ImportUnsupportedItem(function_template_decl,
-                                 "Function templates are not supported yet");
-  }
 
   clang::tidy::lifetimes::LifetimeSymbolTable lifetime_symbol_table;
   llvm::Expected<clang::tidy::lifetimes::FunctionLifetimes> lifetimes =
       clang::tidy::lifetimes::GetLifetimeAnnotations(
-          function_decl, *invocation_.lifetime_context_,
+          function_decl, *ictx_.invocation_.lifetime_context_,
           &lifetime_symbol_table);
 
   std::vector<FuncParam> params;
@@ -482,8 +477,9 @@
   };
   if (auto* method_decl =
           clang::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
-    if (!type_mapper_.Contains(method_decl->getParent())) {
-      return ImportUnsupportedItem(function_decl, "Couldn't import the parent");
+    if (!ictx_.type_mapper_.Contains(method_decl->getParent())) {
+      return ictx_.ImportUnsupportedItem(function_decl,
+                                         "Couldn't import the parent");
     }
 
     // non-static member functions receive an implicit `this` parameter.
@@ -492,9 +488,9 @@
       if (lifetimes) {
         this_lifetimes = lifetimes->GetThisLifetimes();
       }
-      auto param_type = type_mapper_.ConvertQualType(method_decl->getThisType(),
-                                                     this_lifetimes,
-                                                     /*nullable=*/false);
+      auto param_type = ictx_.type_mapper_.ConvertQualType(
+          method_decl->getThisType(), this_lifetimes,
+          /*nullable=*/false);
       if (!param_type.ok()) {
         add_error(absl::StrCat("`this` parameter is not supported: ",
                                param_type.status().message()));
@@ -515,7 +511,7 @@
       param_lifetimes = lifetimes->GetParamLifetimes(i);
     }
     auto param_type =
-        type_mapper_.ConvertQualType(param->getType(), param_lifetimes);
+        ictx_.type_mapper_.ConvertQualType(param->getType(), param_lifetimes);
     if (!param_type.ok()) {
       add_error(absl::Substitute("Parameter #$0 is not supported: $1", i,
                                  param_type.status().message()));
@@ -538,7 +534,7 @@
       }
     }
 
-    std::optional<Identifier> param_name = GetTranslatedIdentifier(param);
+    std::optional<Identifier> param_name = ictx_.GetTranslatedIdentifier(param);
     CRUBIT_CHECK(param_name.has_value());  // No known failure cases.
     params.push_back({*param_type, *std::move(param_name)});
   }
@@ -564,7 +560,7 @@
     return_lifetimes = lifetimes->GetReturnLifetimes();
   }
 
-  auto return_type = type_mapper_.ConvertQualType(
+  auto return_type = ictx_.type_mapper_.ConvertQualType(
       function_decl->getReturnType(), return_lifetimes);
   if (!return_type.ok()) {
     add_error(absl::StrCat("Return type is not supported: ",
@@ -635,14 +631,14 @@
   }
 
   if (!errors.empty()) {
-    return ImportUnsupportedItem(function_decl, errors);
+    return ictx_.ImportUnsupportedItem(function_decl, errors);
   }
 
   bool has_c_calling_convention =
       function_decl->getType()->getAs<clang::FunctionType>()->getCallConv() ==
       clang::CC_C;
   std::optional<UnqualifiedIdentifier> translated_name =
-      GetTranslatedName(function_decl);
+      ictx_.GetTranslatedName(function_decl);
 
   // Silence ClangTidy, checked above: calling `add_error` if
   // `!return_type.ok()` and returning early if `!errors.empty()`.
@@ -651,19 +647,17 @@
   if (translated_name.has_value()) {
     return Func{
         .name = *translated_name,
-        .owning_target = GetOwningTarget(function_decl),
-        .doc_comment = GetComment(function_decl),
-        .mangled_name = GetMangledName(function_decl),
+        .owning_target = ictx_.GetOwningTarget(function_decl),
+        .doc_comment = ictx_.GetComment(function_decl),
+        .mangled_name = ictx_.GetMangledName(function_decl),
         .return_type = *return_type,
         .params = std::move(params),
         .lifetime_params = std::move(lifetime_params),
         .is_inline = function_decl->isInlined(),
         .member_func_metadata = std::move(member_func_metadata),
         .has_c_calling_convention = has_c_calling_convention,
-        .source_loc = ConvertSourceLocation(function_decl->getBeginLoc()),
-        .id = function_template_decl == nullptr
-                  ? GenerateItemId(function_decl)
-                  : GenerateItemId(function_template_decl),
+        .source_loc = ictx_.ConvertSourceLocation(function_decl->getBeginLoc()),
+        .id = GenerateItemId(function_decl),
     };
   }
   return std::nullopt;
@@ -704,7 +698,13 @@
   return invocation_.target_ == GetOwningTarget(decl);
 }
 
-std::optional<IR::Item> Importer::ImportRecord(
+std::optional<IR::Item> ClassTemplateDeclImporter::Import(
+    clang::ClassTemplateDecl* class_template_decl) {
+  return ictx_.ImportUnsupportedItem(class_template_decl,
+                                     "Class templates are not supported yet");
+}
+
+std::optional<IR::Item> CXXRecordDeclImporter::Import(
     clang::CXXRecordDecl* record_decl) {
   const clang::DeclContext* decl_context = record_decl->getDeclContext();
   if (decl_context->isFunctionOrMethod()) {
@@ -714,14 +714,15 @@
     return std::nullopt;
   }
   if (decl_context->isRecord()) {
-    return ImportUnsupportedItem(record_decl,
-                                 "Nested classes are not supported yet");
+    return ictx_.ImportUnsupportedItem(record_decl,
+                                       "Nested classes are not supported yet");
   }
   if (record_decl->isInvalidDecl()) {
     return std::nullopt;
   }
 
-  std::optional<Identifier> record_name = GetTranslatedIdentifier(record_decl);
+  std::optional<Identifier> record_name =
+      ictx_.GetTranslatedIdentifier(record_decl);
   if (!record_name.has_value()) {
     return std::nullopt;
   }
@@ -730,23 +731,25 @@
     record_decl = complete;
   } else {
     CRUBIT_CHECK(!record_decl->isCompleteDefinition());
-    type_mapper_.Insert(record_decl);
-    return IncompleteRecord{.cc_name = std::string(record_name->Ident()),
-                            .id = GenerateItemId(record_decl),
-                            .owning_target = GetOwningTarget(record_decl)};
+    ictx_.type_mapper_.Insert(record_decl);
+    return IncompleteRecord{
+        .cc_name = std::string(record_name->Ident()),
+        .id = GenerateItemId(record_decl),
+        .owning_target = ictx_.GetOwningTarget(record_decl)};
   }
 
   // To compute the memory layout of the record, it needs to be a concrete type,
   // not a template.
   if (record_decl->getDescribedClassTemplate() ||
       clang::isa<clang::ClassTemplateSpecializationDecl>(record_decl)) {
-    return ImportUnsupportedItem(record_decl,
-                                 "Class templates are not supported yet");
+    return ictx_.ImportUnsupportedItem(record_decl,
+                                       "Class templates are not supported yet");
   }
 
-  sema_.ForceDeclarationOfImplicitMembers(record_decl);
+  ictx_.sema_.ForceDeclarationOfImplicitMembers(record_decl);
 
-  const clang::ASTRecordLayout& layout = ctx_.getASTRecordLayout(record_decl);
+  const clang::ASTRecordLayout& layout =
+      ictx_.ctx_.getASTRecordLayout(record_decl);
 
   llvm::Optional<size_t> base_size;
   bool override_alignment = record_decl->hasAttr<clang::AlignedAttr>();
@@ -765,7 +768,7 @@
 
   absl::StatusOr<std::vector<Field>> fields = ImportFields(record_decl);
   if (!fields.ok()) {
-    return ImportUnsupportedItem(record_decl, fields.status().ToString());
+    return ictx_.ImportUnsupportedItem(record_decl, fields.status().ToString());
   }
 
   for (const Field& field : *fields) {
@@ -775,15 +778,15 @@
     }
   }
 
-  type_mapper_.Insert(record_decl);
+  ictx_.type_mapper_.Insert(record_decl);
 
-  auto item_ids = GetItemIdsInSourceOrder(record_decl);
+  auto item_ids = ictx_.GetItemIdsInSourceOrder(record_decl);
   return Record{
       .rs_name = std::string(record_name->Ident()),
       .cc_name = std::string(record_name->Ident()),
       .id = GenerateItemId(record_decl),
-      .owning_target = GetOwningTarget(record_decl),
-      .doc_comment = GetComment(record_decl),
+      .owning_target = ictx_.GetOwningTarget(record_decl),
+      .doc_comment = ictx_.GetComment(record_decl),
       .unambiguous_public_bases = GetUnambiguousPublicBases(*record_decl),
       .fields = *std::move(fields),
       .size = layout.getSize().getQuantity(),
@@ -800,15 +803,16 @@
       .child_item_ids = std::move(item_ids)};
 }
 
-std::optional<IR::Item> Importer::ImportEnum(clang::EnumDecl* enum_decl) {
-  std::optional<Identifier> enum_name = GetTranslatedIdentifier(enum_decl);
+std::optional<IR::Item> EnumDeclImporter::Import(clang::EnumDecl* enum_decl) {
+  std::optional<Identifier> enum_name =
+      ictx_.GetTranslatedIdentifier(enum_decl);
   if (!enum_name.has_value()) {
     // TODO(b/208945197): This corresponds to an unnamed enum declaration like
     // `enum { kFoo = 1 }`, which only exists to provide constants into the
     // surrounding scope and doesn't actually introduce an enum namespace. It
     // seems like it should probably be handled with other constants.
-    return ImportUnsupportedItem(enum_decl,
-                                 "Unnamed enums are not supported yet");
+    return ictx_.ImportUnsupportedItem(enum_decl,
+                                       "Unnamed enums are not supported yet");
   }
 
   clang::QualType cc_type = enum_decl->getIntegerType();
@@ -818,15 +822,15 @@
     // with no fixed underlying type." The same page implies that this can't
     // occur in C++ nor in standard C, but clang supports enums like this
     // in C "as an extension".
-    return ImportUnsupportedItem(
+    return ictx_.ImportUnsupportedItem(
         enum_decl,
         "Forward declared enums without type specifiers are not supported");
   }
   std::optional<clang::tidy::lifetimes::ValueLifetimes> no_lifetimes;
   absl::StatusOr<MappedType> type =
-      type_mapper_.ConvertQualType(cc_type, no_lifetimes);
+      ictx_.type_mapper_.ConvertQualType(cc_type, no_lifetimes);
   if (!type.ok()) {
-    return ImportUnsupportedItem(enum_decl, type.status().ToString());
+    return ictx_.ImportUnsupportedItem(enum_decl, type.status().ToString());
   }
 
   std::vector<Enumerator> enumerators;
@@ -834,10 +838,10 @@
                                     enum_decl->enumerators().end()));
   for (clang::EnumConstantDecl* enumerator : enum_decl->enumerators()) {
     std::optional<Identifier> enumerator_name =
-        GetTranslatedIdentifier(enumerator);
+        ictx_.GetTranslatedIdentifier(enumerator);
     if (!enumerator_name.has_value()) {
       // It's not clear that this case is possible
-      return ImportUnsupportedItem(
+      return ictx_.ImportUnsupportedItem(
           enum_decl, "importing enum failed: missing enumerator name");
     }
 
@@ -850,7 +854,7 @@
   return Enum{
       .identifier = *enum_name,
       .id = GenerateItemId(enum_decl),
-      .owning_target = GetOwningTarget(enum_decl),
+      .owning_target = ictx_.GetOwningTarget(enum_decl),
       .underlying_type = *std::move(type),
       .enumerators = enumerators,
   };
@@ -874,7 +878,7 @@
   return ImportUnsupportedItem(decl, absl::StrJoin(errors, "\n\n"));
 }
 
-std::optional<IR::Item> Importer::ImportTypedefName(
+std::optional<IR::Item> crubit::TypedefNameDeclImporter::Import(
     clang::TypedefNameDecl* typedef_name_decl) {
   const clang::DeclContext* decl_context = typedef_name_decl->getDeclContext();
   if (decl_context) {
@@ -882,7 +886,7 @@
       return std::nullopt;
     }
     if (decl_context->isRecord()) {
-      return ImportUnsupportedItem(
+      return ictx_.ImportUnsupportedItem(
           typedef_name_decl,
           "Typedefs nested in classes are not supported yet");
     }
@@ -890,25 +894,27 @@
 
   clang::QualType type =
       typedef_name_decl->getASTContext().getTypedefType(typedef_name_decl);
-  if (MapKnownCcTypeToRsType(type.getAsString()).has_value()) {
+  if (ictx_.type_mapper_.MapKnownCcTypeToRsType(type.getAsString())
+          .has_value()) {
     return std::nullopt;
   }
 
   std::optional<Identifier> identifier =
-      GetTranslatedIdentifier(typedef_name_decl);
+      ictx_.GetTranslatedIdentifier(typedef_name_decl);
   CRUBIT_CHECK(identifier.has_value());  // This should never happen.
   std::optional<clang::tidy::lifetimes::ValueLifetimes> no_lifetimes;
-  absl::StatusOr<MappedType> underlying_type = type_mapper_.ConvertQualType(
-      typedef_name_decl->getUnderlyingType(), no_lifetimes);
+  absl::StatusOr<MappedType> underlying_type =
+      ictx_.type_mapper_.ConvertQualType(typedef_name_decl->getUnderlyingType(),
+                                         no_lifetimes);
   if (underlying_type.ok()) {
-    type_mapper_.Insert(typedef_name_decl);
+    ictx_.type_mapper_.Insert(typedef_name_decl);
     return TypeAlias{.identifier = *identifier,
                      .id = GenerateItemId(typedef_name_decl),
-                     .owning_target = GetOwningTarget(typedef_name_decl),
-                     .doc_comment = GetComment(typedef_name_decl),
+                     .owning_target = ictx_.GetOwningTarget(typedef_name_decl),
+                     .doc_comment = ictx_.GetComment(typedef_name_decl),
                      .underlying_type = *underlying_type};
   } else {
-    return ImportUnsupportedItem(
+    return ictx_.ImportUnsupportedItem(
         typedef_name_decl, std::string(underlying_type.status().message()));
   }
 }
@@ -957,7 +963,7 @@
                    .column = sm.getSpellingColumnNumber(loc)};
 }
 
-absl::StatusOr<MappedType> Importer::TypeMapper::ConvertTypeDecl(
+absl::StatusOr<MappedType> TypeMapper::ConvertTypeDecl(
     const clang::TypeDecl* decl) const {
   if (!known_type_decls_.contains(decl)) {
     return absl::NotFoundError(absl::Substitute(
@@ -968,7 +974,7 @@
   return MappedType::WithDeclId(decl_id);
 }
 
-absl::StatusOr<MappedType> Importer::TypeMapper::ConvertType(
+absl::StatusOr<MappedType> TypeMapper::ConvertType(
     const clang::Type* type,
     std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
     bool nullable) const {
@@ -1082,7 +1088,7 @@
       "Unsupported clang::Type class '", type->getTypeClassName(), "'"));
 }
 
-absl::StatusOr<MappedType> Importer::TypeMapper::ConvertQualType(
+absl::StatusOr<MappedType> TypeMapper::ConvertQualType(
     clang::QualType qual_type,
     std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
     bool nullable) const {
@@ -1106,17 +1112,18 @@
   return type;
 }
 
-absl::StatusOr<std::vector<Field>> Importer::ImportFields(
+absl::StatusOr<std::vector<Field>> CXXRecordDeclImporter::ImportFields(
     clang::CXXRecordDecl* record_decl) {
   // Provisionally assume that we know this RecordDecl so that we'll be able
   // to import fields whose type contains the record itself.
-  TypeMapper temp_import_mapper(type_mapper_);
+  TypeMapper temp_import_mapper(ictx_.type_mapper_);
   temp_import_mapper.Insert(record_decl);
 
   clang::AccessSpecifier default_access =
       record_decl->isClass() ? clang::AS_private : clang::AS_public;
   std::vector<Field> fields;
-  const clang::ASTRecordLayout& layout = ctx_.getASTRecordLayout(record_decl);
+  const clang::ASTRecordLayout& layout =
+      ictx_.ctx_.getASTRecordLayout(record_decl);
   for (const clang::FieldDecl* field_decl : record_decl->fields()) {
     std::optional<clang::tidy::lifetimes::ValueLifetimes> no_lifetimes;
     auto type =
@@ -1131,7 +1138,8 @@
       access = default_access;
     }
 
-    std::optional<Identifier> field_name = GetTranslatedIdentifier(field_decl);
+    std::optional<Identifier> field_name =
+        ictx_.GetTranslatedIdentifier(field_decl);
     if (!field_name.has_value()) {
       return absl::UnimplementedError(
           absl::Substitute("Cannot translate name for field '$0'",
@@ -1139,7 +1147,7 @@
     }
     fields.push_back(
         {.identifier = *std::move(field_name),
-         .doc_comment = GetComment(field_decl),
+         .doc_comment = ictx_.GetComment(field_decl),
          .type = *type,
          .access = TranslateAccessSpecifier(access),
          .offset = layout.getFieldOffset(field_decl->getFieldIndex()),
@@ -1237,7 +1245,7 @@
   }
 }
 
-std::vector<BaseClass> Importer::GetUnambiguousPublicBases(
+std::vector<BaseClass> CXXRecordDeclImporter::GetUnambiguousPublicBases(
     const clang::CXXRecordDecl& record_decl) const {
   // This function is unfortunate: the only way to correctly get information
   // about the bases is lookupInBases. It runs a complex O(N^3) algorithm for
@@ -1282,13 +1290,13 @@
       const clang::CXXBaseSpecifier& base_specifier =
           *path[path.size() - 1].Base;
       const clang::QualType& base = base_specifier.getType();
-      if (paths.isAmbiguous(ctx_.getCanonicalType(base))) {
+      if (paths.isAmbiguous(ictx_.ctx_.getCanonicalType(base))) {
         continue;
       }
 
       clang::CXXRecordDecl* base_record_decl =
           CRUBIT_DIE_IF_NULL(base_specifier.getType()->getAsCXXRecordDecl());
-      if (!type_mapper_.ConvertTypeDecl(base_record_decl).status().ok()) {
+      if (!ictx_.type_mapper_.ConvertTypeDecl(base_record_decl).status().ok()) {
         continue;
       }
 
@@ -1299,7 +1307,7 @@
           break;
         }
         *offset +=
-            {ctx_.getASTRecordLayout(base_path_element.Class)
+            {ictx_.ctx_.getASTRecordLayout(base_path_element.Class)
                  .getBaseClassOffset(CRUBIT_DIE_IF_NULL(
                      base_path_element.Base->getType()->getAsCXXRecordDecl()))
                  .getQuantity()};
diff --git a/rs_bindings_from_cc/importer.h b/rs_bindings_from_cc/importer.h
index 50aa64e..c2824c8 100644
--- a/rs_bindings_from_cc/importer.h
+++ b/rs_bindings_from_cc/importer.h
@@ -33,154 +33,133 @@
 
 namespace crubit {
 
-// Iterates over the AST created from the invocation's entry headers and
-// creates an intermediate representation of the import (`IR`) into the
-// invocation object.
-class Importer {
+// Top-level parameters as well as return value of an importer invocation.
+class Invocation {
  public:
-  // Top-level parameters as well as return value of an importer invocation.
-  class Invocation {
-   public:
-    Invocation(BazelLabel target, absl::Span<const HeaderName> entry_headers,
-               const absl::flat_hash_map<const HeaderName, const BazelLabel>&
-                   header_targets)
-        : target_(target),
-          entry_headers_(entry_headers),
-          lifetime_context_(
-              std::make_shared<
-                  clang::tidy::lifetimes::LifetimeAnnotationContext>()),
-          header_targets_(header_targets) {
-      // Caller should verify that the inputs are non-empty.
-      CRUBIT_CHECK(!entry_headers_.empty());
-      CRUBIT_CHECK(!header_targets_.empty());
+  Invocation(BazelLabel target, absl::Span<const HeaderName> entry_headers,
+             const absl::flat_hash_map<const HeaderName, const BazelLabel>&
+                 header_targets)
+      : target_(target),
+        entry_headers_(entry_headers),
+        lifetime_context_(std::make_shared<
+                          clang::tidy::lifetimes::LifetimeAnnotationContext>()),
+        header_targets_(header_targets) {
+    // Caller should verify that the inputs are non-empty.
+    CRUBIT_CHECK(!entry_headers_.empty());
+    CRUBIT_CHECK(!header_targets_.empty());
 
-      ir_.used_headers.insert(ir_.used_headers.end(), entry_headers_.begin(),
-                              entry_headers.end());
-      ir_.current_target = target_;
-    }
+    ir_.used_headers.insert(ir_.used_headers.end(), entry_headers_.begin(),
+                            entry_headers.end());
+    ir_.current_target = target_;
+  }
 
-    // Returns the target of a header, if any.
-    std::optional<BazelLabel> header_target(const HeaderName header) const {
-      auto it = header_targets_.find(header);
-      return (it != header_targets_.end()) ? std::optional(it->second)
-                                           : std::nullopt;
-    }
+  // Returns the target of a header, if any.
+  std::optional<BazelLabel> header_target(const HeaderName header) const {
+    auto it = header_targets_.find(header);
+    return (it != header_targets_.end()) ? std::optional(it->second)
+                                         : std::nullopt;
+  }
 
-    // The main target from which we are importing.
-    const BazelLabel target_;
+  // The main target from which we are importing.
+  const BazelLabel target_;
 
-    // The headers from which the import starts (a collection of
-    // paths in the format suitable for a google3-relative quote include).
-    const absl::Span<const HeaderName> entry_headers_;
+  // The headers from which the import starts (a collection of
+  // paths in the format suitable for a google3-relative quote include).
+  const absl::Span<const HeaderName> entry_headers_;
 
-    const std::shared_ptr<clang::tidy::lifetimes::LifetimeAnnotationContext>
-        lifetime_context_;
+  const std::shared_ptr<clang::tidy::lifetimes::LifetimeAnnotationContext>
+      lifetime_context_;
 
-    // The main output of the import process
-    IR ir_;
-
-   private:
-    const absl::flat_hash_map<const HeaderName, const BazelLabel>&
-        header_targets_;
-  };
-
-  // The currently known canonical type decls that we know how to map into
-  // Rust.
-  class TypeMapper {
-   public:
-    TypeMapper(const clang::ASTContext* ctx) : ctx_(ctx) {}
-
-    TypeMapper(const TypeMapper& other) = default;
-    TypeMapper& operator=(const TypeMapper& other) = default;
-
-    // Converts the Clang type `qual_type` into an equivalent `MappedType`.
-    // Lifetimes for the type can optionally be specified using `lifetimes`.
-    // If `qual_type` is a pointer type, `nullable` specifies whether the
-    // pointer can be null.
-    // TODO(b/209390498): Currently, we're able to specify nullability only for
-    // top-level pointers. Extend this so that we can specify nullability for
-    // all pointers contained in `qual_type`, in the same way that `lifetimes`
-    // specifies lifetimes for all these pointers. Once this is done, make sure
-    // that all callers pass in the appropriate information, derived from
-    // nullability annotations.
-    absl::StatusOr<MappedType> ConvertQualType(
-        clang::QualType qual_type,
-        std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
-        bool nullable = true) const;
-    absl::StatusOr<MappedType> ConvertType(
-        const clang::Type* type,
-        std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
-        bool nullable) const;
-    absl::StatusOr<MappedType> ConvertTypeDecl(
-        const clang::TypeDecl* decl) const;
-
-    bool Contains(const clang::TypeDecl* decl) const {
-      return known_type_decls_.contains(
-          clang::cast<clang::TypeDecl>(decl->getCanonicalDecl()));
-    }
-
-    void Insert(const clang::TypeDecl* decl) {
-      known_type_decls_.insert(
-          clang::cast<clang::TypeDecl>(decl->getCanonicalDecl()));
-    }
-
-   private:
-    const clang::ASTContext* ctx_;
-    absl::flat_hash_set<const clang::TypeDecl*> known_type_decls_;
-  };
-
-  explicit Importer(Invocation& invocation, clang::ASTContext& ctx,
-                    clang::Sema& sema)
-      : invocation_(invocation),
-        ctx_(ctx),
-        sema_(sema),
-        mangler_(CRUBIT_DIE_IF_NULL(ctx_.createMangleContext())),
-        type_mapper_(&ctx) {}
-
-  // Import all visible declarations from a translation unit.
-  void Import(clang::TranslationUnitDecl* decl);
+  // The main output of the import process
+  IR ir_;
 
  private:
+  const absl::flat_hash_map<const HeaderName, const BazelLabel>&
+      header_targets_;
+};
+
+// The currently known canonical type decls that we know how to map into
+// Rust.
+class TypeMapper {
+ public:
+  TypeMapper(const clang::ASTContext* ctx) : ctx_(ctx) {}
+
+  TypeMapper(const TypeMapper& other) = default;
+  TypeMapper& operator=(const TypeMapper& other) = default;
+
+  // Converts the Clang type `qual_type` into an equivalent `MappedType`.
+  // Lifetimes for the type can optionally be specified using `lifetimes`.
+  // If `qual_type` is a pointer type, `nullable` specifies whether the
+  // pointer can be null.
+  // TODO(b/209390498): Currently, we're able to specify nullability only for
+  // top-level pointers. Extend this so that we can specify nullability for
+  // all pointers contained in `qual_type`, in the same way that `lifetimes`
+  // specifies lifetimes for all these pointers. Once this is done, make sure
+  // that all callers pass in the appropriate information, derived from
+  // nullability annotations.
+  absl::StatusOr<MappedType> ConvertQualType(
+      clang::QualType qual_type,
+      std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
+      bool nullable = true) const;
+  absl::StatusOr<MappedType> ConvertType(
+      const clang::Type* type,
+      std::optional<clang::tidy::lifetimes::ValueLifetimes>& lifetimes,
+      bool nullable) const;
+  absl::StatusOr<MappedType> ConvertTypeDecl(const clang::TypeDecl* decl) const;
+  std::optional<absl::string_view> MapKnownCcTypeToRsType(
+      absl::string_view cc_type) const;
+
+  bool Contains(const clang::TypeDecl* decl) const {
+    return known_type_decls_.contains(
+        clang::cast<clang::TypeDecl>(decl->getCanonicalDecl()));
+  }
+
+  void Insert(const clang::TypeDecl* decl) {
+    known_type_decls_.insert(
+        clang::cast<clang::TypeDecl>(decl->getCanonicalDecl()));
+  }
+
+ private:
+  const clang::ASTContext* ctx_;
+  absl::flat_hash_set<const clang::TypeDecl*> known_type_decls_;
+};
+
+// Explicitly defined interface that defines how `DeclImporter`s are allowed to
+// interface with the global state of the importer.
+class ImportContext {
+ public:
+  ImportContext(Invocation& invocation, clang::ASTContext& ctx,
+                clang::Sema& sema)
+      : invocation_(invocation), ctx_(ctx), sema_(sema), type_mapper_(&ctx){};
+  virtual ~ImportContext(){};
+
   // Imports all decls contained in a `DeclContext`.
-  void ImportDeclsFromDeclContext(const clang::DeclContext* decl_context);
+  virtual void ImportDeclsFromDeclContext(
+      const clang::DeclContext* decl_context) = 0;
 
-  // Returns the Item of a Decl, importing it first if necessary.
-  std::optional<IR::Item> GetDeclItem(clang::Decl* decl);
+  // Imports an unsupported item with a single error message.
+  virtual IR::Item ImportUnsupportedItem(const clang::Decl* decl,
+                                         std::string error) = 0;
 
-  // Imports a decl and creates an IR item (or error messages).
-  // Does not use or update the cache.
-  std::optional<IR::Item> ImportDecl(clang::Decl* decl);
+  // Imports an unsupported item with multiple error messages.
+  virtual IR::Item ImportUnsupportedItem(const clang::Decl* decl,
+                                         std::set<std::string> errors) = 0;
 
-  // These functions import specific `Decl` subtypes. They use `LookupDecl` to
-  // lookup dependencies. They don't use or update the cache themselves.
-  std::optional<IR::Item> ImportNamespace(clang::NamespaceDecl* namespace_decl);
-  std::optional<IR::Item> ImportFunction(
-      clang::FunctionDecl* function_decl,
-      clang::FunctionTemplateDecl* function_template_decl = nullptr);
-  std::optional<IR::Item> ImportRecord(clang::CXXRecordDecl* record_decl);
-  std::optional<IR::Item> ImportTypedefName(
-      clang::TypedefNameDecl* typedef_name_decl);
-  std::optional<IR::Item> ImportEnum(clang::EnumDecl* enum_decl);
-
-  IR::Item ImportUnsupportedItem(const clang::Decl* decl, std::string error);
-  IR::Item ImportUnsupportedItem(const clang::Decl* decl,
-                                 std::set<std::string> errors);
-
-  absl::StatusOr<std::vector<Field>> ImportFields(
-      clang::CXXRecordDecl* record_decl);
-  // Stores the comments of this target in source order.
-  void ImportFreeComments();
   // Returns the item ids of the children and comments of the given decl in
   // source order. This method assumes that the children decls have already been
   // imported.
-  std::vector<ItemId> GetItemIdsInSourceOrder(clang::Decl* decl);
+  virtual std::vector<ItemId> GetItemIdsInSourceOrder(clang::Decl* decl) = 0;
 
-  std::string GetMangledName(const clang::NamedDecl* named_decl) const;
-  BazelLabel GetOwningTarget(const clang::Decl* decl) const;
+  // Mangles the name of a named decl.
+  virtual std::string GetMangledName(
+      const clang::NamedDecl* named_decl) const = 0;
+
+  // Returs the label of the target that contains a decl.
+  virtual BazelLabel GetOwningTarget(const clang::Decl* decl) const = 0;
 
   // Checks if the given decl belongs to the current target. Does not look into
   // other redeclarations of the decl.
-  bool IsFromCurrentTarget(const clang::Decl* decl) const;
+  virtual bool IsFromCurrentTarget(const clang::Decl* decl) const = 0;
 
   // Gets an IR UnqualifiedIdentifier for the named decl.
   //
@@ -190,36 +169,184 @@
   // a SpecialName.
   //
   // If the translated name is not yet implemented, this returns null.
-  std::optional<UnqualifiedIdentifier> GetTranslatedName(
-      const clang::NamedDecl* named_decl) const;
+  virtual std::optional<UnqualifiedIdentifier> GetTranslatedName(
+      const clang::NamedDecl* named_decl) const = 0;
 
   // GetTranslatedName, but only for identifier names. This is the common case.
+  virtual std::optional<Identifier> GetTranslatedIdentifier(
+      const clang::NamedDecl* named_decl) const = 0;
+
+  // Gets the doc comment of the declaration.
+  virtual llvm::Optional<std::string> GetComment(
+      const clang::Decl* decl) const = 0;
+
+  // Converts a Clang source location to IR.
+  virtual SourceLoc ConvertSourceLocation(clang::SourceLocation loc) const = 0;
+
+  Invocation& invocation_;
+  clang::ASTContext& ctx_;
+  clang::Sema& sema_;
+  TypeMapper type_mapper_;
+};
+
+// Interface for components that can import decls of a certain category.
+class DeclImporter {
+ public:
+  DeclImporter(ImportContext& ictx) : ictx_(ictx){};
+  virtual ~DeclImporter(){};
+
+  // Determines whether this importer is autoritative for a decl. This does not
+  // imply that the import will be succesful.
+  virtual bool CanImport(clang::Decl*) = 0;
+
+  // Returns an IR item for a decl, or `std::nullopt` if importing failed.
+  // This member function may only be called after `CanImport` returned `true`.
+  virtual std::optional<IR::Item> ImportDecl(clang::Decl*) = 0;
+
+ protected:
+  ImportContext& ictx_;
+};
+
+// Common implementation for defining `DeclImporter`s that determine their
+// applicability by the dynamic type of the decl.
+template <typename D>
+class DeclImporterBase : public DeclImporter {
+ public:
+  DeclImporterBase(ImportContext& context) : DeclImporter(context) {}
+
+ protected:
+  bool CanImport(clang::Decl* decl) { return clang::isa<D>(decl); }
+  std::optional<IR::Item> ImportDecl(clang::Decl* decl) {
+    return Import(clang::cast<D>(decl));
+  }
+  virtual std::optional<IR::Item> Import(D*);
+};
+
+// TODO(forster): Move those implementations into separate files.
+
+// A `DeclImporter` for `ClassTemplateDecl`s.
+class ClassTemplateDeclImporter
+    : public DeclImporterBase<clang::ClassTemplateDecl> {
+ public:
+  ClassTemplateDeclImporter(ImportContext& context)
+      : DeclImporterBase(context){};
+  std::optional<IR::Item> Import(clang::ClassTemplateDecl*);
+};
+
+// A `DeclImporter` for `CXXRecordDecl`s.
+class CXXRecordDeclImporter : public DeclImporterBase<clang::CXXRecordDecl> {
+ public:
+  CXXRecordDeclImporter(ImportContext& context) : DeclImporterBase(context){};
+  std::optional<IR::Item> Import(clang::CXXRecordDecl*);
+
+ private:
+  absl::StatusOr<std::vector<Field>> ImportFields(clang::CXXRecordDecl*);
+  std::vector<BaseClass> GetUnambiguousPublicBases(
+      const clang::CXXRecordDecl& record_decl) const;
+};
+
+// A `DeclImporter` for `EnumDecl`s.
+class EnumDeclImporter : public DeclImporterBase<clang::EnumDecl> {
+ public:
+  EnumDeclImporter(ImportContext& context) : DeclImporterBase(context){};
+  std::optional<IR::Item> Import(clang::EnumDecl*);
+};
+
+// A `DeclImporter` for `FunctionTemplateDecl`s.
+class FunctionTemplateDeclImporter
+    : public DeclImporterBase<clang::FunctionTemplateDecl> {
+ public:
+  FunctionTemplateDeclImporter(ImportContext& context)
+      : DeclImporterBase(context){};
+  std::optional<IR::Item> Import(clang::FunctionTemplateDecl*);
+};
+
+// A `DeclImporter` for `FunctionDecl`s.
+class FunctionDeclImporter : public DeclImporterBase<clang::FunctionDecl> {
+ public:
+  FunctionDeclImporter(ImportContext& context) : DeclImporterBase(context){};
+  std::optional<IR::Item> Import(clang::FunctionDecl*);
+};
+
+// A `DeclImporter` for `NamespaceDecl`s.
+class NamespaceDeclImporter : public DeclImporterBase<clang::NamespaceDecl> {
+ public:
+  NamespaceDeclImporter(ImportContext& context) : DeclImporterBase(context){};
+  std::optional<IR::Item> Import(clang::NamespaceDecl*);
+};
+
+// A `DeclImporter` for `TypedefNameDecl`s.
+class TypedefNameDeclImporter
+    : public DeclImporterBase<clang::TypedefNameDecl> {
+ public:
+  TypedefNameDeclImporter(ImportContext& context) : DeclImporterBase(context){};
+  std::optional<IR::Item> Import(clang::TypedefNameDecl*);
+};
+
+// Iterates over the AST created from the invocation's entry headers and
+// creates an intermediate representation of the import (`IR`) into the
+// invocation object.
+class Importer : public ImportContext {
+ public:
+  explicit Importer(Invocation& invocation, clang::ASTContext& ctx,
+                    clang::Sema& sema)
+      : ImportContext(invocation, ctx, sema),
+        mangler_(CRUBIT_DIE_IF_NULL(ctx_.createMangleContext())) {
+    decl_importers_.push_back(
+        std::make_unique<ClassTemplateDeclImporter>(*this));
+    decl_importers_.push_back(std::make_unique<CXXRecordDeclImporter>(*this));
+    decl_importers_.push_back(std::make_unique<EnumDeclImporter>(*this));
+    decl_importers_.push_back(std::make_unique<FunctionDeclImporter>(*this));
+    decl_importers_.push_back(
+        std::make_unique<FunctionTemplateDeclImporter>(*this));
+    decl_importers_.push_back(std::make_unique<NamespaceDeclImporter>(*this));
+    decl_importers_.push_back(std::make_unique<TypedefNameDeclImporter>(*this));
+  }
+
+  // Import all visible declarations from a translation unit.
+  void Import(clang::TranslationUnitDecl* decl);
+
+ protected:
+  // Implementation of `ImportContext`
+  void ImportDeclsFromDeclContext(
+      const clang::DeclContext* decl_context) override;
+  IR::Item ImportUnsupportedItem(const clang::Decl* decl,
+                                 std::string error) override;
+  IR::Item ImportUnsupportedItem(const clang::Decl* decl,
+                                 std::set<std::string> errors) override;
+  std::vector<ItemId> GetItemIdsInSourceOrder(clang::Decl* decl) override;
+  std::string GetMangledName(const clang::NamedDecl* named_decl) const override;
+  BazelLabel GetOwningTarget(const clang::Decl* decl) const override;
+  bool IsFromCurrentTarget(const clang::Decl* decl) const override;
+  std::optional<UnqualifiedIdentifier> GetTranslatedName(
+      const clang::NamedDecl* named_decl) const override;
   std::optional<Identifier> GetTranslatedIdentifier(
-      const clang::NamedDecl* named_decl) const {
+      const clang::NamedDecl* named_decl) const override {
     if (std::optional<UnqualifiedIdentifier> name =
             GetTranslatedName(named_decl)) {
       return std::move(*std::get_if<Identifier>(&*name));
     }
     return std::nullopt;
   }
+  llvm::Optional<std::string> GetComment(
+      const clang::Decl* decl) const override;
+  SourceLoc ConvertSourceLocation(clang::SourceLocation loc) const override;
 
-  // Gets the doc comment of the declaration.
-  llvm::Optional<std::string> GetComment(const clang::Decl* decl) const;
+ private:
+  // Returns the Item of a Decl, importing it first if necessary.
+  std::optional<IR::Item> GetDeclItem(clang::Decl* decl);
 
-  SourceLoc ConvertSourceLocation(clang::SourceLocation loc) const;
+  // Imports a decl and creates an IR item (or error messages).
+  // Does not use or update the cache.
+  std::optional<IR::Item> ImportDecl(clang::Decl* decl);
 
-  std::vector<BaseClass> GetUnambiguousPublicBases(
-      const clang::CXXRecordDecl& record_decl) const;
+  // Stores the comments of this target in source order.
+  void ImportFreeComments();
 
-  Invocation& invocation_;
-
-  clang::ASTContext& ctx_;
-  clang::Sema& sema_;
-
+  std::vector<std::unique_ptr<DeclImporter>> decl_importers_;
   std::unique_ptr<clang::MangleContext> mangler_;
   absl::flat_hash_map<const clang::Decl*, std::optional<IR::Item>>
       import_cache_;
-  TypeMapper type_mapper_;
   std::vector<const clang::RawComment*> comments_;
 };  // class Importer
 
diff --git a/rs_bindings_from_cc/ir_from_cc.cc b/rs_bindings_from_cc/ir_from_cc.cc
index 1fd9166..45ff3be 100644
--- a/rs_bindings_from_cc/ir_from_cc.cc
+++ b/rs_bindings_from_cc/ir_from_cc.cc
@@ -70,8 +70,8 @@
       "-fparse-all-comments"};
   args_as_strings.insert(args_as_strings.end(), args.begin(), args.end());
 
-  if (Importer::Invocation invocation(current_target, entrypoint_headers,
-                                      headers_to_targets);
+  if (Invocation invocation(current_target, entrypoint_headers,
+                            headers_to_targets);
       clang::tooling::runToolOnCodeWithArgs(
           std::make_unique<FrontendAction>(invocation),
           virtual_input_file_content, args_as_strings, kVirtualInputPath,