Pass error format strings through the IR

These formatted errors exist separately on the C++ and Rust sides of
the generator. This change brings the two sides together.

PiperOrigin-RevId: 653405910
Change-Id: I0b655338cc22a0a8c30315aa01206e4cdb785606
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index 7cc40c8..11c293b 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -346,6 +346,7 @@
     deps = [
         "//common:arc_anyhow",
         "//common:code_gen_utils",
+        "//common:error_report",
         "@crate_index//:flagset",
         "@crate_index//:itertools",
         "@crate_index//:once_cell",
diff --git a/rs_bindings_from_cc/generate_bindings/lib.rs b/rs_bindings_from_cc/generate_bindings/lib.rs
index 1cabb3e..87e3e6d 100644
--- a/rs_bindings_from_cc/generate_bindings/lib.rs
+++ b/rs_bindings_from_cc/generate_bindings/lib.rs
@@ -335,7 +335,9 @@
 
 /// Generates Rust source code for a given `UnsupportedItem`.
 fn generate_unsupported(db: &Database, item: &UnsupportedItem) -> Result<GeneratedItem> {
-    db.errors().insert(item.cause());
+    for error in &item.errors {
+        db.errors().insert(&error.to_error());
+    }
 
     let source_loc = item.source_loc();
     let source_loc = match &source_loc {
@@ -345,12 +347,14 @@
         _ => "",
     };
 
-    let message = format!(
-        "{source_loc}{}Error while generating bindings for item '{}':\n{}",
+    let mut message = format!(
+        "{source_loc}{}Error while generating bindings for item '{}':\n",
         if source_loc.is_empty() { "" } else { "\n" },
         item.name.as_ref(),
-        item.message()
     );
+    for (index, error) in item.errors.iter().enumerate() {
+        message = format!("{message}{}{}", if index == 0 { "" } else { "\n\n" }, error.message,);
+    }
     Ok(GeneratedItem { item: quote! { __COMMENT__ #message }, ..Default::default() })
 }
 
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index 0c1106c..654f553 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -532,17 +532,15 @@
 }
 
 llvm::json::Value UnsupportedItem::ToJson() const {
-  std::string message;
+  std::vector<llvm::json::Value> json_errors;
+  json_errors.reserve(errors.size());
   for (const auto& error : errors) {
-    if (!message.empty()) {
-      message += "\n\n";
-    }
-    message += error.message;
+    json_errors.push_back(error.ToJson());
   }
 
   llvm::json::Object unsupported{
       {"name", name},
-      {"message", message},
+      {"errors", json_errors},
       {"source_loc", source_loc},
       {"id", id},
   };
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 349f111..a0f6bda 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -804,11 +804,28 @@
     fn hash<H: Hasher>(&self, _state: &mut H) {}
 }
 
+#[derive(Debug, PartialEq, Eq, Hash, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct FormattedError {
+    pub fmt: Rc<str>,
+    pub message: Rc<str>,
+}
+
+impl FormattedError {
+    pub fn to_error(&self) -> Error {
+        error_report::FormattedError {
+            fmt: self.fmt.to_string().into(),
+            message: self.message.to_string().into(),
+        }
+        .into()
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
 #[serde(deny_unknown_fields)]
 pub struct UnsupportedItem {
     pub name: Rc<str>,
-    message: Rc<str>,
+    pub errors: Vec<Rc<FormattedError>>,
     pub source_loc: Option<Rc<str>>,
     pub id: ItemId,
     #[serde(skip)]
@@ -834,7 +851,7 @@
     fn new(ir: &IR, item: &impl GenericItem, message: Rc<str>, cause: Option<Error>) -> Self {
         Self {
             name: item.debug_name(ir),
-            message,
+            errors: vec![Rc::new(FormattedError { fmt: "{}".into(), message })],
             source_loc: item.source_loc(),
             id: item.id(),
             cause: IgnoredField(cause.map(OnceCell::from).unwrap_or_default()),
@@ -847,14 +864,6 @@
     pub fn new_with_cause(ir: &IR, item: &impl GenericItem, cause: Error) -> Self {
         Self::new(ir, item, format!("{cause:#}").into(), Some(cause))
     }
-
-    pub fn message(&self) -> &str {
-        self.message.as_ref()
-    }
-
-    pub fn cause(&self) -> &Error {
-        self.cause.0.get_or_init(|| anyhow!(self.message.as_ref().to_owned()))
-    }
 }
 
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index aa78601..38db848 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -172,9 +172,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "Self", ...
-          message: "Record name is not supported: Unescapable identifier: Self"
-          ...
+            name: "Self", ...
+            errors: [FormattedError {
+                ... message: "Record name is not supported: Unescapable identifier: Self", ...
+            }], ...
         }}
     );
 }
@@ -185,9 +186,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "Self", ...
-          message: "Enum name is not supported: Unescapable identifier: Self"
-          ...
+            name: "Self", ...
+            errors: [FormattedError {
+                ... message: "Enum name is not supported: Unescapable identifier: Self", ...
+            }], ...
         }}
     );
 }
@@ -198,9 +200,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "SomeEnum", ...
-          message: "Enumerator name is not supported: Unescapable identifier: self"
-          ...
+            name: "SomeEnum", ...
+            errors: [FormattedError {
+                ..., message: "Enumerator name is not supported: Unescapable identifier: self", ...
+            }], ...
         }}
     );
 }
