Skip to content

Commit

Permalink
Add option to deduplicate impl blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
nasso committed Feb 23, 2025
1 parent 20aa65a commit 6f67f7e
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 2 deletions.
44 changes: 44 additions & 0 deletions bindgen-tests/tests/expectations/tests/merge_impl_blocks.rs

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

20 changes: 20 additions & 0 deletions bindgen-tests/tests/headers/merge_impl_blocks.hpp
Original file line number Diff line number Diff line change
@@ -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();
}
82 changes: 82 additions & 0 deletions bindgen/codegen/postprocessing/merge_impl_blocks.rs
Original file line number Diff line number Diff line change
@@ -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<Item>) {
// Keep all the impl blocks in a different `Vec` for faster search.
let mut impl_blocks = Vec::<ItemImpl>::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 <Trait> 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));
}
}
9 changes: 7 additions & 2 deletions bindgen/codegen/postprocessing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<TokenStream>,
Expand Down
5 changes: 5 additions & 0 deletions bindgen/options/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)>,
Expand Down Expand Up @@ -640,6 +643,7 @@ where
vtable_generation,
sort_semantically,
merge_extern_blocks,
merge_impl_blocks,
override_abi,
wrap_unsafe_ops,
clang_macro_fallback,
Expand Down Expand Up @@ -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(),
Expand Down
13 changes: 13 additions & 0 deletions bindgen/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down

0 comments on commit 6f67f7e

Please sign in to comment.