Repository: rust-lang/rust-bindgen
Pull request #3178 (2025-03-22 06:12:41)

Add support for vectors that use "ext_vector_type" 
The SIMD header provided by Apple uses [ext_vector_type](https://clang.llvm.org/docs/LanguageExtensions.html#vectors-and-extended-vectors) to define vectors. Previously bindgen would generate an opaque type as seen [here](https://github.com/gfx-rs/metal-rs/blob/cce2b486dd39d19ba232e823148ee2c557e97e62/examples/texture/src/main.rs#L112). The vector extension seems to behave similar to the already defined vector, so it doesn't look like using that case overlooks anything. 


Patch:
diff --git a/.github/workflows/bindgen.yml b/.github/workflows/bindgen.yml
index df17f2c5..d0112e36 100644
--- a/.github/workflows/bindgen.yml
+++ b/.github/workflows/bindgen.yml
@@ -171,7 +171,7 @@ jobs:
           BINDGEN_FEATURE_RUNTIME: ${{matrix.feature_runtime}}
           BINDGEN_FEATURE_EXTRA_ASSERTS: ${{matrix.feature_extra_asserts}}
           BINDGEN_NO_DEFAULT_FEATURES: ${{matrix.no_default_features}}
-          BINDGEN_RUST_FOR_LINUX_TEST: ${{matrix.os == 'ubuntu-latest' && matrix.llvm_version == '16.0' && matrix.feature_extra_asserts == 0 && 1 || 0}}
+          BINDGEN_RUST_FOR_LINUX_TEST: 0
         run: ./ci/test.sh
 
   check-cfg:
diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs
index c2be6682..cd7bb977 100644
--- a/bindgen/callbacks.rs
+++ b/bindgen/callbacks.rs
@@ -224,7 +224,21 @@ pub enum DiscoveredItem {
         /// The final name of the generated binding
         final_name: String,
     },
-    // functions, modules, etc.
+
+    /// A function or method.
+    Function {
+        /// The final name used.
+        final_name: String,
+    },
+
+    /// A method.
+    Method {
+        /// The final name used.
+        final_name: String,
+
+        /// Type to which this method belongs.
+        parent: DiscoveredItemId,
+    }, // modules, etc.
 }
 
 /// Relevant information about a type to which new derive attributes will be added using
diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs
index f5518e43..801eaaba 100644
--- a/bindgen/codegen/mod.rs
+++ b/bindgen/codegen/mod.rs
@@ -2481,6 +2481,7 @@ impl CodeGenerator for CompInfo {
 
         let is_rust_union = is_union && struct_layout.is_rust_union();
 
+        let discovered_id = DiscoveredItemId::new(item.id().as_usize());
         ctx.options().for_each_callback(|cb| {
             let discovered_item = match self.kind() {
                 CompKind::Struct => DiscoveredItem::Struct {
@@ -2501,10 +2502,7 @@ impl CodeGenerator for CompInfo {
                 },
             };
 
-            cb.new_item_found(
-                DiscoveredItemId::new(item.id().as_usize()),
-                discovered_item,
-            );
+            cb.new_item_found(discovered_id, discovered_item);
         });
 
         // The custom derives callback may return a list of derive attributes;
@@ -2711,6 +2709,7 @@ impl CodeGenerator for CompInfo {
                         &mut method_names,
                         result,
                         self,
+                        discovered_id,
                     );
                 }
             }
@@ -2729,6 +2728,7 @@ impl CodeGenerator for CompInfo {
                         &mut method_names,
                         result,
                         self,
+                        discovered_id,
                     );
                 }
             }
@@ -2742,6 +2742,7 @@ impl CodeGenerator for CompInfo {
                         &mut method_names,
                         result,
                         self,
+                        discovered_id,
                     );
                 }
             }
