Skip to content

Commit aa5e696

Browse files
committed
codegen: Deduplicate derive traits added by add_derives() parse callback
Fixes #3286
1 parent 50ec3ee commit aa5e696

File tree

4 files changed

+146
-4
lines changed

4 files changed

+146
-4
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
struct SimpleStruct {
2+
int a;
3+
};
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use bindgen::callbacks::{DeriveInfo, ParseCallbacks};
4+
use bindgen::{Bindings, Builder};
5+
use std::path::PathBuf;
6+
7+
#[derive(Debug)]
8+
struct AddDerivesCallback(Vec<String>);
9+
10+
impl AddDerivesCallback {
11+
fn new(derives: &[&str]) -> Self {
12+
Self(derives.iter().map(|s| (*s).to_string()).collect())
13+
}
14+
}
15+
16+
impl ParseCallbacks for AddDerivesCallback {
17+
fn add_derives(&self, _info: &DeriveInfo<'_>) -> Vec<String> {
18+
self.0.clone()
19+
}
20+
}
21+
22+
struct WriteAdapter<'a>(&'a mut Vec<u8>);
23+
24+
impl std::io::Write for WriteAdapter<'_> {
25+
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
26+
self.0.extend_from_slice(buf);
27+
Ok(buf.len())
28+
}
29+
fn flush(&mut self) -> std::io::Result<()> {
30+
Ok(())
31+
}
32+
}
33+
34+
fn write_bindings_to_string(bindings: &Bindings) -> String {
35+
let mut output = Vec::<u8>::new();
36+
bindings
37+
.write(Box::new(WriteAdapter(&mut output)))
38+
.unwrap_or_else(|e| {
39+
panic!("Failed to write generated bindings: {e}")
40+
});
41+
String::from_utf8(output).unwrap_or_else(|e| {
42+
panic!("Failed to convert generated bindings to string: {e}")
43+
})
44+
}
45+
46+
fn make_builder(header_path: &PathBuf, add_derives: &[&str]) -> Builder {
47+
Builder::default()
48+
.header(header_path.display().to_string())
49+
.derive_debug(true)
50+
.derive_copy(false)
51+
.derive_default(false)
52+
.derive_partialeq(false)
53+
.derive_eq(false)
54+
.derive_partialord(false)
55+
.derive_ord(false)
56+
.derive_hash(false)
57+
.parse_callbacks(Box::new(AddDerivesCallback::new(add_derives)))
58+
}
59+
60+
/// Tests that adding a derive trait that's already derived automatically
61+
/// does not result in a duplicate derive trait (which would not compile).
62+
#[test]
63+
fn test_add_derives_callback_dedupe() {
64+
let crate_dir =
65+
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
66+
let header_path = crate_dir.join(
67+
"tests/parse_callbacks/add_derives_callback/header_add_derives.h",
68+
);
69+
70+
let builder = make_builder(&header_path, &["Debug"]);
71+
let bindings = builder
72+
.generate()
73+
.unwrap_or_else(|e| panic!("Failed to generate bindings: {e}"));
74+
let output = write_bindings_to_string(&bindings);
75+
let output_without_spaces = output.replace(' ', "");
76+
assert!(
77+
output_without_spaces.contains("#[derive(Debug)]") &&
78+
!output_without_spaces.contains("#[derive(Debug,Debug)]"),
79+
"Unexpected bindgen output:\n{}",
80+
output.as_str()
81+
);
82+
}
83+
84+
/// Tests that adding a derive trait that's not already derived automatically
85+
/// adds it to the end of the derive list.
86+
#[test]
87+
fn test_add_derives_callback() {
88+
let crate_dir =
89+
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
90+
let header_path = crate_dir.join(
91+
"tests/parse_callbacks/add_derives_callback/header_add_derives.h",
92+
);
93+
94+
let builder = make_builder(&header_path, &["Default"]);
95+
let bindings = builder
96+
.generate()
97+
.unwrap_or_else(|e| panic!("Failed to generate bindings: {e}"));
98+
let output = write_bindings_to_string(&bindings);
99+
let output_without_spaces = output.replace(' ', "");
100+
assert!(
101+
output_without_spaces.contains("#[derive(Debug,Default)]"),
102+
"Unexpected bindgen output:\n{}",
103+
output.as_str()
104+
);
105+
}
106+
}

bindgen-tests/tests/parse_callbacks/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod add_derives_callback;
12
mod item_discovery_callback;
23

34
use bindgen::callbacks::*;

bindgen/codegen/mod.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,24 @@ fn derives_of_item(
199199
derivable_traits
200200
}
201201

202+
/// Appends the contents of the `custom_derives` iterator to the `derives` vector,
203+
/// ignoring duplicates and preserving order.
204+
fn append_custom_derives<'a, I>(derives: &mut Vec<&'a str>, custom_derives: I)
205+
where
206+
I: Iterator<Item = &'a str>,
207+
{
208+
// Use a HashSet to track already seen elements.
209+
let mut seen: HashSet<&'a str> = Default::default();
210+
seen.extend(derives.iter().copied());
211+
212+
// Add the custom derives to the derives vector, ignoring duplicates.
213+
for custom_derive in custom_derives {
214+
if seen.insert(custom_derive) {
215+
derives.push(custom_derive);
216+
}
217+
}
218+
}
219+
202220
impl From<DerivableTraits> for Vec<&'static str> {
203221
fn from(derivable_traits: DerivableTraits) -> Vec<&'static str> {
204222
[
@@ -1043,8 +1061,12 @@ impl CodeGenerator for Type {
10431061
})
10441062
});
10451063
// In most cases this will be a no-op, since custom_derives will be empty.
1046-
derives
1047-
.extend(custom_derives.iter().map(|s| s.as_str()));
1064+
if !custom_derives.is_empty() {
1065+
append_custom_derives(
1066+
&mut derives,
1067+
custom_derives.iter().map(|s| s.as_str()),
1068+
);
1069+
}
10481070
attributes.push(attributes::derives(&derives));
10491071

10501072
let custom_attributes =
@@ -2475,7 +2497,12 @@ impl CodeGenerator for CompInfo {
24752497
})
24762498
});
24772499
// In most cases this will be a no-op, since custom_derives will be empty.
2478-
derives.extend(custom_derives.iter().map(|s| s.as_str()));
2500+
if !custom_derives.is_empty() {
2501+
append_custom_derives(
2502+
&mut derives,
2503+
custom_derives.iter().map(|s| s.as_str()),
2504+
);
2505+
}
24792506

24802507
if !derives.is_empty() {
24812508
attributes.push(attributes::derives(&derives));
@@ -3678,7 +3705,12 @@ impl CodeGenerator for Enum {
36783705
})
36793706
});
36803707
// In most cases this will be a no-op, since custom_derives will be empty.
3681-
derives.extend(custom_derives.iter().map(|s| s.as_str()));
3708+
if !custom_derives.is_empty() {
3709+
append_custom_derives(
3710+
&mut derives,
3711+
custom_derives.iter().map(|s| s.as_str()),
3712+
);
3713+
}
36823714

36833715
attrs.extend(
36843716
item.annotations()

0 commit comments

Comments
 (0)