Add source location information to diagnostics.

PiperOrigin-RevId: 402541826
diff --git a/rs_bindings_from_cc/ast_visitor.cc b/rs_bindings_from_cc/ast_visitor.cc
index 11dc5b1..205ac25 100644
--- a/rs_bindings_from_cc/ast_visitor.cc
+++ b/rs_bindings_from_cc/ast_visitor.cc
@@ -20,6 +20,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/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"
 #include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Casting.h"
@@ -47,7 +48,21 @@
   return Base::TraverseTranslationUnitDecl(translation_unit_decl);
 }
 
+SourceLoc ConvertSourceLoc(const clang::SourceManager& sm,
+                           clang::SourceLocation loc) {
+  auto filename = sm.getFileEntryForID(sm.getFileID(loc))->getName();
+  if (filename.startswith("./")) {
+    filename = filename.substr(2);
+  }
+
+  return SourceLoc{.filename = filename.str(),
+                   .line = sm.getSpellingLineNumber(loc),
+                   .column = sm.getSpellingColumnNumber(loc)};
+}
+
 bool AstVisitor::VisitFunctionDecl(clang::FunctionDecl* function_decl) {
+  auto& sm = function_decl->getASTContext().getSourceManager();
+
   std::vector<FuncParam> params;
   bool success = true;
 
@@ -58,7 +73,8 @@
       ir_.items.push_back(UnsupportedItem{
           .name = function_decl->getQualifiedNameAsString(),
           .message = absl::Substitute("Parameter type '$0' is not supported",
-                                      param->getType().getAsString())});
+                                      param->getType().getAsString()),
+          .source_loc = ConvertSourceLoc(sm, param->getBeginLoc())});
       success = false;
       continue;
     }
@@ -67,7 +83,7 @@
       ir_.items.push_back(UnsupportedItem{
           .name = function_decl->getQualifiedNameAsString(),
           .message = "Empty parameter names are not supported",
-      });
+          .source_loc = ConvertSourceLoc(sm, param->getBeginLoc())});
       success = false;
       continue;
     }
@@ -81,7 +97,9 @@
         .name = function_decl->getQualifiedNameAsString(),
         .message =
             absl::Substitute("Return type '$0' is not supported",
-                             function_decl->getReturnType().getAsString())});
+                             function_decl->getReturnType().getAsString()),
+        .source_loc = ConvertSourceLoc(
+            sm, function_decl->getReturnTypeSourceRange().getBegin())});
     success = false;
   }
   std::optional<Identifier> translated_name = GetTranslatedName(function_decl);
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index 4ab4879..a86e9cf 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -165,11 +165,19 @@
   item["Record"] = std::move(record);
   return item;
 }
+nlohmann::json SourceLoc::ToJson() const {
+  nlohmann::json source_loc;
+  source_loc["filename"] = filename;
+  source_loc["line"] = line;
+  source_loc["column"] = column;
+  return source_loc;
+}
 
 nlohmann::json UnsupportedItem::ToJson() const {
   nlohmann::json unsupported;
   unsupported["name"] = name;
   unsupported["message"] = message;
+  unsupported["source_loc"] = source_loc.ToJson();
 
   nlohmann::json item;
   item["UnsupportedItem"] = std::move(unsupported);
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index a1fb1b3..a52a2ee 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -262,6 +262,19 @@
   return o << r.ToJson();
 }
 
+// Source code location
+struct SourceLoc {
+  nlohmann::json ToJson() const;
+
+  std::string filename;
+  uint64 line;
+  uint64 column;
+};
+
+inline std::ostream& operator<<(std::ostream& o, const SourceLoc& r) {
+  return o << r.ToJson();
+}
+
 // A placeholder for an item that we can't generate bindings for (yet)
 struct UnsupportedItem {
   nlohmann::json ToJson() const;
@@ -275,6 +288,7 @@
   // Explanation of why we couldn't generate bindings
   // TODO(forster): We should support multiple reasons per unsupported item.
   std::string message;
+  SourceLoc source_loc;
 };
 
 inline std::ostream& operator<<(std::ostream& o, const UnsupportedItem& r) {
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 7afe5c4..42ce646 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -102,9 +102,17 @@
 }
 
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
+pub struct SourceLoc {
+    pub filename: String,
+    pub line: u64,
+    pub column: u64,
+}
+
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
 pub struct UnsupportedItem {
     pub name: String,
     pub message: String,
+    pub source_loc: SourceLoc,
 }
 
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 71d22f3..11a114a 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -259,8 +259,15 @@
 
 /// Generates Rust source code for a given `UnsupportedItem`.
 fn generate_unsupported(item: &UnsupportedItem) -> Result<TokenStream> {
-    let message =
-        format!("Error while generating bindings for item '{}':\n{}", &item.name, &item.message);
+    let message = format!(
+        // TODO(forster): The "google3" prefix should probably come from a command line argument.
+        "google3/{}:{}:{}\nError while generating bindings for item '{}':\n{}",
+        &item.source_loc.filename,
+        &item.source_loc.line,
+        &item.source_loc.column,
+        &item.name,
+        &item.message
+    );
     Ok(quote! { __COMMENT__ #message })
 }
 
diff --git a/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs b/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs
index b657d8a..2eeeba2 100644
--- a/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs
@@ -14,6 +14,7 @@
 
 impl !Unpin for Nontrivial {}
 
+// rs_bindings_from_cc/test/golden/nontrivial_type.h:5:14
 // Error while generating bindings for item 'Nontrivial::Nontrivial':
 // Parameter type 'struct Nontrivial &&' is not supported
 
diff --git a/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs b/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
index 8caf9c1..bdc0727 100644
--- a/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
@@ -13,21 +13,27 @@
     pub i: i32,
 }
 
+// rs_bindings_from_cc/test/golden/unsupported.h:8:27
 // Error while generating bindings for item 'UnsupportedParamType':
 // Parameter type 'struct CustomType' is not supported
 
+// rs_bindings_from_cc/test/golden/unsupported.h:9:30
 // Error while generating bindings for item 'UnsupportedUnnamedParam':
 // Empty parameter names are not supported
 
+// rs_bindings_from_cc/test/golden/unsupported.h:10:1
 // Error while generating bindings for item 'UnsupportedReturnType':
 // Return type 'struct CustomType' is not supported
 
+// rs_bindings_from_cc/test/golden/unsupported.h:12:28
 // Error while generating bindings for item 'MultipleReasons':
 // Parameter type 'struct CustomType' is not supported
 
+// rs_bindings_from_cc/test/golden/unsupported.h:12:42
 // Error while generating bindings for item 'MultipleReasons':
 // Empty parameter names are not supported
 
+// rs_bindings_from_cc/test/golden/unsupported.h:12:1
 // Error while generating bindings for item 'MultipleReasons':
 // Return type 'struct CustomType' is not supported