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/header_item_discovery_with_namespaces.hpp b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery_with_namespaces.hpp new file mode 100644 index 0000000000..ad8d4a524f --- /dev/null +++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery_with_namespaces.hpp @@ -0,0 +1,30 @@ +void a(); + +namespace B { + void c(); + + namespace D { + void e(); + } + + // We should not report empty namespaces + namespace F { + } + + namespace { + void g(); + } + + inline namespace H { + void i(); + namespace J { + void k(); + } + } + + struct L { + struct M { + + }; + }; +}; \ 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..6659be62b9 100644 --- a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs +++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs @@ -1,98 +1,478 @@ use std::cell::RefCell; use std::collections::HashMap; +use std::convert::identity; 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>); -pub type ItemCache = HashMap; +pub type ItemCache = HashMap; + +#[derive(Debug)] +pub struct DiscoveredInformation( + DiscoveredItem, + Option, + Option, +); 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>, + parent: Option, + ) { + self.0.borrow_mut().insert( + id, + DiscoveredInformation(item, source_location.cloned(), parent), + ); } } -#[test] -pub fn test_item_discovery_callback() { + +#[derive(Debug)] +pub struct ItemExpectations { + item: DiscoveredItem, + source_location: Option<(usize, usize, usize)>, + parent: Option, +} + +impl ItemExpectations { + fn new( + item: DiscoveredItem, + line: usize, + col: usize, + byte_offset: usize, + parent: Option, + ) -> Self { + Self { + item, + source_location: Some((line, col, byte_offset)), + parent, + } + } + + fn new_no_source_location( + item: DiscoveredItem, + parent: Option, + ) -> Self { + Self { + item, + source_location: None, + parent, + } + } +} + +type ExpectationMap = HashMap; + +fn test_item_discovery_callback Builder>( + header: &str, + expected: HashMap, + builder_adjuster: F, +) { let discovery = ItemDiscovery::default(); let info = Rc::clone(&discovery.0); - Builder::default() - .header(concat!( - env!("CARGO_MANIFEST_DIR"), - "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h" - )) - .parse_callbacks(Box::new(discovery)) - .generate() - .expect("TODO: panic message"); + let mut header_path = env!("CARGO_MANIFEST_DIR").to_string(); + header_path.push_str(header); + + let b = Builder::default() + .header(header_path) + .parse_callbacks(Box::new(discovery)); + builder_adjuster(b).generate().expect("TODO: panic message"); + + compare_item_caches(&info.borrow(), &expected, header); +} - let expected = ItemCache::from([ +#[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, + None, + ), ), ( 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, + None, + ), ), ( 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, + None, + ), ), ( 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, + None, + ), ), ( 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, + None, + ), ), ( DiscoveredItemId::new(24), - DiscoveredItem::Enum { - final_name: "NamedEnum".to_string(), - }, + ItemExpectations::new( + DiscoveredItem::Enum { + final_name: "NamedEnum".to_string(), + }, + 24, + 6, + 466, + None, + ), ), ( 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, + None, + ), ), ( 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, + None, + ), + ), + ( + DiscoveredItemId::new(41), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "named_function".to_string(), + }, + 32, + 6, + 553, + None, + ), ), ]); + test_item_discovery_callback( + "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h", expected, identity); +} - 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, + None, + ), + ), + ( + DiscoveredItemId::new(2), + ItemExpectations::new( + DiscoveredItem::Method { + final_name: "named_method".to_string(), + parent: DiscoveredItemId::new(1), + }, + 5, + 10, + 47, + None, + ), + ), + ]); + test_item_discovery_callback( + "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp", expected, identity); } -pub fn compare_item_caches(generated: &ItemCache, expected: &ItemCache) { +/// Returns the expectations corresponding to header_item_discovery_with_namespaces.hpp, +/// other than those items whose behavior changes based on the setting for +/// conservative inline namespaces, which we test each way. +fn cpp_expectation_map() -> ExpectationMap { + ExpectationMap::from([ + ( + DiscoveredItemId::new(0), + ItemExpectations::new_no_source_location( + DiscoveredItem::Mod { + final_name: "".to_string(), + anonymous: false, + inline: false, + }, + None, + ), + ), + ( + DiscoveredItemId::new(4), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "a".to_string(), + }, + 1, + 6, + 5, + Some(DiscoveredItemId::new(0)), + ), + ), + ( + DiscoveredItemId::new(5), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "B".to_string(), + anonymous: false, + inline: false, + }, + 3, + 11, + 21, + Some(DiscoveredItemId::new(0)), + ), + ), + ( + DiscoveredItemId::new(9), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "c".to_string(), + }, + 4, + 10, + 34, + Some(DiscoveredItemId::new(5)), + ), + ), + ( + DiscoveredItemId::new(10), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "D".to_string(), + anonymous: false, + inline: false, + }, + 6, + 15, + 54, + Some(DiscoveredItemId::new(5)), + ), + ), + ( + DiscoveredItemId::new(14), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "e".to_string(), + }, + 7, + 14, + 71, + Some(DiscoveredItemId::new(10)), + ), + ), + ( + DiscoveredItemId::new(16), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "".to_string(), + anonymous: true, + inline: false, + }, + 14, + 15, + 167, + Some(DiscoveredItemId::new(5)), + ), + ), + ( + DiscoveredItemId::new(30), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "k".to_string(), + }, + 21, + 18, + 276, + Some(DiscoveredItemId::new(26)), + ), + ), + ( + DiscoveredItemId::new(31), + ItemExpectations::new( + DiscoveredItem::Struct { + final_name: "L".to_string(), + original_name: Some("L".to_string()), + }, + 25, + 12, + 309, + Some(DiscoveredItemId::new(5)), + ), + ), + ( + DiscoveredItemId::new(32), + ItemExpectations::new( + DiscoveredItem::Struct { + final_name: "L_M".to_string(), + original_name: Some("M".to_string()), + }, + 26, + 16, + 328, + Some(DiscoveredItemId::new(31)), + ), + ), + ]) +} + +#[test] +fn test_item_discovery_callback_cpp_namespaces_no_inline_namespaces() { + let mut expected = cpp_expectation_map(); + expected.insert( + DiscoveredItemId::new(25), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "i".to_string(), + }, + 19, + 14, + 232, + Some(DiscoveredItemId::new(5)), + ), + ); + expected.insert( + DiscoveredItemId::new(26), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "J".to_string(), + anonymous: false, + inline: false, + }, + 20, + 19, + 255, + Some(DiscoveredItemId::new(5)), + ), + ); + + // C++11 for inline namespace + test_item_discovery_callback( + "/tests/parse_callbacks/item_discovery_callback/header_item_discovery_with_namespaces.hpp", expected, |b| b.enable_cxx_namespaces().clang_arg("--std=c++11")); +} + +#[test] +fn test_item_discovery_callback_cpp_namespaces_with_inline_namespaces() { + let mut expected = cpp_expectation_map(); + expected.insert( + DiscoveredItemId::new(21), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "H".to_string(), + anonymous: false, + inline: true, + }, + 18, + 22, + 215, + Some(DiscoveredItemId::new(5)), + ), + ); + expected.insert( + DiscoveredItemId::new(25), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "i".to_string(), + }, + 19, + 14, + 232, + Some(DiscoveredItemId::new(21)), + ), + ); + expected.insert( + DiscoveredItemId::new(26), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "J".to_string(), + anonymous: false, + inline: false, + }, + 20, + 19, + 255, + Some(DiscoveredItemId::new(21)), + ), + ); + + // C++11 for inline namespace + test_item_discovery_callback( + "/tests/parse_callbacks/item_discovery_callback/header_item_discovery_with_namespaces.hpp", expected, |b| b.enable_cxx_namespaces().conservative_inline_namespaces().clang_arg("--std=c++11")); +} + +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 +484,7 @@ pub fn compare_item_caches(generated: &ItemCache, expected: &ItemCache) { generated_item, expected, generated, + expected_filename, ) }); @@ -115,34 +496,90 @@ 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) + } + DiscoveredItem::Mod { .. } => { + compare_mod_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(), + "Source location wasn't as expected for generated={generated_item:?}, expected={expected_item:?}" + ); + 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" + ); + } + + // Compare C++ name info + assert!( + generated_item.2.is_some() == + expected_item.parent.is_some(), + "Parent information didn't match: generated item {generated_item:?}" + ); + + if let Some(generated_parent) = generated_item.2.as_ref() { + let expected_parent = expected_item.parent.as_ref().unwrap(); + assert_eq!( + generated_parent, expected_parent, + "Parent didn't match for {expected_item:?}" + ); } } + is_a_match } pub fn compare_names(expected_name: &str, generated_name: &str) -> bool { @@ -246,8 +683,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 +715,104 @@ 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 +} + +pub fn compare_mod_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, +) -> bool { + let DiscoveredItem::Mod { + final_name: expected_final_name, + anonymous: expected_anonymous, + inline: expected_inline, + } = expected_item + else { + unreachable!() + }; + + let DiscoveredItem::Mod { + final_name: generated_final_name, + anonymous: generated_anonymous, + inline: generated_inline, + } = generated_item + else { + unreachable!() + }; + + if expected_anonymous != generated_anonymous || + *expected_inline != *generated_inline + { + return false; + } + + // We generate arbitrary names for anonymous namespaces - do not compare + if !expected_anonymous { + // Do not use regexes to compare mod names since the root mod + // has an empty name and would match everything + if expected_final_name != generated_final_name { + return false; + } + } + true } diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs index c2be66828a..5cbc1e2b17 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -163,8 +163,24 @@ pub trait ParseCallbacks: fmt::Debug { None } - /// 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) {} + /// This will get called everytime an item is found with some information about it. + /// `_parent` is the location in which the item has been found, if any. + /// This is guaranteed to be a [`DiscoveredItem`] as reported + /// by [`ParseCallbacks::new_item_found`], most likely a + /// [`DiscoveredItem::Mod`] but perhaps something else such as a + /// [`DiscoveredItem::Struct`]. + /// If C++ namespace support has not been enabled in bindgen's options, + /// most items will have no declared `_parent`. If C++ namespace support + /// has been enabled, all items should have a parent other than the root + /// namespace. + fn new_item_found( + &self, + _id: DiscoveredItemId, + _item: DiscoveredItem, + _source_location: Option<&SourceLocation>, + _parent: Option, + ) { + } // TODO add callback for ResolvedTypeRef } @@ -224,7 +240,36 @@ pub enum DiscoveredItem { /// The final name of the generated binding final_name: String, }, - // functions, modules, etc. + + /// A module, representing a C++ namespace. + /// The root module can be identified by the fact that it has a `None` + /// parent declared within [`ParseCallbacks::new_item_found`]. + /// Inline namespaces won't be reported at all unless the + /// "enable conservative inline namespaces" option is enabled. + Mod { + /// The final name used. + final_name: String, + /// Whether this was originally an anonymous namespace. + /// bindgen will have assigned a name within `final_name`. + anonymous: bool, + /// Whether this is an inline namespace. + inline: bool, + }, + + /// 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, + }, } /// Relevant information about a type to which new derive attributes will be added using @@ -290,3 +335,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, +} diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index f5518e432d..4fdc4aa094 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -627,6 +627,19 @@ impl CodeGenerator for Module { } let name = item.canonical_name(ctx); + + utils::call_discovered_item_callback(ctx, item, || { + DiscoveredItem::Mod { + final_name: if item.id() == item.parent_id() { + "".to_string() // root module + } else { + name.clone() + }, + anonymous: self.name().is_none(), + inline: self.is_inline(), + } + }); + let ident = ctx.rust_ident(name); result.push(if item.id() == ctx.root_module() { quote! { @@ -995,16 +1008,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 +2491,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 +2705,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 +2715,7 @@ impl CodeGenerator for CompInfo { &mut method_names, result, self, + discovered_id, ); } } @@ -2729,6 +2734,7 @@ impl CodeGenerator for CompInfo { &mut method_names, result, self, + discovered_id, ); } } @@ -2742,6 +2748,7 @@ impl CodeGenerator for CompInfo { &mut method_names, result, self, + discovered_id, ); } } @@ -2999,6 +3006,7 @@ impl Method { method_names: &mut HashSet, result: &mut CodegenResult<'_>, _parent: &CompInfo, + parent_id: DiscoveredItemId, ) { assert!({ let cc = &ctx.options().codegen_config; @@ -3065,6 +3073,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 +3785,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 +4662,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,10 +5206,12 @@ 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}; use crate::ir::item::{Item, ItemCanonicalPath}; + use crate::ir::item_kind::ItemKind; use crate::ir::ty::TypeKind; use crate::{args_are_cpp, file_is_cpp}; use std::borrow::Cow; @@ -5918,4 +5937,80 @@ 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(), + find_reportable_parent(ctx, item), + ); + }); + } + + /// Identify a suitable parent, the details of which will have + /// been passed to `ParseCallbacks`. We don't inform + /// [`crate::callbacks::ParseCallbacks::new_item_found`] + /// about everything - notably, not usually inline namespaces - and always + /// want to ensure that the `parent_id` we report within `new_item_found` + /// always corresponds to some other item which we'll have + /// told the client about. This function hops back through the ancestor + /// chain until it finds a reportable ID. + pub(super) fn find_reportable_parent( + ctx: &BindgenContext, + item: &Item, + ) -> Option { + // We choose never to report parents if C++ namespaces are not + // enabled. Sometimes a struct might be within another struct, but + // for now we simply don't report parentage at all. + if !ctx.options().enable_cxx_namespaces { + return None; + } + let mut parent_item = ctx.resolve_item(item.parent_id()); + while !is_reportable_parent(ctx, &parent_item) { + let parent_id = parent_item.parent_id(); + if parent_id == parent_item.id() { + return None; + } + parent_item = ctx.resolve_item(parent_id); + } + if parent_item.id() == item.id() { + // This is itself the root module. + None + } else { + Some(DiscoveredItemId::new(parent_item.id().as_usize())) + } + } + + /// Returns whether a given [`Item`] will have been reported, or will + /// be reported, in [`crate::callbacks::ParseCallbacks::new_item_found`]. + fn is_reportable_parent(ctx: &BindgenContext, item: &Item) -> bool { + match item.kind() { + ItemKind::Module(ref module) => { + !module.is_inline() || + ctx.options().conservative_inline_namespaces + } + ItemKind::Type(t) => match t.kind() { + TypeKind::Comp(..) | TypeKind::Enum(..) => true, + _ => false, + }, + _ => false, + } + } }