@@ -211,9 +214,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "Self", ...
-          message: "Record name is not supported: Unescapable identifier: Self"
-          ...
+            name: "Self", ...
+            errors: [FormattedError {
+                ..., message: "Record name is not supported: Unescapable identifier: Self", ...
+            }], ...
         }}
     );
 }
@@ -243,9 +247,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "self", ...
-          message: "Namespace name is not supported: Unescapable identifier: self"
-          ...
+            name: "self", ...
+            errors: [FormattedError {
+                ..., message: "Namespace name is not supported: Unescapable identifier: self", ...
+            }], ...
         }}
     );
 }
@@ -256,9 +261,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "self", ...
-          message: "Function name is not supported: Unescapable identifier: self"
-          ...
+            name: "self", ...
+            errors: [FormattedError {
+                ..., message: "Function name is not supported: Unescapable identifier: self", ...
+            }], ...
         }}
     );
 }
@@ -269,9 +275,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "Self", ...
-          message: "Type alias name is not supported: Unescapable identifier: Self"
-          ...
+            name: "Self", ...
+            errors: [FormattedError {
+                ..., message: "Type alias name is not supported: Unescapable identifier: Self", ...
+            }], ...
         }}
     );
 }
@@ -321,8 +328,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "MyTemplate",
-          message: "Class templates are not supported yet" ...
+            name: "MyTemplate",
+            errors: [FormattedError {
+                ..., message: "Class templates are not supported yet", ...
+            }], ...
         }}
     );
 }
@@ -333,8 +342,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "SomeFunctionTemplate",
-          message: "Function templates are not supported yet" ...
+            name: "SomeFunctionTemplate",
+            errors: [FormattedError {
+                ..., message: "Function templates are not supported yet", ...
+            }], ...
         }}
     );
 }
@@ -482,9 +493,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "PackedStruct", ...
-          message: "Records with packed layout are not supported"
-          ...
+            name: "PackedStruct", ...
+            errors: [FormattedError {
+                ..., message: "Records with packed layout are not supported", ...
+            }], ...
         }}
     );
 }
@@ -504,9 +516,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "PackedStruct", ...
-          message: "Records with packed layout are not supported"
-          ...
+            name: "PackedStruct", ...
+            errors: [FormattedError {
+                ..., message: "Records with packed layout are not supported", ...
+            }], ...
         }}
     );
 }
@@ -2323,8 +2336,10 @@
     assert_ir_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "SomeStruct::NestedStruct",
-          message: "Nested classes are not supported yet" ...
+            name: "SomeStruct::NestedStruct",
+            errors: [FormattedError {
+                ..., message: "Nested classes are not supported yet", ...
+            }], ...
         }}
     );
 }
@@ -2378,8 +2393,9 @@
         quote! {
             UnsupportedItem {
                 name: "StructWithUnsupportedField::NestedStruct",
-                message: "Nested classes are not supported yet",
-                ...
+                errors: [FormattedError {
+                    ..., message: "Nested classes are not supported yet", ...
+                }], ...
             }
         }
     );
@@ -2443,9 +2459,10 @@
         ir,
         quote! {
            UnsupportedItem {
-               name: "OuterStruct::NestedStruct",
-               message: "Nested classes are not supported yet",
-               ...
+                name: "OuterStruct::NestedStruct",
+                errors: [FormattedError {
+                    ..., message: "Nested classes are not supported yet", ...
+                }], ...
            }
         }
     );
@@ -2504,8 +2521,10 @@
     assert_ir_not_matches!(
         ir,
         quote! { UnsupportedItem {
-          name: "SomeStruct::SomeStruct",
-          message: "Nested classes are not supported yet" ...
+            name: "SomeStruct::SomeStruct",
+            errors: [FormattedError {
+                ..., message: "Nested classes are not supported yet", ...
+            }], ...
         }}
     );
 }
@@ -3179,7 +3198,10 @@
 #[test]
 fn test_volatile_is_unsupported() {
     let ir = ir_from_cc("volatile int* foo();").unwrap();
-    let f = ir.unsupported_items().find(|i| i.message().contains("volatile")).unwrap();
+    let f = ir
+        .unsupported_items()
+        .find(|i| i.errors.iter().any(|e| e.message.contains("volatile")))
+        .unwrap();
     assert_eq!("foo", f.name.as_ref());
 }
 
@@ -3191,7 +3213,9 @@
         quote! {
             UnsupportedItem {
                 name: "(unnamed enum at ./ir_from_cc_virtual_header.h:3:1)",
-                message: "Unnamed enums are not supported yet" ...
+                errors: [FormattedError {
+                    ..., message: "Unnamed enums are not supported yet", ...
+                }], ...
             }
         }
     );
@@ -3211,8 +3235,9 @@
         quote! {
             UnsupportedItem {
                 name: "operator\"\"_foobar",
-                message: "Function name is not supported: Unsupported name: operator\"\"_foobar"
-                ...
+                errors: [FormattedError {
+                    ..., message: "Function name is not supported: Unsupported name: operator\"\"_foobar", ...
+                }], ...
             }
         }
     );
@@ -3364,7 +3389,9 @@
             quote! {
               ... UnsupportedItem {
                   name: "TopLevelStruct::operator int",
-                  message: "Function name is not supported: Unsupported name: operator int",
+                  errors: [FormattedError {
+                    ..., message: "Function name is not supported: Unsupported name: operator int",
+                  }],
                   ...
               }
             },