Only emit comments from the public headers.

Also, move the logic for parsing headers into a single function
invocation, instead of the somewhat complicated state machine that we
needed while using an `AstVisitor`

PiperOrigin-RevId: 424637057
diff --git a/rs_bindings_from_cc/importer.cc b/rs_bindings_from_cc/importer.cc
index cf034be..5fcbea1 100644
--- a/rs_bindings_from_cc/importer.cc
+++ b/rs_bindings_from_cc/importer.cc
@@ -35,6 +35,7 @@
 #include "third_party/llvm/llvm-project/clang/include/clang/AST/RawCommentList.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/AST/RecordLayout.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/AST/Type.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/Basic/FileManager.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/Basic/SourceLocation.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/Basic/SourceManager.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/Basic/Specifiers.h"
@@ -101,12 +102,57 @@
 
   ImportDeclsFromDeclContext(translation_unit_decl);
 
-  // Emit comments after the last decl
-  comment_manager_.FlushComments();
-
   EmitIRItems();
 }
 
+std::vector<clang::RawComment*> Importer::ImportFreeComments() {
+  clang::SourceManager& sm = ctx_->getSourceManager();
+
+  // We put all comments into an ordered set in source order. Later we'll remove
+  // the comments that we don't want or that we get by other means.
+  auto source_order = [&sm](const clang::SourceLocation& a,
+                            const clang::SourceLocation& b) {
+    return b.isValid() && (a.isInvalid() || sm.isBeforeInTranslationUnit(a, b));
+  };
+  auto ordered_comments = std::map<clang::SourceLocation, clang::RawComment*,
+                                   decltype(source_order)>(source_order);
+
+  // We start off by getting the comments from all public header files...
+  for (const auto& header : public_header_names_) {
+    if (auto file = sm.getFileManager().getFile(header.IncludePath())) {
+      if (auto comments = ctx_->Comments.getCommentsInFile(
+              sm.getOrCreateFileID(*file, clang::SrcMgr::C_User))) {
+        for (const auto& [_, comment] : *comments) {
+          ordered_comments.insert({comment->getBeginLoc(), comment});
+        }
+      }
+    }
+  }
+
+  // ... and then we remove those that "conflict" with an IR item.
+  for (const auto& [decl, result] : lookup_cache_) {
+    if (result.item()) {
+      // Remove doc comments of imported items.
+      if (auto raw_comment = ctx_->getRawCommentForDeclNoCache(decl)) {
+        ordered_comments.erase(raw_comment->getBeginLoc());
+      }
+      // Remove comments that are within a visited decl.
+      // TODO(forster): We should retain floating comments in decls like
+      // records and namespaces.
+      ordered_comments.erase(ordered_comments.lower_bound(decl->getBeginLoc()),
+                             ordered_comments.upper_bound(decl->getEndLoc()));
+    }
+  }
+
+  // Return the remaining comments as a `std::vector`.
+  std::vector<clang::RawComment*> result;
+  result.reserve(ordered_comments.size());
+  for (auto& [_, comment] : ordered_comments) {
+    result.push_back(comment);
+  }
+  return result;
+}
+
 void Importer::EmitIRItems() {
   using OrderedItem = std::tuple<clang::SourceRange, int, IR::Item>;
   clang::SourceManager& sm = ctx_->getSourceManager();
@@ -169,7 +215,7 @@
     }
   }
 
