Make a common `Item` -> `UnsupportedItem` path.

This removes the myriad ways to create an `UnsupportedItem`, and will make it easier to convert an item to "unsupported" for new reasons, as well. (In particular, I want to convert arbitrary items to unsupported in a followup CL if they are filtered out by the features flags checks.)

I was tempted to use a custom type implementing `Display`, rather than just `Rc<str>`, but it's actually an annoying amount of work and doesn't hide the type completely. (You end up with `type DebugName<'a> : Display; fn debug_name<'a>(&'a self, ir: &'a IR) -> Self::DebugName<'a>;` so that the Display impl can hold onto the two references.)

PiperOrigin-RevId: 518732262
diff --git a/common/ffi_types.rs b/common/ffi_types.rs
index 3f334f9..ee75fdf 100644
--- a/common/ffi_types.rs
+++ b/common/ffi_types.rs
@@ -94,7 +94,7 @@
 /// Whether or not the generated binding will have doc comments indicating their
 /// source location.
 #[repr(C)]
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
 pub enum SourceLocationDocComment {
     Disabled,
     Enabled,
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index e7f0e94..894f532 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -13,11 +13,38 @@
 use serde::Deserialize;
 use std::collections::hash_map::{Entry, HashMap};
 use std::convert::TryFrom;
-use std::fmt::{self, Debug, Formatter};
+use std::fmt::{self, Debug, Display, Formatter};
 use std::hash::{Hash, Hasher};
 use std::io::Read;
 use std::rc::Rc;
 
+/// Common data about all items.
+pub trait GenericItem {
+    fn id(&self) -> ItemId;
+    /// The name of the item, readable by programmers.
+    ///
+    /// For example, `void Foo();` should have name `Foo`.
+    fn debug_name(&self, ir: &IR) -> Rc<str>;
+
+    /// The recorded source location, or None if none is present.
+    fn source_loc(&self) -> Option<Rc<str>>;
+}
+
+impl<T> GenericItem for Rc<T>
+where
+    T: GenericItem + ?Sized,
+{
+    fn id(&self) -> ItemId {
+        (**self).id()
+    }
+    fn debug_name(&self, ir: &IR) -> Rc<str> {
+        (**self).debug_name(ir)
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        (**self).source_loc()
+    }
+}
+
 /// Deserialize `IR` from JSON given as a reader.
 pub fn deserialize_ir<R: Read>(reader: R) -> Result<IR> {
     let flat_ir = serde_json::from_reader(reader)?;
@@ -199,9 +226,15 @@
     pub identifier: Rc<str>,
 }
 
-impl fmt::Debug for Identifier {
+impl Display for Identifier {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.write_str(&format!("\"{}\"", &self.identifier))
+        write!(f, "{}", self.identifier)
+    }
+}
+
+impl Debug for Identifier {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "\"{}\"", self.identifier)
     }
 }
 
@@ -228,9 +261,9 @@
     }
 }
 
-impl fmt::Debug for Operator {
+impl Debug for Operator {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.write_str(&format!("\"{}\"", &self.cc_name()))
+        write!(f, "\"{}\"", self.cc_name())
     }
 }
 
@@ -274,8 +307,8 @@
     }
 }
 
-impl std::fmt::Display for BazelLabel {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+impl Display for BazelLabel {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(f, "{}", &*self.0)
     }
 }
@@ -297,11 +330,11 @@
     }
 }
 
-impl fmt::Debug for UnqualifiedIdentifier {
+impl Debug for UnqualifiedIdentifier {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            UnqualifiedIdentifier::Identifier(identifier) => fmt::Debug::fmt(identifier, f),
-            UnqualifiedIdentifier::Operator(op) => fmt::Debug::fmt(op, f),
+            UnqualifiedIdentifier::Identifier(identifier) => Debug::fmt(identifier, f),
+            UnqualifiedIdentifier::Operator(op) => Debug::fmt(op, f),
             UnqualifiedIdentifier::Constructor => f.write_str("Constructor"),
             UnqualifiedIdentifier::Destructor => f.write_str("Destructor"),
         }
@@ -363,6 +396,36 @@
     pub adl_enclosing_record: Option<ItemId>,
 }
 
