diff --git a/Cargo.lock b/Cargo.lock index 4786857205..d8b6ad7220 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,7 @@ dependencies = [ "owo-colors", "prettyplease", "proc-macro2", + "regex", "shlex", "similar", "syn 2.0.18", diff --git a/bindgen-tests/Cargo.toml b/bindgen-tests/Cargo.toml index 47fc0b8ca0..ccb9c9e870 100644 --- a/bindgen-tests/Cargo.toml +++ b/bindgen-tests/Cargo.toml @@ -15,6 +15,7 @@ syn = { version = "2.0" } tempfile = "3" similar = { version = "2.2.1", features = ["inline"] } owo-colors = "3.5.0" +regex = "1.7.1" [features] logging = ["bindgen/logging"] @@ -23,4 +24,4 @@ runtime = ["bindgen/runtime"] __testing_only_extra_assertions = ["bindgen/__testing_only_extra_assertions"] __testing_only_libclang_9 = ["bindgen/__testing_only_libclang_9"] -__testing_only_libclang_16 = ["bindgen/__testing_only_libclang_16"] +__testing_only_libclang_16 = ["bindgen/__testing_only_libclang_16"] \ No newline at end of file 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 new file mode 100644 index 0000000000..10e97ea480 --- /dev/null +++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h @@ -0,0 +1,16 @@ +// Unions +void function_using_anonymous_struct(struct {} arg0); + +struct NamedStruct { +}; + +typedef struct NamedStruct AliasOfNamedStruct; + + +// Unions +void function_using_anonymous_union(union {} arg0); + +union NamedUnion { +}; + +typedef union NamedUnion AliasOfNamedUnion; \ 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 new file mode 100644 index 0000000000..da37eeec97 --- /dev/null +++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs @@ -0,0 +1,246 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use regex::Regex; + +use bindgen::callbacks::{DiscoveredItem, DiscoveredItemId, ParseCallbacks}; +use bindgen::Builder; + +#[derive(Debug, Default)] +struct ItemDiscovery(Rc>); + +pub type ItemCache = HashMap; + +impl ParseCallbacks for ItemDiscovery { + fn new_item_found(&self, _id: DiscoveredItemId, _item: DiscoveredItem) { + self.0.borrow_mut().insert(_id, _item); + } +} +#[test] +pub fn test_item_discovery_callback() { + 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 expected = ItemCache::from([ + ( + DiscoveredItemId::new(10), + DiscoveredItem::Struct { + original_name: Some("NamedStruct".to_string()), + final_name: "NamedStruct".to_string(), + }, + ), + ( + DiscoveredItemId::new(11), + DiscoveredItem::Alias { + alias_name: "AliasOfNamedStruct".to_string(), + alias_for: DiscoveredItemId::new(10), + }, + ), + ( + DiscoveredItemId::new(20), + DiscoveredItem::Union { + original_name: Some("NamedUnion".to_string()), + final_name: "NamedUnion".to_string(), + }, + ), + ( + DiscoveredItemId::new(21), + DiscoveredItem::Alias { + alias_name: "AliasOfNamedUnion".to_string(), + alias_for: DiscoveredItemId::new(20), + }, + ), + ( + DiscoveredItemId::new(30), + DiscoveredItem::Struct { + original_name: None, + final_name: "_bindgen_ty_*".to_string(), + }, + ), + ( + DiscoveredItemId::new(40), + DiscoveredItem::Union { + original_name: None, + final_name: "_bindgen_ty_*".to_string(), + }, + ), + ]); + + compare_item_caches(info.borrow().clone(), expected); +} + +pub fn compare_item_caches(generated: ItemCache, expected: ItemCache) { + // 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 + // - aliases have a DiscoveredItemId that we can't directly compare for the same instability reasons + for expected_item in expected.values() { + let found = generated.iter().find(|(_generated_id, generated_item)| { + compare_item_info( + expected_item, + generated_item, + &expected, + &generated, + ) + }); + + if found.is_none() { + panic!( + "Missing Expected Item: {:#?}\n in {:#?}", + expected_item, generated + ); + } + } +} + +fn compare_item_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, + expected: &ItemCache, + generated: &ItemCache, +) -> bool { + if std::mem::discriminant(expected_item) != + std::mem::discriminant(generated_item) + { + return false; + } + + match generated_item { + DiscoveredItem::Struct { .. } => { + compare_struct_info(expected_item, generated_item) + } + DiscoveredItem::Union { .. } => { + compare_union_info(expected_item, generated_item) + } + DiscoveredItem::Alias { .. } => compare_alias_info( + expected_item, + generated_item, + expected, + generated, + ), + } +} + +pub fn compare_names(expected_name: &str, generated_name: &str) -> bool { + if let Ok(regex) = Regex::new(expected_name) { + regex.is_match(generated_name) + } else { + false + } +} + +pub fn compare_struct_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, +) -> bool { + let DiscoveredItem::Struct { + original_name: expected_original_name, + final_name: expected_final_name, + } = expected_item + else { + unreachable!() + }; + + let DiscoveredItem::Struct { + original_name: generated_original_name, + final_name: generated_final_name, + } = generated_item + else { + unreachable!() + }; + + if !compare_names(expected_final_name, generated_final_name) { + return false; + } + + match (expected_original_name, generated_original_name) { + (None, None) => true, + (Some(expected_original_name), Some(generated_original_name)) => { + compare_names(expected_original_name, generated_original_name) + } + _ => false, + } +} + +pub fn compare_union_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, +) -> bool { + let DiscoveredItem::Union { + original_name: expected_original_name, + final_name: expected_final_name, + } = expected_item + else { + unreachable!() + }; + + let DiscoveredItem::Union { + original_name: generated_original_name, + final_name: generated_final_name, + } = generated_item + else { + unreachable!() + }; + + if !compare_names(expected_final_name, generated_final_name) { + return false; + } + + match (expected_original_name, generated_original_name) { + (None, None) => true, + (Some(expected_original_name), Some(generated_original_name)) => { + compare_names(expected_original_name, generated_original_name) + } + _ => false, + } +} + +pub fn compare_alias_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, + expected: &ItemCache, + generated: &ItemCache, +) -> bool { + let DiscoveredItem::Alias { + alias_name: expected_alias_name, + alias_for: expected_alias_for, + } = expected_item + else { + unreachable!() + }; + + let DiscoveredItem::Alias { + alias_name: generated_alias_name, + alias_for: generated_alias_for, + } = generated_item + else { + unreachable!() + }; + + if !compare_names(expected_alias_name, generated_alias_name) { + return false; + } + + // Assumes correct test definition + let expected_aliased = expected.get(expected_alias_for).unwrap(); + + // We must have the aliased type in the cache + let generated_aliased = + if let Some(generated_aliased) = generated.get(generated_alias_for) { + generated_aliased + } else { + return false; + }; + + compare_item_info(expected_aliased, generated_aliased, expected, generated) +} diff --git a/bindgen-tests/tests/parse_callbacks/mod.rs b/bindgen-tests/tests/parse_callbacks/mod.rs index ee31d56e49..af99d22866 100644 --- a/bindgen-tests/tests/parse_callbacks/mod.rs +++ b/bindgen-tests/tests/parse_callbacks/mod.rs @@ -1,3 +1,5 @@ +mod item_discovery_callback; + use bindgen::callbacks::*; use bindgen::FieldVisibilityKind;