-  for (auto comment : comment_manager_.comments()) {
+  for (auto comment : ImportFreeComments()) {
     items.push_back(std::make_tuple(
         comment->getSourceRange(), 0 /* local_order */,
         Comment{.text = comment->getFormattedText(sm, sm.getDiagnostics())}));
@@ -184,9 +230,6 @@
 void Importer::ImportDeclsFromDeclContext(
     const clang::DeclContext* decl_context) {
   for (auto decl : decl_context->decls()) {
-    // Emit all comments in the current file before the decl
-    comment_manager_.TraverseDecl(decl);
-
     LookupDecl(decl->getCanonicalDecl());
 
     if (auto* nested_context = clang::dyn_cast<clang::DeclContext>(decl)) {
@@ -798,65 +841,4 @@
   }
 }
 
-void Importer::CommentManager::TraverseDecl(clang::Decl* decl) {
-  ctx_ = &decl->getASTContext();
-
-  // When we go to a new file we flush the comments from the previous file,
-  // because source locations won't be comparable by '<' any more.
-  clang::FileID file = ctx_->getSourceManager().getFileID(decl->getBeginLoc());
-  // For example, we hit an invalid FileID for virtual destructor definitions.
-  if (file.isInvalid()) {
-    return;
-  }
-  if (file != current_file_) {
-    FlushComments();
-    current_file_ = file;
-    LoadComments();
-  }
-
-  // Visit all comments from the current file up to the current decl.
-  clang::RawComment* decl_comment = ctx_->getRawCommentForDeclNoCache(decl);
-  while (next_comment_ != file_comments_.end() &&
-         (*next_comment_)->getBeginLoc() < decl->getBeginLoc()) {
-    // Skip the decl's doc comment, which will be emitted as part of the decl.
-    if (*next_comment_ != decl_comment) {
-      VisitTopLevelComment(*next_comment_);
-    }
-    ++next_comment_;
-  }
-
-  // Skip comments that are within the decl, e.g., comments in the body of an
-  // inline function
-  // TODO(forster): We should retain floating comments within `Record`s
-  if (!clang::isa<clang::NamespaceDecl>(decl)) {
-    while (next_comment_ != file_comments_.end() &&
-           (*next_comment_)->getBeginLoc() < decl->getEndLoc()) {
-      ++next_comment_;
-    }
-  }
-}
-
-void Importer::CommentManager::LoadComments() {
-  auto comments = ctx_->Comments.getCommentsInFile(current_file_);
-  if (comments) {
-    for (auto [_, comment] : *comments) {
-      file_comments_.push_back(comment);
-    }
-  }
-  next_comment_ = file_comments_.begin();
-}
-
-void Importer::CommentManager::FlushComments() {
-  while (next_comment_ != file_comments_.end()) {
-    VisitTopLevelComment(*next_comment_);
-    next_comment_++;
-  }
-  file_comments_.clear();
-}
-
-void Importer::CommentManager::VisitTopLevelComment(
-    const clang::RawComment* comment) {
-  comments_.push_back(comment);
-}
-
 }  // namespace rs_bindings_from_cc
diff --git a/rs_bindings_from_cc/importer.h b/rs_bindings_from_cc/importer.h
index 58a77b9..843d228 100644
--- a/rs_bindings_from_cc/importer.h
+++ b/rs_bindings_from_cc/importer.h
@@ -91,6 +91,7 @@
 
   absl::StatusOr<std::vector<Field>> ImportFields(
       clang::RecordDecl* record_decl, clang::AccessSpecifier default_access);
+  std::vector<clang::RawComment*> ImportFreeComments();
   void EmitIRItems();
 
   std::string GetMangledName(const clang::NamedDecl* named_decl) const;
@@ -151,35 +152,6 @@
   std::unique_ptr<clang::MangleContext> mangler_;
   absl::flat_hash_map<const clang::Decl*, LookupResult> lookup_cache_;
   absl::flat_hash_set<const clang::TypeDecl*> known_type_decls_;
-
-  // A component that keeps track of all top-level comments that are not doc
-  // comments.
-  class CommentManager {
-   public:
-    // Notify the comment manager that we the visitor is traversing a decl.
-    // This will emit IR for all preceding comments.
-    void TraverseDecl(clang::Decl* decl);
-
-    // Emit IR for the remaining comments after the last decl.
-    void FlushComments();
-
-    // Return the found comments.
-    const std::vector<const clang::RawComment*>& comments() const {
-      return comments_;
-    }
-
-   private:
-    void LoadComments();
-    void VisitTopLevelComment(const clang::RawComment* comment);
-
-    clang::ASTContext* ctx_;
-    clang::FileID current_file_;
-    std::vector<const clang::RawComment*> file_comments_;
-    std::vector<const clang::RawComment*>::iterator next_comment_;
-    std::vector<const clang::RawComment*> comments_;
-  };
-
-  CommentManager comment_manager_;
   const devtools_rust::LifetimeAnnotationContext& lifetime_context_;
 };  // class AstVisitor
 
diff --git a/rs_bindings_from_cc/importer_test.cc b/rs_bindings_from_cc/importer_test.cc
index 91e0f92..1da8f50 100644
--- a/rs_bindings_from_cc/importer_test.cc
+++ b/rs_bindings_from_cc/importer_test.cc
@@ -284,7 +284,9 @@
 }
 
 TEST(ImporterTest, Noop) {
-  ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc("// nothing interesting there."));
+  // Nothing interesting there, but also not empty, so that the header gets
+  // generated.
+  ASSERT_OK_AND_ASSIGN(IR ir, IrFromCc(" "));
 
   EXPECT_THAT(ItemsWithoutBuiltins(ir), IsEmpty());
 }
diff --git a/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs b/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs
index 4c95390..3bf22f0 100644
--- a/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs
@@ -11,8 +11,6 @@
 
 pub type __builtin_ms_va_list = *mut u8;
 
-// CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_TRIVIAL_TYPE_H_
-
 #[inline(always)]
 pub fn UsesImportedType(t: trivial_type_cc::Trivial) -> trivial_type_cc::Trivial {
     unsafe { crate::detail::__rust_thunk___Z16UsesImportedType7Trivial(t) }
diff --git a/rs_bindings_from_cc/test/golden/user_of_unsupported_rs_api.rs b/rs_bindings_from_cc/test/golden/user_of_unsupported_rs_api.rs
index a4c0981..b87f48d 100644
--- a/rs_bindings_from_cc/test/golden/user_of_unsupported_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/user_of_unsupported_rs_api.rs
@@ -9,10 +9,6 @@
 
 pub type __builtin_ms_va_list = *mut u8;
 
-// namespace ns
-
-// CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_UNSUPPORTED_H_
-
 // rs_bindings_from_cc/test/golden/user_of_unsupported.h;l=8
 // Error while generating bindings for item 'UseNontrivialCustomType':
 // Non-trivial_abi type 'struct NontrivialCustomType' is not supported by value as a parameter