@@ -2999,6 +3000,7 @@ impl Method {
         method_names: &mut HashSet<String>,
         result: &mut CodegenResult<'_>,
         _parent: &CompInfo,
+        parent_id: DiscoveredItemId,
     ) {
         assert!({
             let cc = &ctx.options().codegen_config;
@@ -3019,6 +3021,7 @@ impl Method {
 
         // First of all, output the actual function.
         let function_item = ctx.resolve_item(self.signature());
+        let id = DiscoveredItemId::new(function_item.id().as_usize());
         if !function_item.process_before_codegen(ctx, result) {
             return;
         }
@@ -3065,6 +3068,16 @@ impl Method {
 
         method_names.insert(name.clone());
 
+        ctx.options().for_each_callback(|cb| {
+            cb.new_item_found(
+                id,
+                DiscoveredItem::Method {
+                    parent: parent_id,
+                    final_name: name.clone(),
+                },
+            )
+        });
+
         let mut function_name = function_item.canonical_name(ctx);
         if times_seen > 0 {
             write!(&mut function_name, "{times_seen}").unwrap();
@@ -4540,6 +4553,7 @@ impl CodeGenerator for Function {
     ) -> Self::Return {
         debug!("<Function as CodeGenerator>::codegen: item = {item:?}");
         debug_assert!(item.is_enabled_for_codegen(ctx));
+        let id = DiscoveredItemId::new(item.id().as_usize());
 
         let is_internal = matches!(self.linkage(), Linkage::Internal);
 
@@ -4564,14 +4578,20 @@ impl CodeGenerator for Function {
             }
         }
 
-        // Pure virtual methods have no actual symbol, so we can't generate
-        // something meaningful for them.
-        let is_dynamic_function = match self.kind() {
-            FunctionKind::Method(ref method_kind)
-                if method_kind.is_pure_virtual() =>
-            {
-                return None;
+        let is_pure_virtual = match self.kind() {
+            FunctionKind::Method(ref method_kind) => {
+                method_kind.is_pure_virtual()
             }
+            _ => false,
+        };
+        if is_pure_virtual && !ctx.options().generate_pure_virtual_functions {
+            // Pure virtual methods have no actual symbol, so we can't generate
+            // something meaningful for them. Downstream code postprocessors
+            // might want to find out about them.
+            return None;
+        }
+
+        let is_dynamic_function = match self.kind() {
             FunctionKind::Function => {
                 ctx.options().dynamic_library_name.is_some()
             }
@@ -4650,6 +4670,14 @@ impl CodeGenerator for Function {
         if times_seen > 0 {
             write!(&mut canonical_name, "{times_seen}").unwrap();
         }
+        ctx.options().for_each_callback(|cb| {
+            cb.new_item_found(
+                id,
+                DiscoveredItem::Function {
+                    final_name: canonical_name.to_string(),
+                },
+            );
+        });
 
         let link_name_attr = self.link_name().or_else(|| {
             let mangled_name = mangled_name.unwrap_or(name);
diff --git a/bindgen/ir/function.rs b/bindgen/ir/function.rs
index 52fc94d9..65a12d4b 100644
--- a/bindgen/ir/function.rs
+++ b/bindgen/ir/function.rs
@@ -433,7 +433,7 @@ impl FunctionSig {
             spelling.starts_with("operator") &&
                 !clang::is_valid_identifier(spelling)
         };
-        if is_operator(&spelling) {
+        if is_operator(&spelling) && !ctx.options().represent_cxx_operators {
             return Err(ParseError::Continue);
         }
 
@@ -734,8 +734,9 @@ impl ClangSubItemParser for Function {
         if visibility != CXVisibility_Default {
             return Err(ParseError::Continue);
         }
-
-        if cursor.access_specifier() == CX_CXXPrivate {
+        if cursor.access_specifier() == CX_CXXPrivate &&
+            !context.options().generate_private_functions
+        {
             return Err(ParseError::Continue);
         }
 
@@ -755,7 +756,9 @@ impl ClangSubItemParser for Function {
                 return Err(ParseError::Continue);
             }
 
-            if cursor.is_deleted_function() {
+            if cursor.is_deleted_function() &&
+                !context.options().generate_deleted_functions
+            {
                 return Err(ParseError::Continue);
             }
 
diff --git a/bindgen/ir/ty.rs b/bindgen/ir/ty.rs
index 2589a56f..baaa4a49 100644
--- a/bindgen/ir/ty.rs
+++ b/bindgen/ir/ty.rs
@@ -1117,7 +1117,7 @@ impl Type {
 
                     TypeKind::Comp(complex)
                 }
-                CXType_Vector => {
+                CXType_Vector | CXType_ExtVector => {
                     let inner = Item::from_ty(
                         ty.elem_type().as_ref().unwrap(),
                         location,
diff --git a/bindgen/lib.rs b/bindgen/lib.rs
index 1a15d51d..5a946e6d 100644
--- a/bindgen/lib.rs
+++ b/bindgen/lib.rs
@@ -684,29 +684,44 @@ pub(crate) const HOST_TARGET: &str =
 fn rust_to_clang_target(rust_target: &str) -> Box<str> {
     const TRIPLE_HYPHENS_MESSAGE: &str = "Target triple should contain hyphens";
 
-    let mut clang_target = rust_target.to_owned();
+    let mut triple: Vec<&str> = rust_target.split_terminator('-').collect();
 
-    if clang_target.starts_with("riscv32") {
-        let idx = clang_target.find('-').expect(TRIPLE_HYPHENS_MESSAGE);
+    assert!(!triple.is_empty(), "{}", TRIPLE_HYPHENS_MESSAGE);
+    triple.resize(4, "");
 
-        clang_target.replace_range(..idx, "riscv32");
-    } else if clang_target.starts_with("riscv64") {
-        let idx = clang_target.find('-').expect(TRIPLE_HYPHENS_MESSAGE);
-
-        clang_target.replace_range(..idx, "riscv64");
-    } else if clang_target.starts_with("aarch64-apple-") {
-        let idx = clang_target.find('-').expect(TRIPLE_HYPHENS_MESSAGE);
-
-        clang_target.replace_range(..idx, "arm64");
+    // RISC-V
+    if triple[0].starts_with("riscv32") {
+        triple[0] = "riscv32";
+    } else if triple[0].starts_with("riscv64") {
+        triple[0] = "riscv64";
     }
 
-    if clang_target.ends_with("-espidf") {
-        let idx = clang_target.rfind('-').expect(TRIPLE_HYPHENS_MESSAGE);
+    // Apple
+    if triple[1] == "apple" {
+        if triple[0] == "aarch64" {
+            triple[0] = "arm64";
+        }
+        if triple[3] == "sim" {
+            triple[3] = "simulator";
+        }
+    }
 
-        clang_target.replace_range((idx + 1).., "elf");
+    // ESP-IDF
+    if triple[2] == "espidf" {
+        triple[2] = "elf";
     }
 
-    clang_target.into()
+    triple
+        .iter()
+        .skip(1)
+        .fold(triple[0].to_string(), |triple, part| {
+            if part.is_empty() {
+                triple
+            } else {
+                triple + "-" + part
+            }
+        })
+        .into()
 }
 
 /// Returns the effective target, and whether it was explicitly specified on the
@@ -1389,3 +1404,19 @@ fn test_rust_to_clang_target_espidf() {
         "xtensa-esp32-elf"
     );
 }
+
+#[test]
+fn test_rust_to_clang_target_simulator() {
+    assert_eq!(
+        rust_to_clang_target("aarch64-apple-ios-sim").as_ref(),
+        "arm64-apple-ios-simulator"
+    );
+    assert_eq!(
+        rust_to_clang_target("aarch64-apple-tvos-sim").as_ref(),
+        "arm64-apple-tvos-simulator"
+    );
+    assert_eq!(
+        rust_to_clang_target("aarch64-apple-watchos-sim").as_ref(),
+        "arm64-apple-watchos-simulator"
+    );
+}
diff --git a/bindgen/log_stubs.rs b/bindgen/log_stubs.rs
index 83159831..51d2f81f 100644
--- a/bindgen/log_stubs.rs
+++ b/bindgen/log_stubs.rs
@@ -1,5 +1,6 @@
 #![allow(unused)]
 
+#[clippy::format_args]
 macro_rules! log {
     (target: $target:expr, $lvl:expr, $($arg:tt)+) => {{
         let _ = $target;
@@ -10,22 +11,27 @@ macro_rules! log {
         let _ = format_args!($($arg)+);
     }};
 }
+#[clippy::format_args]
 macro_rules! error {
     (target: $target:expr, $($arg:tt)+) => { log!(target: $target, "", $($arg)+) };
     ($($arg:tt)+) => { log!("", $($arg)+) };
 }
+#[clippy::format_args]
 macro_rules! warn {
     (target: $target:expr, $($arg:tt)*) => { log!(target: $target, "", $($arg)*) };
     ($($arg:tt)*) => { log!("", $($arg)*) };
 }
+#[clippy::format_args]
 macro_rules! info {
     (target: $target:expr, $($arg:tt)+) => { log!(target: $target, "", $($arg)+) };
     ($($arg:tt)+) => { log!("", $($arg)+) };
 }
+#[clippy::format_args]
 macro_rules! debug {
     (target: $target:expr, $($arg:tt)+) => { log!(target: $target, "", $($arg)+) };
     ($($arg:tt)+) => { log!("", $($arg)+) };
 }
+#[clippy::format_args]
 macro_rules! trace {
     (target: $target:expr, $($arg:tt)+) => { log!(target: $target, "", $($arg)+) };
     ($($arg:tt)+) => { log!("", $($arg)+) };
diff --git a/bindgen/options/cli.rs b/bindgen/options/cli.rs
index f9a85729..d3cf5d09 100644
--- a/bindgen/options/cli.rs
+++ b/bindgen/options/cli.rs
@@ -447,6 +447,9 @@ struct BindgenCommand {
     /// Use distinct char16_t
     #[arg(long)]
     use_distinct_char16_t: bool,
+    /// Output C++ overloaded operators
+    #[arg(long)]
+    represent_cxx_operators: bool,
     /// Enables generation of vtable functions.
     #[arg(long)]
     vtable_generation: bool,
@@ -510,6 +513,18 @@ struct BindgenCommand {
     /// bitfields. This flag is ignored if the `--respect-cxx-access-specs` flag is used.
     #[arg(long, value_name = "VISIBILITY")]
     default_visibility: Option<FieldVisibilityKind>,
+    /// Whether to generate C++ functions marked with "=delete" even though they
+    /// can't be called.
+    #[arg(long)]
+    generate_deleted_functions: bool,
+    /// Whether to generate C++ "pure virtual" functions even though they can't
+    /// be called.
+    #[arg(long)]
+    generate_pure_virtual_functions: bool,
+    /// Whether to generate C++ private functions even though they can't
+    /// be called.
+    #[arg(long)]
+    generate_private_functions: bool,
     /// Whether to emit diagnostics or not.
     #[cfg(feature = "experimental")]
     #[arg(long, requires = "experimental")]
@@ -637,6 +652,7 @@ where
         explicit_padding,
         use_specific_virtual_function_receiver,
         use_distinct_char16_t,
+        represent_cxx_operators,
         vtable_generation,
         sort_semantically,
         merge_extern_blocks,
@@ -657,6 +673,9 @@ where
         wrap_static_fns_path,
         wrap_static_fns_suffix,
         default_visibility,
+        generate_deleted_functions,
+        generate_pure_virtual_functions,
+        generate_private_functions,
         #[cfg(feature = "experimental")]
         emit_diagnostics,
         generate_shell_completions,
@@ -936,6 +955,7 @@ where
             explicit_padding,
             use_specific_virtual_function_receiver,
             use_distinct_char16_t,
+            represent_cxx_operators,
             vtable_generation,
             sort_semantically,
             merge_extern_blocks,
@@ -948,6 +968,9 @@ where
             wrap_static_fns_path,
             wrap_static_fns_suffix,
             default_visibility,
+            generate_deleted_functions,
+            generate_pure_virtual_functions,
+            generate_private_functions,
         }
     );
 
diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs
index ab6b232e..c9ef7c8b 100644
--- a/bindgen/options/mod.rs
+++ b/bindgen/options/mod.rs
@@ -192,6 +192,24 @@ options! {
         },
         as_args: "--use-distinct-char16-t",
     },
+    /// Whether we should output C++ overloaded operators. By itself,
+    /// this option is not sufficient to produce valid output, because
+    /// such operators will have names that are not acceptable Rust
+    /// names (for example `operator=`). If you use this option, you'll also
+    /// have to rename the resulting functions - for example by using
+    /// [`ParseCallbacks::generated_name_override`].
+    represent_cxx_operators: bool {
+        methods: {
+            /// If this is true, output existence of C++ overloaded operators.
+            /// At present, only operator= is noted.
+            /// Disabled by default.
+            pub fn represent_cxx_operators(mut self, doit: bool) -> Builder {
+                self.options.represent_cxx_operators = doit;
+                self
+            }
+        },
+        as_args: "--represent-cxx-operators",
+    },
 
     /// Types that have been blocklisted and should not appear anywhere in the generated code.
     blocklisted_types: RegexSet {
@@ -2211,4 +2229,58 @@ options! {
         },
         as_args: "--clang-macro-fallback-build-dir",
     }
+    /// Whether to always report C++ "deleted" functions.
+    generate_deleted_functions: bool {
+        methods: {
+            /// Set whether to generate C++ functions even marked "=deleted"
+            ///
+            /// Although not useful to call these functions, downstream code
+            /// generators may need to know whether they've been deleted in
+            /// order to determine the relocatability of a C++ type
+            /// (specifically by virtue of which constructors exist.)
+            pub fn generate_deleted_functions(mut self, doit: bool) -> Self {
+                self.options.generate_deleted_functions = doit;
+                self
+            }
+
+        },
+        as_args: "--generate-deleted-functions",
+    },
+    /// Whether to always report C++ "pure virtual" functions.
+    generate_pure_virtual_functions: bool {
+        methods: {
+            /// Set whether to generate C++ functions that are pure virtual.
+            ///
+            /// These functions can't be called, so the only reason
+            /// to generate them is if downstream postprocessors
+            /// need to know of their existence. This is necessary,
+            /// for instance, to determine whether a type itself is
+            /// pure virtual and thus can't be allocated.
+            /// Downstream code generators may choose to make code to
+            /// allow types to be allocated but need to avoid doing so
+            /// if the type contains pure virtual functions.
+            pub fn generate_pure_virtual_functions(mut self, doit: bool) -> Self {
+                self.options.generate_pure_virtual_functions = doit;
+                self
+            }
+
+        },
+        as_args: "--generate-pure-virtual-functions",
+    },
+    /// Whether to always report C++ "private" functions.
+    generate_private_functions: bool {
+        methods: {
+            /// Set whether to generate C++ functions that are private.
+            ///
+            /// These functions can't be called, so the only reason
+            /// to generate them is if downstream postprocessors
+            /// need to know of their existence.
+            pub fn generate_private_functions(mut self, doit: bool) -> Self {
+                self.options.generate_private_functions = doit;
+                self
+            }
+
+        },
+        as_args: "--generate-private-functions",
+    },
 }


Test patch:
diff --git a/bindgen-tests/tests/expectations/tests/opencl_vector.rs b/bindgen-tests/tests/expectations/tests/opencl_vector.rs
new file mode 100644
index 00000000..3c2525c8
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/opencl_vector.rs
@@ -0,0 +1,6 @@
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+pub type float4 = [f32; 4usize];
+pub type float2 = [f32; 2usize];
+unsafe extern "C" {
+    pub fn foo(a: float2, b: float2) -> float4;
+}
diff --git a/bindgen-tests/tests/expectations/tests/operator_equals.rs b/bindgen-tests/tests/expectations/tests/operator_equals.rs
new file mode 100644
index 00000000..ff9f4fdd
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/operator_equals.rs
@@ -0,0 +1,24 @@
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct SomeClass {
+    pub _address: u8,
+}
+#[allow(clippy::unnecessary_operation, clippy::identity_op)]
+const _: () = {
+    ["Size of SomeClass"][::std::mem::size_of::<SomeClass>() - 1usize];
+    ["Alignment of SomeClass"][::std::mem::align_of::<SomeClass>() - 1usize];
+};
+unsafe extern "C" {
+    #[link_name = "\u{1}_ZN9SomeClassaSERKS_"]
+    pub fn SomeClass_operatorequals(
+        this: *mut SomeClass,
+        another: *const SomeClass,
+    ) -> bool;
+}
+impl SomeClass {
+    #[inline]
+    pub unsafe fn operatorequals(&mut self, another: *const SomeClass) -> bool {
+        SomeClass_operatorequals(self, another)
+    }
+}
diff --git a/bindgen-tests/tests/expectations/tests/uncallable_functions.rs b/bindgen-tests/tests/expectations/tests/uncallable_functions.rs
new file mode 100644
index 00000000..fddb5d41
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/uncallable_functions.rs
@@ -0,0 +1,46 @@
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+#[repr(C)]
+pub struct Test__bindgen_vtable {
+    pub Test_a: unsafe extern "C" fn(this: *mut Test),
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct Test {
+    pub vtable_: *const Test__bindgen_vtable,
+}
+#[allow(clippy::unnecessary_operation, clippy::identity_op)]
+const _: () = {
+    ["Size of Test"][::std::mem::size_of::<Test>() - 8usize];
+    ["Alignment of Test"][::std::mem::align_of::<Test>() - 8usize];
+};
+unsafe extern "C" {
+    #[link_name = "\u{1}_ZN4Test1bEv"]
+    pub fn Test_b(this: *mut Test);
+}
+unsafe extern "C" {
+    #[link_name = "\u{1}_ZN4Test1cEv"]
+    pub fn Test_c(this: *mut Test);
+}
+impl Default for Test {
+    fn default() -> Self {
+        let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+        unsafe {
+            ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+            s.assume_init()
+        }
+    }
+}
+impl Test {
+    #[inline]
+    pub unsafe fn b(&mut self) {
+        Test_b(self)
+    }
+    #[inline]
+    pub unsafe fn c(&mut self) {
+        Test_c(self)
+    }
+}
+unsafe extern "C" {
+    #[link_name = "\u{1}_ZN4Test1aEv"]
+    pub fn Test_a(this: *mut ::std::os::raw::c_void);
+}
diff --git a/bindgen-tests/tests/headers/opencl_vector.h b/bindgen-tests/tests/headers/opencl_vector.h
new file mode 100644
index 00000000..79022c76
--- /dev/null
+++ b/bindgen-tests/tests/headers/opencl_vector.h
@@ -0,0 +1,9 @@
+typedef float float4 __attribute__((ext_vector_type(4)));
+typedef float float2 __attribute__((ext_vector_type(2)));
+
+float4 foo(float2 a, float2 b) {
+  float4 c;
+  c.xz = a;
+  c.yw = b;
+  return c;
+}
\ No newline at end of file
diff --git a/bindgen-tests/tests/headers/operator_equals.hpp b/bindgen-tests/tests/headers/operator_equals.hpp
new file mode 100644
index 00000000..38a3a092
--- /dev/null
+++ b/bindgen-tests/tests/headers/operator_equals.hpp
@@ -0,0 +1,9 @@
+// bindgen-flags: --represent-cxx-operators --generate-inline-functions -- -x c++
+// bindgen-parse-callbacks: operator-rename
+
+class SomeClass {
+public:
+    bool operator=(const SomeClass& another) {
+        return false;
+    }
+};
diff --git a/bindgen-tests/tests/headers/uncallable_functions.hpp b/bindgen-tests/tests/headers/uncallable_functions.hpp
new file mode 100644
index 00000000..9187470c
--- /dev/null
+++ b/bindgen-tests/tests/headers/uncallable_functions.hpp
@@ -0,0 +1,9 @@
+// bindgen-flags: --generate-deleted-functions --generate-private-functions --generate-pure-virtual-functions --generate-inline-functions -- -x c++ -std=c++14
+
+class Test {
+public:
+    virtual void a() = 0;
+    void b() = delete;
+private:
+    void c() {}
+};
diff --git a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h
index b2bb04f1..eb44e5fc 100644
--- a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h
+++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h
@@ -25,4 +25,8 @@ enum NamedEnum {
     Fish,
 };
 
-typedef enum NamedEnum AliasOfNamedEnum;
\ No newline at end of file
+typedef enum NamedEnum AliasOfNamedEnum;
+
+// Functions
+
+void named_function();
diff --git a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp
new file mode 100644
index 00000000..1de8d99e
--- /dev/null
+++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp
@@ -0,0 +1,6 @@
+// Methods
+
+class SomeClass {
+public:
+    void named_method();
+};
\ No newline at end of file
diff --git a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs
index 93a2b029..645235b2 100644
--- a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs
+++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs
@@ -17,20 +17,28 @@ impl ParseCallbacks for ItemDiscovery {
         self.0.borrow_mut().insert(_id, _item);
     }
 }
-#[test]
-pub fn test_item_discovery_callback() {
+
+fn test_item_discovery_callback(
+    header: &str,
+    expected: HashMap<DiscoveredItemId, DiscoveredItem>,
+) {
     let discovery = ItemDiscovery::default();
     let info = Rc::clone(&discovery.0);
 
+    let mut header_path = env!("CARGO_MANIFEST_DIR").to_string();
+    header_path.push_str(header);
+
     Builder::default()
-        .header(concat!(
-        env!("CARGO_MANIFEST_DIR"),
-        "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h"
-        ))
+        .header(header_path)
         .parse_callbacks(Box::new(discovery))
         .generate()
         .expect("TODO: panic message");
 
+    compare_item_caches(&info.borrow(), &expected);
+}
+
+#[test]
+fn test_item_discovery_callback_c() {
     let expected = ItemCache::from([
         (
             DiscoveredItemId::new(10),
@@ -87,9 +95,37 @@ pub fn test_item_discovery_callback() {
                 final_name: "_bindgen_ty_*".to_string(),
             },
         ),
+        (
+            DiscoveredItemId::new(41),
+            DiscoveredItem::Function {
+                final_name: "named_function".to_string(),
+            },
+        ),
     ]);
+    test_item_discovery_callback(
+        "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h", expected);
+}
 
-    compare_item_caches(&info.borrow(), &expected);
+#[test]
+fn test_item_discovery_callback_cpp() {
+    let expected = ItemCache::from([
+        (
+            DiscoveredItemId::new(1),
+            DiscoveredItem::Struct {
+                original_name: Some("SomeClass".to_string()),
+                final_name: "SomeClass".to_string(),
+            },
+        ),
+        (
+            DiscoveredItemId::new(2),
+            DiscoveredItem::Method {
+                final_name: "named_method".to_string(),
+                parent: DiscoveredItemId::new(1),
+            },
+        ),
+    ]);
+    test_item_discovery_callback(
+        "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp", expected);
 }
 
 pub fn compare_item_caches(generated: &ItemCache, expected: &ItemCache) {
@@ -142,6 +178,12 @@ fn compare_item_info(
         DiscoveredItem::Enum { .. } => {
             compare_enum_info(expected_item, generated_item)
         }
+        DiscoveredItem::Function { .. } => {
+            compare_function_info(expected_item, generated_item)
+        }
+        DiscoveredItem::Method { .. } => {
+            compare_method_info(expected_item, generated_item)
+        }
     }
 }
 
@@ -279,3 +321,57 @@ pub fn compare_alias_info(
 
     compare_item_info(expected_aliased, generated_aliased, expected, generated)
 }
+
+pub fn compare_function_info(
+    expected_item: &DiscoveredItem,
+    generated_item: &DiscoveredItem,
+) -> bool {
+    let DiscoveredItem::Function {
+        final_name: expected_final_name,
+    } = expected_item
+    else {
+        unreachable!()
+    };
+
+    let DiscoveredItem::Function {
+        final_name: generated_final_name,
+    } = generated_item
+    else {
+        unreachable!()
+    };
+
+    if !compare_names(expected_final_name, generated_final_name) {
+        return false;
+    }
+    true
+}
+
+pub fn compare_method_info(
+    expected_item: &DiscoveredItem,
+    generated_item: &DiscoveredItem,
+) -> bool {
+    let DiscoveredItem::Method {
+        final_name: expected_final_name,
+        parent: expected_parent,
+    } = expected_item
+    else {
+        unreachable!()
+    };
+
+    let DiscoveredItem::Method {
+        final_name: generated_final_name,
+        parent: generated_parent,
+    } = generated_item
+    else {
+        unreachable!()
+    };
+
+    if expected_parent != generated_parent {
+        return false;
+    }
+
+    if !compare_names(expected_final_name, generated_final_name) {
+        return false;
+    }
+    true
+}
diff --git a/bindgen-tests/tests/parse_callbacks/mod.rs b/bindgen-tests/tests/parse_callbacks/mod.rs
index 7aca0fd1..5fe8d90d 100644
--- a/bindgen-tests/tests/parse_callbacks/mod.rs
+++ b/bindgen-tests/tests/parse_callbacks/mod.rs
@@ -146,6 +146,19 @@ impl ParseCallbacks for WrapAsVariadicFn {
     }
 }
 
+#[derive(Debug)]
+pub(super) struct OperatorRename;
+
+impl ParseCallbacks for OperatorRename {
+    fn generated_name_override(&self, info: ItemInfo) -> Option<String> {
+        if info.name == "operator=" {
+            Some("operatorequals".to_string())
+        } else {
+            None
+        }
+    }
+}
+
 pub fn lookup(cb: &str) -> Box<dyn ParseCallbacks> {
     match cb {
         "enum-variant-rename" => Box::new(EnumVariantRename),
@@ -154,6 +167,7 @@ pub fn lookup(cb: &str) -> Box<dyn ParseCallbacks> {
         }
         "wrap-as-variadic-fn" => Box::new(WrapAsVariadicFn),
         "type-visibility" => Box::new(TypeVisibility),
+        "operator-rename" => Box::new(OperatorRename),
         call_back => {
             if let Some(prefix) =
                 call_back.strip_prefix("remove-function-prefix-")