From 6f67f7e2e31146486fca26b641d54aca491ea4e8 Mon Sep 17 00:00:00 2001 From: nasso Date: Sun, 23 Feb 2025 00:56:52 +0100 Subject: [PATCH] Add option to deduplicate impl blocks --- .../expectations/tests/merge_impl_blocks.rs | 44 ++++++++++ .../tests/headers/merge_impl_blocks.hpp | 20 +++++ .../postprocessing/merge_impl_blocks.rs | 82 +++++++++++++++++++ bindgen/codegen/postprocessing/mod.rs | 9 +- bindgen/options/cli.rs | 5 ++ bindgen/options/mod.rs | 13 +++ 6 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 bindgen-tests/tests/expectations/tests/merge_impl_blocks.rs create mode 100644 bindgen-tests/tests/headers/merge_impl_blocks.hpp create mode 100644 bindgen/codegen/postprocessing/merge_impl_blocks.rs diff --git a/bindgen-tests/tests/expectations/tests/merge_impl_blocks.rs b/bindgen-tests/tests/expectations/tests/merge_impl_blocks.rs new file mode 100644 index 0000000000..ec98565310 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/merge_impl_blocks.rs @@ -0,0 +1,44 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +#[allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] +pub mod root { + #[allow(unused_imports)] + use self::super::root; + unsafe extern "C" { + #[link_name = "\u{1}_Z3foov"] + pub fn foo() -> ::std::os::raw::c_int; + } + #[repr(transparent)] + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + pub struct Foo(pub ::std::os::raw::c_int); + unsafe extern "C" { + #[link_name = "\u{1}_Z3barv"] + pub fn bar() -> ::std::os::raw::c_int; + } + pub mod ns { + #[allow(unused_imports)] + use self::super::super::root; + unsafe extern "C" { + #[link_name = "\u{1}_ZN2ns3fooEv"] + pub fn foo() -> ::std::os::raw::c_int; + } + #[repr(transparent)] + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + pub struct Bar(pub ::std::os::raw::c_int); + unsafe extern "C" { + #[link_name = "\u{1}_ZN2ns3barEv"] + pub fn bar() -> ::std::os::raw::c_int; + } + impl Bar { + pub const B0: root::ns::Bar = root::ns::Bar(1); + pub const B1: root::ns::Bar = root::ns::Bar(4); + pub const B2: root::ns::Bar = root::ns::Bar(3); + pub const B3: root::ns::Bar = root::ns::Bar(-1); + } + } + impl Foo { + pub const Bar: root::Foo = root::Foo(2); + pub const Baz: root::Foo = root::Foo(4); + pub const Duplicated: root::Foo = root::Foo(4); + pub const Negative: root::Foo = root::Foo(-3); + } +} diff --git a/bindgen-tests/tests/headers/merge_impl_blocks.hpp b/bindgen-tests/tests/headers/merge_impl_blocks.hpp new file mode 100644 index 0000000000..fff10479d1 --- /dev/null +++ b/bindgen-tests/tests/headers/merge_impl_blocks.hpp @@ -0,0 +1,20 @@ +// bindgen-flags: --merge-impl-blocks --newtype-enum '.*' --enable-cxx-namespaces -- --target=x86_64-unknown-linux +int foo(); +enum Foo { + Bar = 1 << 1, + Baz = 1 << 2, + Duplicated = 1 << 2, + Negative = -3, +}; +int bar(); + +namespace ns { + int foo(); + enum Bar { + B0 = 1, + B1 = B0 + 3, + B2 = B0 + 2, + B3 = B0 - 2, + }; + int bar(); +} diff --git a/bindgen/codegen/postprocessing/merge_impl_blocks.rs b/bindgen/codegen/postprocessing/merge_impl_blocks.rs new file mode 100644 index 0000000000..31743c1638 --- /dev/null +++ b/bindgen/codegen/postprocessing/merge_impl_blocks.rs @@ -0,0 +1,82 @@ +use syn::{ + visit_mut::{visit_file_mut, visit_item_mod_mut, VisitMut}, + File, Item, ItemImpl, ItemMod, +}; + +pub(super) fn merge_impl_blocks(file: &mut File) { + Visitor.visit_file_mut(file); +} + +struct Visitor; + +impl VisitMut for Visitor { + fn visit_file_mut(&mut self, file: &mut File) { + visit_items(&mut file.items); + visit_file_mut(self, file); + } + + fn visit_item_mod_mut(&mut self, item_mod: &mut ItemMod) { + if let Some((_, ref mut items)) = item_mod.content { + visit_items(items); + } + visit_item_mod_mut(self, item_mod); + } +} + +fn visit_items(items: &mut Vec) { + // Keep all the impl blocks in a different `Vec` for faster search. + let mut impl_blocks = Vec::::new(); + + for item in std::mem::take(items) { + if let Item::Impl(ItemImpl { + attrs, + defaultness, + unsafety, + impl_token, + generics, + trait_: None, // don't merge `impl for T` blocks + self_ty, + brace_token, + items: impl_block_items, + }) = item + { + let mut exists = false; + for impl_block in &mut impl_blocks { + // Check if there is an equivalent impl block + if impl_block.attrs == attrs && + impl_block.unsafety == unsafety && + impl_block.generics == generics && + impl_block.self_ty == self_ty + { + // Merge the items of the two blocks. + impl_block.items.extend_from_slice(&impl_block_items); + exists = true; + break; + } + } + // If no matching impl block was found, store it. + if !exists { + impl_blocks.push(ItemImpl { + attrs, + defaultness, + unsafety, + impl_token, + generics, + trait_: None, + self_ty, + brace_token, + items: impl_block_items, + }); + } + } else { + // If the item is not an mergeable impl block, we don't have to do + // anything and just push it back. + items.push(item); + } + } + + // Move all the impl blocks alongside the rest of the items. + for impl_block in impl_blocks { + items.push(Item::Impl(impl_block)); + } +} diff --git a/bindgen/codegen/postprocessing/mod.rs b/bindgen/codegen/postprocessing/mod.rs index 9641698521..2d91fe40b5 100644 --- a/bindgen/codegen/postprocessing/mod.rs +++ b/bindgen/codegen/postprocessing/mod.rs @@ -5,9 +5,11 @@ use syn::{parse2, File}; use crate::BindgenOptions; mod merge_extern_blocks; +mod merge_impl_blocks; mod sort_semantically; use merge_extern_blocks::merge_extern_blocks; +use merge_impl_blocks::merge_impl_blocks; use sort_semantically::sort_semantically; struct PostProcessingPass { @@ -26,8 +28,11 @@ macro_rules! pass { }; } -const PASSES: &[PostProcessingPass] = - &[pass!(merge_extern_blocks), pass!(sort_semantically)]; +const PASSES: &[PostProcessingPass] = &[ + pass!(merge_extern_blocks), + pass!(merge_impl_blocks), + pass!(sort_semantically), +]; pub(crate) fn postprocessing( items: Vec, diff --git a/bindgen/options/cli.rs b/bindgen/options/cli.rs index f9a8572976..592aa9ba73 100644 --- a/bindgen/options/cli.rs +++ b/bindgen/options/cli.rs @@ -456,6 +456,9 @@ struct BindgenCommand { /// Deduplicates extern blocks. #[arg(long)] merge_extern_blocks: bool, + /// Deduplicates `impl` blocks. + #[arg(long)] + merge_impl_blocks: bool, /// Overrides the ABI of functions matching REGEX. The OVERRIDE value must be of the shape REGEX=ABI where ABI can be one of C, stdcall, efiapi, fastcall, thiscall, aapcs, win64 or C-unwind<.> #[arg(long, value_name = "OVERRIDE", value_parser = parse_abi_override)] override_abi: Vec<(Abi, String)>, @@ -640,6 +643,7 @@ where vtable_generation, sort_semantically, merge_extern_blocks, + merge_impl_blocks, override_abi, wrap_unsafe_ops, clang_macro_fallback, @@ -939,6 +943,7 @@ where vtable_generation, sort_semantically, merge_extern_blocks, + merge_impl_blocks, override_abi => |b, (abi, regex)| b.override_abi(abi, regex), wrap_unsafe_ops, clang_macro_fallback => |b, _| b.clang_macro_fallback(), diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs index ab6b232ec3..adda35971e 100644 --- a/bindgen/options/mod.rs +++ b/bindgen/options/mod.rs @@ -2030,6 +2030,19 @@ options! { }, as_args: "--merge-extern-blocks", }, + /// Whether to deduplicate `impl` blocks. + merge_impl_blocks: bool { + methods: { + /// Merge all `impl` blocks under the same module into a single one. + /// + /// `impl` blocks are not merged by default. + pub fn merge_impl_blocks(mut self, doit: bool) -> Self { + self.options.merge_impl_blocks = doit; + self + } + }, + as_args: "--merge-impl-blocks", + }, /// Whether to wrap unsafe operations in unsafe blocks. wrap_unsafe_ops: bool { methods: {