Skip to content

Commit 5a2c0f4

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

File tree

4 files changed

+152
-4
lines changed

4 files changed

+152
-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: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
/// Tests that adding a derive trait that's already derived automatically
47+
/// does not result in a duplicate derive trait (which would not compile).
48+
#[test]
49+
fn test_add_derives_callback_dedupe() {
50+
let crate_dir =
51+
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
52+
let header_path = crate_dir.join(
53+
"tests/parse_callbacks/add_derives_callback/header_add_derives.h",
54+
);
55+
56+
let builder = Builder::default()
57+
.header(header_path.display().to_string())
58+
.derive_debug(true)
59+
.derive_copy(false)
60+
.derive_default(false)
61+
.derive_partialeq(false)
62+
.derive_eq(false)
63+
.derive_partialord(false)
64+
.derive_ord(false)
65+
.derive_hash(false)
66+
.parse_callbacks(Box::new(AddDerivesCallback::new(&["Debug"])));
67+
let bindings = builder
68+
.generate()
69+
.unwrap_or_else(|e| panic!("Failed to generate bindings: {e}"));
70+
let output = write_bindings_to_string(&bindings);
71+
let output_without_spaces = output.replace(' ', "");
72+
assert!(
73+
output_without_spaces.contains("#[derive(Debug)]") &&
74+
!output_without_spaces.contains("#[derive(Debug,Debug)]"),
75+
"Unexpected bindgen output:\n{}",
76+
output.as_str()
77+
);
78+
}
79+
80+
/// Tests that adding a derive trait that's not already derived automatically
81+
/// adds it to the end of the derive list.
82+
#[test]
83+
fn test_add_derives_callback() {
84+
let crate_dir =
85+
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
86+
let header_path = crate_dir.join(
87+
"tests/parse_callbacks/add_derives_callback/header_add_derives.h",
88+
);
89+
90+
let builder = Builder::default()
91+
.header(header_path.display().to_string())
92+
.derive_debug(true)
93+
.derive_copy(false)
94+
.derive_default(false)
95+
.derive_partialeq(false)
96+
.derive_eq(false)
97+
.derive_partialord(false)
98+
.derive_ord(false)
99+
.derive_hash(false)
100+
.parse_callbacks(Box::new(AddDerivesCallback::new(&["Default"])));
101+
let bindings = builder
102+
.generate()
103+
.unwrap_or_else(|e| panic!("Failed to generate bindings: {e}"));
104+
let output = write_bindings_to_string(&bindings);
105+
let output_without_spaces = output.replace(' ', "");
106+
assert!(
107+
output_without_spaces.contains("#[derive(Debug,Default)]"),
108+
"Unexpected bindgen output:\n{}",
109+
output.as_str()
110+
);
111+
}
112+
}

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)