+impl GenericItem for Func {
+    fn id(&self) -> ItemId {
+        self.id
+    }
+    fn debug_name(&self, ir: &IR) -> Rc<str> {
+        let record: Option<Rc<str>> = ir.record_for_member_func(self).map(|r| r.debug_name(ir));
+        let record: Option<&str> = record.as_deref();
+
+        let func_name = match &self.name {
+            UnqualifiedIdentifier::Identifier(id) => id.identifier.to_string(),
+            UnqualifiedIdentifier::Operator(op) => op.cc_name(),
+            UnqualifiedIdentifier::Destructor => {
+                format!("~{}", record.expect("destructor must be associated with a record"))
+            }
+            UnqualifiedIdentifier::Constructor => {
+                record.expect("constructor must be associated with a record").to_string()
+            }
+        };
+
+        if let Some(record_name) = record {
+            format!("{}::{}", record_name, func_name).into()
+        } else {
+            func_name.into()
+        }
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        Some(self.source_loc.clone())
+    }
+}
+
 impl Func {
     pub fn is_instance_method(&self) -> bool {
         self.member_func_metadata
@@ -422,6 +485,18 @@
     pub enclosing_namespace_id: Option<ItemId>,
 }
 
+impl GenericItem for IncompleteRecord {
+    fn id(&self) -> ItemId {
+        self.id
+    }
+    fn debug_name(&self, _: &IR) -> Rc<str> {
+        self.cc_name.clone()
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        None
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize)]
 pub enum RecordType {
     Struct,
@@ -471,6 +546,18 @@
     pub enclosing_namespace_id: Option<ItemId>,
 }
 
+impl GenericItem for Record {
+    fn id(&self) -> ItemId {
+        self.id
+    }
+    fn debug_name(&self, _: &IR) -> Rc<str> {
+        self.cc_name.clone()
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        Some(self.source_loc.clone())
+    }
+}
+
 impl Record {
     /// Whether this type has Rust-like object semantics for mutating
     /// assignment, and can be passed by mut reference as a result.
@@ -522,6 +609,18 @@
     pub enclosing_namespace_id: Option<ItemId>,
 }
 
+impl GenericItem for Enum {
+    fn id(&self) -> ItemId {
+        self.id
+    }
+    fn debug_name(&self, _: &IR) -> Rc<str> {
+        self.identifier.to_string().into()
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        Some(self.source_loc.clone())
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
 #[serde(deny_unknown_fields)]
 pub struct Enumerator {
@@ -542,6 +641,18 @@
     pub enclosing_namespace_id: Option<ItemId>,
 }
 
+impl GenericItem for TypeAlias {
+    fn id(&self) -> ItemId {
+        self.id
+    }
+    fn debug_name(&self, _: &IR) -> Rc<str> {
+        self.identifier.to_string().into()
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        Some(self.source_loc.clone())
+    }
+}
+
 /// A wrapper type that does not contribute to equality or hashing. All
 /// instances are equal.
 #[derive(Clone, Copy, Default)]
@@ -570,31 +681,42 @@
 pub struct UnsupportedItem {
     pub name: Rc<str>,
     message: Rc<str>,
-    pub source_loc: Rc<str>,
+    pub source_loc: Option<Rc<str>>,
     pub id: ItemId,
     #[serde(skip)]
     cause: IgnoredField<OnceCell<Error>>,
 }
 
+impl GenericItem for UnsupportedItem {
+    fn id(&self) -> ItemId {
+        self.id
+    }
+    fn debug_name(&self, _: &IR) -> Rc<str> {
+        self.name.clone()
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        self.source_loc.clone()
+    }
+}
+
 impl UnsupportedItem {
-    pub fn new_with_message(name: &str, message: &str, source_loc: Rc<str>, id: ItemId) -> Self {
+    fn new(ir: &IR, item: &impl GenericItem, message: Rc<str>, cause: Option<Error>) -> Self {
         Self {
-            name: name.into(),
-            message: message.into(),
-            source_loc,
-            id,
-            cause: Default::default(),
+            name: item.debug_name(ir),
+            message,
+            source_loc: item.source_loc(),
+            id: item.id(),
+            cause: IgnoredField(cause.map(OnceCell::from).unwrap_or_default()),
         }
     }
-    pub fn new_with_cause(name: String, cause: Error, source_loc: Rc<str>, id: ItemId) -> Self {
-        Self {
-            name: name.into(),
-            message: cause.to_string().into(),
-            source_loc,
-            id,
-            cause: IgnoredField(cause.into()),
-        }
+
+    pub fn new_with_message(ir: &IR, item: &impl GenericItem, message: impl Into<Rc<str>>) -> Self {
+        Self::new(ir, item, message.into(), None)
     }
+    pub fn new_with_cause(ir: &IR, item: &impl GenericItem, cause: Error) -> Self {
+        Self::new(ir, item, cause.to_string().into(), Some(cause))
+    }
+
     pub fn message(&self) -> &str {
         self.message.as_ref()
     }
@@ -611,6 +733,18 @@
     pub id: ItemId,
 }
 
+impl GenericItem for Comment {
+    fn id(&self) -> ItemId {
+        self.id
+    }
+    fn debug_name(&self, _: &IR) -> Rc<str> {
+        "comment".into()
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        None
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
 #[serde(deny_unknown_fields)]
 pub struct Namespace {
@@ -624,6 +758,18 @@
     pub is_inline: bool,
 }
 
+impl GenericItem for Namespace {
+    fn id(&self) -> ItemId {
+        self.id
+    }
+    fn debug_name(&self, _: &IR) -> Rc<str> {
+        self.name.to_string().into()
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        None
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
 #[serde(deny_unknown_fields)]
 pub struct UseMod {
@@ -632,6 +778,18 @@
     pub id: ItemId,
 }
 
+impl GenericItem for UseMod {
+    fn id(&self) -> ItemId {
+        self.id
+    }
+    fn debug_name(&self, _: &IR) -> Rc<str> {
+        format!("[internal] use mod {}::* = {}", self.mod_name, self.path).into()
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        None
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
 pub enum Item {
     Func(Rc<Func>),
@@ -645,20 +803,47 @@
     UseMod(Rc<UseMod>),
 }
 
-impl Item {
+macro_rules! forward_item {
+    (match $item:ident { _($item_name:ident) => $expr:expr $(,)? }) => {
+        match $item {
+                                                    Item::Func($item_name) => $expr,
+                                                    Item::IncompleteRecord($item_name) => $expr,
+                                                    Item::Record($item_name) => $expr,
+                                                    Item::Enum($item_name) => $expr,
+                                                    Item::TypeAlias($item_name) => $expr,
+                                                    Item::UnsupportedItem($item_name) => $expr,
+                                                    Item::Comment($item_name) => $expr,
+                                                    Item::Namespace($item_name) => $expr,
+                                                    Item::UseMod($item_name) => $expr,
+                                                }
+    };
+}
+
+impl GenericItem for Item {
     fn id(&self) -> ItemId {
-        match self {
-            Item::Func(func) => func.id,
-            Item::IncompleteRecord(record) => record.id,
-            Item::Record(record) => record.id,
-            Item::Enum(enum_) => enum_.id,
-            Item::TypeAlias(type_alias) => type_alias.id,
-            Item::UnsupportedItem(unsupported) => unsupported.id,
-            Item::Comment(comment) => comment.id,
-            Item::Namespace(namespace) => namespace.id,
-            Item::UseMod(use_mod) => use_mod.id,
+        forward_item! {
+            match self {
+                _(x) => x.id()
+            }
         }
     }
+    fn debug_name(&self, ir: &IR) -> Rc<str> {
+        forward_item! {
+            match self {
+                _(x) => x.debug_name(ir)
+            }
+        }
+    }
+    fn source_loc(&self) -> Option<Rc<str>> {
+        forward_item! {
+            match self {
+                _(x) => x.source_loc()
+            }
+        }
+    }
+}
+
+impl Item {
     pub fn enclosing_namespace_id(&self) -> Option<ItemId> {
         match self {
             Item::Record(record) => record.enclosing_namespace_id,
@@ -823,16 +1008,16 @@
 /// A custom debug impl that wraps the HashMap in rustfmt-friendly notation.
 ///
 /// See b/272530008.
-impl std::fmt::Debug for FlatIR {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+impl Debug for FlatIR {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         struct DebugHashMap<T: Debug>(pub T);
-        impl<T: Debug> std::fmt::Debug for DebugHashMap<T> {
-            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        impl<T: Debug> Debug for DebugHashMap<T> {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                 // prefix the hash map with `hash_map!` so that the output can be fed to
                 // rustfmt. The end result is something like `hash_map!{k:v,
                 // k2:v2}`, which reads well.
                 write!(f, "hash_map!")?;
-                std::fmt::Debug::fmt(&self.0, f)
+                Debug::fmt(&self.0, f)
             }
         }
         // exhaustive-match so we don't forget to add fields to Debug when we add to
@@ -1028,9 +1213,13 @@
     /// in `self`.
     pub fn record_for_member_func(&self, func: &Func) -> Option<&Rc<Record>> {
         if let Some(meta) = func.member_func_metadata.as_ref() {
-            Some(self.find_decl(meta.record_id).with_context(|| {
-                format!("Failed to retrieve Record for MemberFuncMetadata of {:?}", func)
-            }).unwrap())
+            Some(
+                self.find_decl(meta.record_id)
+                    .with_context(|| {
+                        format!("Failed to retrieve Record for MemberFuncMetadata of {:?}", func)
+                    })
+                    .unwrap(),
+            )
         } else {
             None
         }
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index 8823b89..9468d1f 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -3838,109 +3838,86 @@
 
 #[test]
 fn test_source_location_with_macro() {
-    for (ir_type, cc_snippet, expected_source_loc) in [
-        (
-            quote! {Func},
-            r#"
+    let assert_matches = |cc_snippet: &str, expected: proc_macro2::TokenStream| {
+        let ir = ir_from_cc(cc_snippet).unwrap();
+        assert_ir_matches!(ir, expected);
+    };
+    let loc = "Generated from: google3/ir_from_cc_virtual_header.h;l=5\n\
+        Expanded at: google3/ir_from_cc_virtual_header.h;l=7";
+    assert_matches(
+        r#"
 #define NO_OP_FUNC(func_name) \
   void fun_name();
 
 NO_OP_FUNC(no_op_func_to_test_source_location_with_macro);"#,
-            "Generated from: google3/ir_from_cc_virtual_header.h;l=5\n\
-             Expanded at: google3/ir_from_cc_virtual_header.h;l=7",
-        ),
-        (
-            quote! {TypeAlias},
-            r#"
+        quote! {Func { ..., source_loc: #loc, ... } },
+    );
+
+    let loc = "Generated from: google3/ir_from_cc_virtual_header.h;l=4\n\
+        Expanded at: google3/ir_from_cc_virtual_header.h;l=5";
+    assert_matches(
+        r#"
 #define TYPE_ALIAS_TO_INT(type_alias) using type_alias = int;
 TYPE_ALIAS_TO_INT(MyIntToTestSourceLocationWithMacro);"#,
-            "Generated from: google3/ir_from_cc_virtual_header.h;l=4\n\
-             Expanded at: google3/ir_from_cc_virtual_header.h;l=5",
-        ),
-        (
-            quote! {UnsupportedItem},
-            r#"
+        quote! {TypeAlias { ..., source_loc: #loc, ... } },
+    );
+    let loc = "Generated from: google3/ir_from_cc_virtual_header.h;l=4\n\
+        Expanded at: google3/ir_from_cc_virtual_header.h;l=6";
+    assert_matches(
+        r#"
 #define TEMPLATE_NO_OP_FUNC(func_name) \
 template <typename T> void func_name() {};
   TEMPLATE_NO_OP_FUNC(unsupported_templated_no_op_func_to_test_source_location_with_macro);"#,
-            "Generated from: google3/ir_from_cc_virtual_header.h;l=4\n\
-             Expanded at: google3/ir_from_cc_virtual_header.h;l=6",
-        ),
-        (
-            quote! {Enum},
-            r#"
+        quote! {UnsupportedItem { ..., source_loc: Some(#loc,), ... } },
+    );
+
+    let loc = "Generated from: google3/ir_from_cc_virtual_header.h;l=5\n\
+        Expanded at: google3/ir_from_cc_virtual_header.h;l=6";
+    assert_matches(
+        r#"
 #define DEFINE_EMPTY_ENUM(enum_name) \
   enum enum_name {};
 DEFINE_EMPTY_ENUM(EmptyEnumToTestSourceLocationWithMacro);"#,
-            "Generated from: google3/ir_from_cc_virtual_header.h;l=5\n\
-             Expanded at: google3/ir_from_cc_virtual_header.h;l=6",
-        ),
-        (
-            quote! {Record},
-            r#"
+        quote! {Enum { ..., source_loc: #loc, ... } },
+    );
+
+    let loc = "Generated from: google3/ir_from_cc_virtual_header.h;l=5\n\
+        Expanded at: google3/ir_from_cc_virtual_header.h;l=6";
+    assert_matches(
+        r#"
 #define DEFINE_EMPTY_STRUCT(struct_name) \
   struct struct_name {};
 DEFINE_EMPTY_STRUCT(EmptyStructToTestSourceLocationWithMacro);"#,
-            "Generated from: google3/ir_from_cc_virtual_header.h;l=5\n\
-             Expanded at: google3/ir_from_cc_virtual_header.h;l=6",
-        ),
-    ] {
-        let ir = ir_from_cc(cc_snippet).unwrap();
-        assert_ir_matches!(
-            ir,
-            quote! {
-            #ir_type {
-              ...
-                source_loc: #expected_source_loc,
-              ...
-              }
-            }
-        );
-    }
+        quote! {Record { ..., source_loc: #loc, ... } },
+    );
 }
 
 #[test]
 fn test_source_location() {
-    for (ir_type, cc_snippet, expected_source_loc) in [
-        (
-            quote! {Func},
-            "void no_op_func_to_test_source_location();",
-            "Generated from: google3/ir_from_cc_virtual_header.h;l=3",
-        ),
-        (
-            quote! {TypeAlias},
-            r#"
-typedef float SomeTypedefToTestSourceLocation;"#,
-            "Generated from: google3/ir_from_cc_virtual_header.h;l=4",
-        ),
-        (
-            quote! {UnsupportedItem},
-            r#"  template <typename T> void unsupported_templated_func_to_test_source_location() {}"#,
-            "Generated from: google3/ir_from_cc_virtual_header.h;l=3",
-        ),
-        (
-            quote! {Enum},
-            r#"enum SomeEmptyEnumToTestSourceLocation {};"#,
-            "Generated from: google3/ir_from_cc_virtual_header.h;l=3",
-        ),
-        (
-            quote! {Record},
-            r#"struct SomeEmptyStructToTestSourceLocation {};"#,
-            "Generated from: google3/ir_from_cc_virtual_header.h;l=3",
-        ),
-    ] {
+    let assert_matches = |cc_snippet: &str, expected: proc_macro2::TokenStream| {
         let ir = ir_from_cc(cc_snippet).unwrap();
-        assert_ir_matches!(
-            ir,
-            quote! {
-            #ir_type {
-              ...
-                source_loc: #expected_source_loc,
-              ...
-              }
-            }
-        );
-    }
+        assert_ir_matches!(ir, expected);
+    };
+    assert_matches(
+        "void no_op_func_to_test_source_location();",
+        quote! {Func { ..., source_loc: "Generated from: google3/ir_from_cc_virtual_header.h;l=3", ... } },
+    );
+    assert_matches(
+        r#"typedef float SomeTypedefToTestSourceLocation;"#,
+        quote! {TypeAlias { ..., source_loc: "Generated from: google3/ir_from_cc_virtual_header.h;l=3", ... } },
+    );
+    assert_matches(
+        r#"  template <typename T> void unsupported_templated_func_to_test_source_location() {}"#,
+        quote! {UnsupportedItem { ..., source_loc: Some("Generated from: google3/ir_from_cc_virtual_header.h;l=3"), ... } },
+    );
+    assert_matches(
+        r#"enum SomeEmptyEnumToTestSourceLocation {};"#,
+        quote! {Enum { ..., source_loc: "Generated from: google3/ir_from_cc_virtual_header.h;l=3", ... } },
+    );
+    assert_matches(
+        r#"struct SomeEmptyStructToTestSourceLocation {};"#,
+        quote! {Record { ..., source_loc: "Generated from: google3/ir_from_cc_virtual_header.h;l=3", ... } },
+    );
 }
 
 #[test]
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index d4836dd..eb926bc 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -345,47 +345,6 @@
     function_path: syn::Path,
 }
 
-/// Returns the name of `func` in C++ syntax.
-fn cxx_function_name(func: &Func, ir: &IR) -> String {
-    let record: Option<&str> = ir.record_for_member_func(func).map(|r| r.cc_name.as_ref());
-
-    let func_name = match &func.name {
-        UnqualifiedIdentifier::Identifier(id) => id.identifier.to_string(),
-        UnqualifiedIdentifier::Operator(op) => op.cc_name(),
-        UnqualifiedIdentifier::Destructor => {
-            format!("~{}", record.expect("destructor must be associated with a record"))
-        }
-        UnqualifiedIdentifier::Constructor => {
-            record.expect("constructor must be associated with a record").to_string()
-        }
-    };
-
-    if let Some(record_name) = record {
-        format!("{}::{}", record_name, func_name)
-    } else {
-        func_name
-    }
-}
-
-fn make_unsupported_fn(func: &Func, ir: &IR, message: &str) -> Result<UnsupportedItem> {
-    Ok(UnsupportedItem::new_with_message(
-        cxx_function_name(func, ir).as_ref(),
-        message,
-        func.source_loc.clone(),
-        func.id,
-    ))
-}
-
-fn make_unsupported_nested_type_alias(type_alias: &TypeAlias) -> Result<UnsupportedItem> {
-    Ok(UnsupportedItem::new_with_message(
-        // TODO(jeanpierreda): It would be nice to include the enclosing record name here too.
-        type_alias.identifier.identifier.as_ref(),
-        "Typedefs nested in classes are not supported yet",
-        type_alias.source_loc.clone(),
-        type_alias.id,
-    ))
-}
-
 /// The name of a one-function trait, with extra entries for
 /// specially-understood traits and families of traits.
 #[derive(Clone, Debug, Eq, PartialEq)]
@@ -2606,16 +2565,17 @@
 ) -> Result<GeneratedItem> {
     errors.insert(item.cause());
 
+    let source_loc = item.source_loc();
+    let source_loc = match &source_loc {
+        Some(loc) if generate_source_loc_doc_comment == SourceLocationDocComment::Enabled => {
+            loc.as_ref()
+        }
+        _ => "",
+    };
+
     let message = format!(
-        "{}{}Error while generating bindings for item '{}':\n{}",
-        match generate_source_loc_doc_comment {
-            SourceLocationDocComment::Enabled => item.source_loc.as_ref(),
-            SourceLocationDocComment::Disabled => "",
-        },
-        match generate_source_loc_doc_comment {
-            SourceLocationDocComment::Enabled => "\n",
-            SourceLocationDocComment::Disabled => "",
-        },
+        "{source_loc}{}Error while generating bindings for item '{}':\n{}",
+        if source_loc.len() > 0 { "\n" } else { "" },
         item.name.as_ref(),
         item.message()
     );
@@ -2796,7 +2756,7 @@
     let generated_item = match item {
         Item::Func(func) => match db.generate_func(func.clone()) {
             Err(e) => generate_unsupported(
-                &make_unsupported_fn(func, &ir, format!("{e}").as_str())?,
+                &UnsupportedItem::new_with_message(&ir, func, format!("{e}")),
                 errors,
                 db.generate_source_loc_doc_comment(),
             )?,
@@ -2804,11 +2764,11 @@
             Ok(Some((item, function_id))) => {
                 if overloaded_funcs.contains(&function_id) {
                     generate_unsupported(
-                        &make_unsupported_fn(
-                            func,
+                        &UnsupportedItem::new_with_message(
                             &ir,
+                            func,
                             "Cannot generate bindings for overloaded function",
-                        )?,
+                        ),
                         errors,
                         db.generate_source_loc_doc_comment(),
                     )?
@@ -2824,7 +2784,11 @@
             if type_alias.enclosing_record_id.is_some() {
                 // TODO(b/200067824): support nested type aliases.
                 generate_unsupported(
-                    &make_unsupported_nested_type_alias(type_alias)?,
+                    &UnsupportedItem::new_with_message(
+                        &ir,
+                        type_alias,
+                        "Typedefs nested in classes are not supported yet",
+                    ),
                     errors,
                     db.generate_source_loc_doc_comment(),
                 )?
@@ -8742,41 +8706,70 @@
         assert_rs_matches!(actual, quote! {#[doc = " Some doc comment"]});
     }
 
-    #[test]
-    fn test_generate_unsupported_item_with_source_loc_enabled() {
-        let unsupported_item = UnsupportedItem::new_with_message(
-            "unsupported_item",
-            "unsupported_message",
-            "Generated from: google3/some/header;l=1".into(),
-            ItemId::new_for_testing(123),
-        );
-        let actual = generate_unsupported(
-            &unsupported_item,
-            &mut ErrorReport::new(),
-            SourceLocationDocComment::Enabled,
-        )
-        .unwrap();
-        let expected = "Generated from: google3/some/header;l=1\nError while generating bindings for item 'unsupported_item':\nunsupported_message";
-        assert_rs_matches!(actual.item, quote! { __COMMENT__ #expected});
+    struct TestItem {
+        source_loc: Option<Rc<str>>,
+    }
+    impl ir::GenericItem for TestItem {
+        fn id(&self) -> ItemId {
+            ItemId::new_for_testing(123)
+        }
+        fn debug_name(&self, _: &IR) -> Rc<str> {
+            "test_item".into()
+        }
+        fn source_loc(&self) -> Option<Rc<str>> {
+            self.source_loc.clone()
+        }
     }
 
     #[test]
-    fn test_generate_unsupported_item_with_source_loc_disabled() {
-        let unsupported_item = UnsupportedItem::new_with_message(
-            "unsupported_item2",
-            "unsupported_message2",
-            "Generated from: google3/some/header;l=2".into(),
-            ItemId::new_for_testing(1234),
-        );
+    fn test_generate_unsupported_item_with_source_loc_enabled() -> Result<()> {
         let actual = generate_unsupported(
-            &unsupported_item,
+            &UnsupportedItem::new_with_message(
+                &make_ir_from_items([])?,
+                &TestItem {source_loc: Some("Generated from: google3/some/header;l=1".into())},
+                "unsupported_message",
+            ),
+            &mut ErrorReport::new(),
+            SourceLocationDocComment::Enabled,
+        )?;
+        let expected = "Generated from: google3/some/header;l=1\nError while generating bindings for item 'test_item':\nunsupported_message";
+        assert_rs_matches!(actual.item, quote! { __COMMENT__ #expected});
+        Ok(())
+    }
+
+    /// Not all items currently have source_loc(), e.g. comments.
+    ///
+    /// For these, we omit the mention of the location.
+    #[test]
+    fn test_generate_unsupported_item_with_missing_source_loc() -> Result<()> {
+        let actual = generate_unsupported(
+            &UnsupportedItem::new_with_message(
+                &make_ir_from_items([])?,
+                &TestItem {source_loc: None},
+                "unsupported_message",
+            ),
+            &mut ErrorReport::new(),
+            SourceLocationDocComment::Enabled,
+        )?;
+        let expected = "Error while generating bindings for item 'test_item':\nunsupported_message";
+        assert_rs_matches!(actual.item, quote! { __COMMENT__ #expected});
+        Ok(())
+    }
+
+    #[test]
+    fn test_generate_unsupported_item_with_source_loc_disabled() -> Result<()> {
+        let actual = generate_unsupported(
+            &UnsupportedItem::new_with_message(
+                &make_ir_from_items([])?,
+                &TestItem {source_loc: Some("Generated from: google3/some/header;l=1".into())},
+                "unsupported_message",
+            ),
             &mut ErrorReport::new(),
             SourceLocationDocComment::Disabled,
-        )
-        .unwrap();
-        let expected =
-            "Error while generating bindings for item 'unsupported_item2':\nunsupported_message2";
+        )?;
+        let expected = "Error while generating bindings for item 'test_item':\nunsupported_message";
         assert_rs_matches!(actual.item, quote! { __COMMENT__ #expected});
+        Ok(())
     }
 
     /// The default crubit feature set currently results in no bindings at all.