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 b2bb04f15f..eb44e5fc58 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 0000000000..1de8d99e4d
--- /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 93a2b029d7..6f64bd2f10 100644
--- a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs
+++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs
@@ -4,95 +4,224 @@ use std::rc::Rc;
 
 use regex::Regex;
 
-use bindgen::callbacks::{DiscoveredItem, DiscoveredItemId, ParseCallbacks};
+use bindgen::callbacks::{
+    DiscoveredItem, DiscoveredItemId, ParseCallbacks, SourceLocation,
+};
 use bindgen::Builder;
 
 #[derive(Debug, Default)]
 struct ItemDiscovery(Rc<RefCell<ItemCache>>);
 
-pub type ItemCache = HashMap<DiscoveredItemId, DiscoveredItem>;
+pub type ItemCache = HashMap<DiscoveredItemId, DiscoveredInformation>;
+
+#[derive(Debug)]
+pub struct DiscoveredInformation(DiscoveredItem, Option<SourceLocation>);
 
 impl ParseCallbacks for ItemDiscovery {
-    fn new_item_found(&self, _id: DiscoveredItemId, _item: DiscoveredItem) {
-        self.0.borrow_mut().insert(_id, _item);
+    fn new_item_found(
+        &self,
+        id: DiscoveredItemId,
+        item: DiscoveredItem,
+        source_location: Option<&SourceLocation>,
+    ) {
+        self.0
+            .borrow_mut()
+            .insert(id, DiscoveredInformation(item, source_location.cloned()));
     }
 }
-#[test]
-pub fn test_item_discovery_callback() {
+
+#[derive(Debug)]
+pub struct ItemExpectations {
+    item: DiscoveredItem,
+    source_location: Option<(usize, usize, usize)>,
+}
+
+impl ItemExpectations {
+    fn new(
+        item: DiscoveredItem,
+        line: usize,
+        col: usize,
+        byte_offset: usize,
+    ) -> Self {
+        Self {
+            item,
+            source_location: Some((line, col, byte_offset)),
+        }
+    }
+}
+
+type ExpectationMap = HashMap<DiscoveredItemId, ItemExpectations>;
+
+fn test_item_discovery_callback(
+    header: &str,
+    expected: HashMap<DiscoveredItemId, ItemExpectations>,
+) {
     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");
 
-    let expected = ItemCache::from([
+    compare_item_caches(&info.borrow(), &expected, header);
+}
+
+#[test]
+fn test_item_discovery_callback_c() {
+    let expected = ExpectationMap::from([
         (
             DiscoveredItemId::new(10),
-            DiscoveredItem::Struct {
-                original_name: Some("NamedStruct".to_string()),
-                final_name: "NamedStruct".to_string(),
-            },
+            ItemExpectations::new(
+                DiscoveredItem::Struct {
+                    original_name: Some("NamedStruct".to_string()),
+                    final_name: "NamedStruct".to_string(),
+                },
+                4,
+                8,
+                73,
+            ),
         ),
         (
             DiscoveredItemId::new(11),
-            DiscoveredItem::Alias {
-                alias_name: "AliasOfNamedStruct".to_string(),
-                alias_for: DiscoveredItemId::new(10),
-            },
+            ItemExpectations::new(
+                DiscoveredItem::Alias {
+                    alias_name: "AliasOfNamedStruct".to_string(),
+                    alias_for: DiscoveredItemId::new(10),
+                },
+                7,
+                28,
+                118,
+            ),
         ),
         (
             DiscoveredItemId::new(20),
-            DiscoveredItem::Union {
-                original_name: Some("NamedUnion".to_string()),
-                final_name: "NamedUnion".to_string(),
-            },
+            ItemExpectations::new(
+                DiscoveredItem::Union {
+                    original_name: Some("NamedUnion".to_string()),
+                    final_name: "NamedUnion".to_string(),
+                },
+                13,
+                7,
+                209,
+            ),
         ),
         (
             DiscoveredItemId::new(21),
-            DiscoveredItem::Alias {
-                alias_name: "AliasOfNamedUnion".to_string(),
-                alias_for: DiscoveredItemId::new(20),
-            },
+            ItemExpectations::new(
+                DiscoveredItem::Alias {
+                    alias_name: "AliasOfNamedUnion".to_string(),
+                    alias_for: DiscoveredItemId::new(20),
+                },
+                16,
+                26,
+                251,
+            ),
         ),
         (
             DiscoveredItemId::new(27),
-            DiscoveredItem::Alias {
-                alias_name: "AliasOfNamedEnum".to_string(),
-                alias_for: DiscoveredItemId::new(24),
-            },
+            ItemExpectations::new(
+                DiscoveredItem::Alias {
+                    alias_name: "AliasOfNamedEnum".to_string(),
+                    alias_for: DiscoveredItemId::new(24),
+                },
+                28,
+                24,
+                515,
+            ),
         ),
         (
             DiscoveredItemId::new(24),
-            DiscoveredItem::Enum {
-                final_name: "NamedEnum".to_string(),
-            },
+            ItemExpectations::new(
+                DiscoveredItem::Enum {
+                    final_name: "NamedEnum".to_string(),
+                },
+                24,
+                6,
+                466,
+            ),
         ),
         (
             DiscoveredItemId::new(30),
-            DiscoveredItem::Struct {
-                original_name: None,
-                final_name: "_bindgen_ty_*".to_string(),
-            },
+            ItemExpectations::new(
+                DiscoveredItem::Struct {
+                    original_name: None,
+                    final_name: "_bindgen_ty_*".to_string(),
+                },
+                2,
+                38,
+                48,
+            ),
         ),
         (
             DiscoveredItemId::new(40),
-            DiscoveredItem::Union {
-                original_name: None,
-                final_name: "_bindgen_ty_*".to_string(),
-            },
+            ItemExpectations::new(
+                DiscoveredItem::Union {
+                    original_name: None,
+                    final_name: "_bindgen_ty_*".to_string(),
+                },
+                11,
+                37,
+                186,
+            ),
+        ),
+        (
+            DiscoveredItemId::new(41),
+            ItemExpectations::new(
+                DiscoveredItem::Function {
+                    final_name: "named_function".to_string(),
+                },
+                32,
+                6,
+                553,
+            ),
         ),
     ]);
+    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 = ExpectationMap::from([
+        (
+            DiscoveredItemId::new(1),
+            ItemExpectations::new(
+                DiscoveredItem::Struct {
+                    original_name: Some("SomeClass".to_string()),
+                    final_name: "SomeClass".to_string(),
+                },
+                3,
+                7,
+                18,
+            ),
+        ),
+        (
+            DiscoveredItemId::new(2),
+            ItemExpectations::new(
+                DiscoveredItem::Method {
+                    final_name: "named_method".to_string(),
+                    parent: DiscoveredItemId::new(1),
+                },
+                5,
+                10,
+                47,
+            ),
+        ),
+    ]);
+    test_item_discovery_callback(
+        "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp", expected);
 }
 
-pub fn compare_item_caches(generated: &ItemCache, expected: &ItemCache) {
+fn compare_item_caches(
+    generated: &ItemCache,
+    expected: &ExpectationMap,
+    expected_filename: &str,
+) {
     // We can't use a simple Eq::eq comparison because of two reasons:
     // - anonymous structs/unions will have a final name generated by bindgen which may change
     //   if the header file or the bindgen logic is altered
@@ -104,6 +233,7 @@ pub fn compare_item_caches(generated: &ItemCache, expected: &ItemCache) {
                 generated_item,
                 expected,
                 generated,
+                expected_filename,
             )
         });
 
@@ -115,34 +245,72 @@ pub fn compare_item_caches(generated: &ItemCache, expected: &ItemCache) {
 }
 
 fn compare_item_info(
-    expected_item: &DiscoveredItem,
-    generated_item: &DiscoveredItem,
-    expected: &ItemCache,
+    expected_item: &ItemExpectations,
+    generated_item: &DiscoveredInformation,
+    expected: &ExpectationMap,
     generated: &ItemCache,
+    expected_filename: &str,
 ) -> bool {
-    if std::mem::discriminant(expected_item) !=
-        std::mem::discriminant(generated_item)
+    if std::mem::discriminant(&expected_item.item) !=
+        std::mem::discriminant(&generated_item.0)
     {
         return false;
     }
 
-    match generated_item {
+    let is_a_match = match generated_item.0 {
         DiscoveredItem::Struct { .. } => {
-            compare_struct_info(expected_item, generated_item)
+            compare_struct_info(&expected_item.item, &generated_item.0)
         }
         DiscoveredItem::Union { .. } => {
-            compare_union_info(expected_item, generated_item)
+            compare_union_info(&expected_item.item, &generated_item.0)
         }
         DiscoveredItem::Alias { .. } => compare_alias_info(
-            expected_item,
-            generated_item,
+            &expected_item.item,
+            &generated_item.0,
             expected,
             generated,
+            expected_filename,
         ),
         DiscoveredItem::Enum { .. } => {
-            compare_enum_info(expected_item, generated_item)
+            compare_enum_info(&expected_item.item, &generated_item.0)
+        }
+        DiscoveredItem::Function { .. } => {
+            compare_function_info(&expected_item.item, &generated_item.0)
+        }
+        DiscoveredItem::Method { .. } => {
+            compare_method_info(&expected_item.item, &generated_item.0)
+        }
+    };
+
+    if is_a_match {
+        // Compare source location
+        assert!(
+            generated_item.1.is_some() ==
+                expected_item.source_location.is_some(),
+            "No source location provided when one was expected"
+        );
+        if let Some(generated_location) = generated_item.1.as_ref() {
+            let expected_location = expected_item.source_location.unwrap();
+            assert!(
+                generated_location
+                    .file_name
+                    .as_ref()
+                    .expect("No filename provided")
+                    .ends_with(expected_filename),
+                "Filename differed"
+            );
+            assert_eq!(
+                (
+                    generated_location.line,
+                    generated_location.col,
+                    generated_location.byte_offset
+                ),
+                expected_location,
+                "Line/col/offsets differ"
+            );
         }
     }
+    is_a_match
 }
 
 pub fn compare_names(expected_name: &str, generated_name: &str) -> bool {
@@ -246,8 +414,9 @@ pub fn compare_enum_info(
 pub fn compare_alias_info(
     expected_item: &DiscoveredItem,
     generated_item: &DiscoveredItem,
-    expected: &ItemCache,
+    expected: &ExpectationMap,
     generated: &ItemCache,
+    expected_filename: &str,
 ) -> bool {
     let DiscoveredItem::Alias {
         alias_name: expected_alias_name,
@@ -277,5 +446,65 @@ pub fn compare_alias_info(
         return false;
     };
 
-    compare_item_info(expected_aliased, generated_aliased, expected, generated)
+    compare_item_info(
+        expected_aliased,
+        generated_aliased,
+        expected,
+        generated,
+        expected_filename,
+    )
+}
+
+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/callbacks.rs b/bindgen/callbacks.rs
index c2be66828a..329cb888c1 100644
--- a/bindgen/callbacks.rs
+++ b/bindgen/callbacks.rs
@@ -164,7 +164,13 @@ pub trait ParseCallbacks: fmt::Debug {
     }
 
     /// This will get called everytime an item (currently struct, union, and alias) is found with some information about it
-    fn new_item_found(&self, _id: DiscoveredItemId, _item: DiscoveredItem) {}
+    fn new_item_found(
+        &self,
+        _id: DiscoveredItemId,
+        _item: DiscoveredItem,
+        _source_location: Option<&SourceLocation>,
+    ) {
+    }
 
     // TODO add callback for ResolvedTypeRef
 }
@@ -224,7 +230,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
@@ -290,3 +310,17 @@ pub struct FieldInfo<'a> {
     /// The name of the type of the field.
     pub field_type_name: Option<&'a str>,
 }
+
+/// Location in the source code. Roughly equivalent to the same type
+/// within `clang_sys`.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct SourceLocation {
+    /// Line number.
+    pub line: usize,
+    /// Column number within line.
+    pub col: usize,
+    /// Byte offset within file.
+    pub byte_offset: usize,
+    /// Filename, if known.
+    pub file_name: Option<String>,
+}
diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs
index f5518e432d..322922b463 100644
--- a/bindgen/codegen/mod.rs
+++ b/bindgen/codegen/mod.rs
@@ -995,16 +995,13 @@ impl CodeGenerator for Type {
 
                 let rust_name = ctx.rust_ident(&name);
 
-                ctx.options().for_each_callback(|cb| {
-                    cb.new_item_found(
-                        DiscoveredItemId::new(item.id().as_usize()),
-                        DiscoveredItem::Alias {
-                            alias_name: rust_name.to_string(),
-                            alias_for: DiscoveredItemId::new(
-                                inner_item.id().as_usize(),
-                            ),
-                        },
-                    );
+                utils::call_discovered_item_callback(ctx, item, || {
+                    DiscoveredItem::Alias {
+                        alias_name: rust_name.to_string(),
+                        alias_for: DiscoveredItemId::new(
+                            inner_item.id().as_usize(),
+                        ),
+                    }
                 });
 
                 let mut tokens = if let Some(comment) = item.comment(ctx) {
@@ -2481,30 +2478,23 @@ impl CodeGenerator for CompInfo {
 
         let is_rust_union = is_union && struct_layout.is_rust_union();
 
-        ctx.options().for_each_callback(|cb| {
-            let discovered_item = match self.kind() {
-                CompKind::Struct => DiscoveredItem::Struct {
-                    original_name: item
-                        .kind()
-                        .expect_type()
-                        .name()
-                        .map(String::from),
-                    final_name: canonical_ident.to_string(),
-                },
-                CompKind::Union => DiscoveredItem::Union {
-                    original_name: item
-                        .kind()
-                        .expect_type()
-                        .name()
-                        .map(String::from),
-                    final_name: canonical_ident.to_string(),
-                },
-            };
-
-            cb.new_item_found(
-                DiscoveredItemId::new(item.id().as_usize()),
-                discovered_item,
-            );
+        utils::call_discovered_item_callback(ctx, item, || match self.kind() {
+            CompKind::Struct => DiscoveredItem::Struct {
+                original_name: item
+                    .kind()
+                    .expect_type()
+                    .name()
+                    .map(String::from),
+                final_name: canonical_ident.to_string(),
+            },
+            CompKind::Union => DiscoveredItem::Union {
+                original_name: item
+                    .kind()
+                    .expect_type()
+                    .name()
+                    .map(String::from),
+                final_name: canonical_ident.to_string(),
+            },
         });
 
         // The custom derives callback may return a list of derive attributes;
@@ -2702,6 +2692,7 @@ impl CodeGenerator for CompInfo {
             }
 
             let mut method_names = Default::default();
+            let discovered_id = DiscoveredItemId::new(item.id().as_usize());
             if ctx.options().codegen_config.methods() {
                 for method in self.methods() {
                     assert_ne!(method.kind(), MethodKind::Constructor);
@@ -2711,6 +2702,7 @@ impl CodeGenerator for CompInfo {
                         &mut method_names,
                         result,
                         self,
+                        discovered_id,
                     );
                 }
             }
@@ -2729,6 +2721,7 @@ impl CodeGenerator for CompInfo {
                         &mut method_names,
                         result,
                         self,
+                        discovered_id,
                     );
                 }
             }
@@ -2742,6 +2735,7 @@ impl CodeGenerator for CompInfo {
                         &mut method_names,
                         result,
                         self,
+                        discovered_id,
                     );
                 }
             }
@@ -2999,6 +2993,7 @@ impl Method {
         method_names: &mut HashSet<String>,
         result: &mut CodegenResult<'_>,
         _parent: &CompInfo,
+        parent_id: DiscoveredItemId,
     ) {
         assert!({
             let cc = &ctx.options().codegen_config;
@@ -3065,6 +3060,13 @@ impl Method {
 
         method_names.insert(name.clone());
 
+        utils::call_discovered_item_callback(ctx, function_item, || {
+            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();
@@ -3770,13 +3772,10 @@ impl CodeGenerator for Enum {
         let repr = repr.to_rust_ty_or_opaque(ctx, item);
         let has_typedef = ctx.is_enum_typedef_combo(item.id());
 
-        ctx.options().for_each_callback(|cb| {
-            cb.new_item_found(
-                DiscoveredItemId::new(item.id().as_usize()),
-                DiscoveredItem::Enum {
-                    final_name: name.to_string(),
-                },
-            );
+        utils::call_discovered_item_callback(ctx, item, || {
+            DiscoveredItem::Enum {
+                final_name: name.to_string(),
+            }
         });
 
         let mut builder =
@@ -4650,6 +4649,11 @@ impl CodeGenerator for Function {
         if times_seen > 0 {
             write!(&mut canonical_name, "{times_seen}").unwrap();
         }
+        utils::call_discovered_item_callback(ctx, item, || {
+            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);
@@ -5189,6 +5193,7 @@ pub(crate) mod utils {
     use super::helpers::BITFIELD_UNIT;
     use super::serialize::CSerialize;
     use super::{error, CodegenError, CodegenResult, ToRustTyOrOpaque};
+    use crate::callbacks::DiscoveredItemId;
     use crate::ir::context::BindgenContext;
     use crate::ir::context::TypeId;
     use crate::ir::function::{Abi, ClangAbi, FunctionSig};
@@ -5918,4 +5923,28 @@ pub(crate) mod utils {
 
         true
     }
+
+    pub(super) fn call_discovered_item_callback(
+        ctx: &BindgenContext,
+        item: &Item,
+        discovered_item_creator: impl Fn() -> crate::callbacks::DiscoveredItem,
+    ) {
+        let source_location = item.location().map(|clang_location| {
+            let (file, line, col, byte_offset) = clang_location.location();
+            let file_name = file.name();
+            crate::callbacks::SourceLocation {
+                line,
+                col,
+                byte_offset,
+                file_name,
+            }
+        });
+        ctx.options().for_each_callback(|cb| {
+            cb.new_item_found(
+                DiscoveredItemId::new(item.id().as_usize()),
+                discovered_item_creator(),
+                source_location.as_ref(),
+            );
+        });
+    }
 }