Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extend parse callbacks to expose discovered composite types and aliases #2658

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion bindgen-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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"]
Original file line number Diff line number Diff line change
@@ -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;
246 changes: 246 additions & 0 deletions bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs
Original file line number Diff line number Diff line change
@@ -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<RefCell<ItemCache>>);

pub type ItemCache = HashMap<DiscoveredItemId, DiscoveredItem>;

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)
}
2 changes: 2 additions & 0 deletions bindgen-tests/tests/parse_callbacks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod item_discovery_callback;

use bindgen::callbacks::*;
use bindgen::FieldVisibilityKind;

Expand Down
56 changes: 56 additions & 0 deletions bindgen/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,62 @@ pub trait ParseCallbacks: fmt::Debug {
fn wrap_as_variadic_fn(&self, _name: &str) -> Option<String> {
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) {}

// TODO add callback for ResolvedTypeRef
}

/// An identifier for a discovered item. Used to identify an aliased type (see [DiscoveredItem::Alias])
#[derive(Ord, PartialOrd, PartialEq, Eq, Hash, Debug, Clone, Copy)]
pub struct DiscoveredItemId(usize);

impl DiscoveredItemId {
/// Constructor
pub fn new(value: usize) -> Self {
Self(value)
}
}

/// Struct passed to [ParseCallbacks::new_item_found] containing information about discovered
/// items (struct, union, and alias)
#[derive(Debug, Hash, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum DiscoveredItem {
/// Represents a struct with its original name in C and its generated binding name
Struct {
/// The original name (learnt from C) of the structure
/// Can be None if the union is anonymous.
original_name: Option<String>,

/// The name of the generated binding
final_name: String,
},

/// Represents a union with its original name in C and its generated binding name
Union {
/// The original name (learnt from C) of the structure.
/// Can be None if the union is anonymous.
original_name: Option<String>,

/// The name of the generated binding
final_name: String,
},

/// Represents an alias like a typedef
/// ```c
/// typedef struct MyStruct {
/// ...
/// } StructAlias;
/// ```
/// Here, the name of the alias is `StructAlias` and it's an alias for `MyStruct`
Alias {
/// The name of the alias in C (`StructAlias`)
alias_name: String,

/// The identifier of the discovered type
alias_for: DiscoveredItemId,
}, // functions, modules, etc.
}

/// Relevant information about a type to which new derive attributes will be added using
Expand Down
Loading