Skip to content

Move codegen postprocessing to its own module #2282

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

Merged
merged 9 commits into from
Sep 27, 2022
16 changes: 8 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,14 @@ generated Rust code are implemented using the [`syn`](https://docs.rs/syn) crate

### Implementing new options using `syn`

Here is a list of recommendations to be followed if a new option can be
implemented using the `syn` crate:

- The `BindgenOptions::require_syn` method must be updated to reflect that this
new option requires parsing the generated Rust code with `syn`.

- The implementation of the new option should be added at the end of
`Bindings::generate`, inside the `if options.require_syn() { ... }` block.
If a new option can be implemented using the `syn` crate it should be added to
the `codegen::postprocessing` module by following these steps:

- Introduce a new field to `BindgenOptions` for the option.
- Write a free function inside `codegen::postprocessing` implementing the
option. This function with the same name of the `BindgenOptions` field.
- Add a new value to the `codegen::postprocessing::PASSES` for the option using
the `pass!` macro.

## Pull Requests and Code Reviews

Expand Down
5 changes: 3 additions & 2 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod error;
mod helpers;
mod impl_debug;
mod impl_partialeq;
mod postprocessing;
pub mod struct_layout;

#[cfg(test)]
Expand Down Expand Up @@ -4439,7 +4440,7 @@ impl CodeGenerator for ObjCInterface {

pub(crate) fn codegen(
context: BindgenContext,
) -> (Vec<proc_macro2::TokenStream>, BindgenOptions, Vec<String>) {
) -> (proc_macro2::TokenStream, BindgenOptions, Vec<String>) {
context.gen(|context| {
let _t = context.timer("codegen");
let counter = Cell::new(0);
Expand Down Expand Up @@ -4489,7 +4490,7 @@ pub(crate) fn codegen(
result.push(dynamic_items_tokens);
}

result.items
postprocessing::postprocessing(result.items, context.options())
})
}

Expand Down
46 changes: 46 additions & 0 deletions src/codegen/postprocessing/merge_extern_blocks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use syn::{Item, ItemForeignMod};

pub(super) fn merge_extern_blocks(items: &mut Vec<Item>) {
// Keep all the extern blocks in a different `Vec` for faster search.
let mut foreign_mods = Vec::<ItemForeignMod>::new();

for item in std::mem::take(items) {
match item {
Item::ForeignMod(ItemForeignMod {
attrs,
abi,
brace_token,
items: foreign_items,
}) => {
let mut exists = false;
for foreign_mod in &mut foreign_mods {
// Check if there is a extern block with the same ABI and
// attributes.
if foreign_mod.attrs == attrs && foreign_mod.abi == abi {
// Merge the items of the two blocks.
foreign_mod.items.extend_from_slice(&foreign_items);
exists = true;
break;
}
}
// If no existing extern block had the same ABI and attributes, store
// it.
if !exists {
foreign_mods.push(ItemForeignMod {
attrs,
abi,
brace_token,
items: foreign_items,
});
}
}
// If the item is not an extern block, we don't have to do anything.
_ => items.push(item),
}
}

// Move all the extern blocks alongside the rest of the items.
for foreign_mod in foreign_mods {
items.push(Item::ForeignMod(foreign_mod));
}
}
66 changes: 66 additions & 0 deletions src/codegen/postprocessing/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::Item;

use crate::BindgenOptions;

mod merge_extern_blocks;
mod sort_semantically;

use merge_extern_blocks::merge_extern_blocks;
use sort_semantically::sort_semantically;

struct PostProcessingPass {
should_run: fn(&BindgenOptions) -> bool,
run: fn(&mut Vec<Item>),
}

// TODO: This can be a const fn when mutable references are allowed in const
// context.
macro_rules! pass {
($pass:ident) => {
PostProcessingPass {
should_run: |options| options.$pass,
run: |items| $pass(items),
}
};
}

const PASSES: &[PostProcessingPass] =
&[pass!(merge_extern_blocks), pass!(sort_semantically)];

pub(crate) fn postprocessing(
items: Vec<TokenStream>,
options: &BindgenOptions,
) -> TokenStream {
let require_syn = PASSES.iter().any(|pass| (pass.should_run)(options));
if !require_syn {
return items.into_iter().collect();
}
let module_wrapped_tokens =
quote!(mod wrapper_for_sorting_hack { #( #items )* });

// This syn business is a hack, for now. This means that we are re-parsing already
// generated code using `syn` (as opposed to `quote`) because `syn` provides us more
// control over the elements.
// One caveat is that some of the items coming from `quote`d output might have
// multiple items within them. Hence, we have to wrap the incoming in a `mod`.
// The two `unwrap`s here are deliberate because
// The first one won't panic because we build the `mod` and know it is there
// The second one won't panic because we know original output has something in
// it already.
let (_, mut items) = syn::parse2::<syn::ItemMod>(module_wrapped_tokens)
.unwrap()
.content
.unwrap();

for pass in PASSES {
if (pass.should_run)(options) {
(pass.run)(&mut items);
}
}

let synful_items = items.into_iter().map(|item| item.into_token_stream());

quote! { #( #synful_items )* }
}
24 changes: 24 additions & 0 deletions src/codegen/postprocessing/sort_semantically.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use syn::Item;

pub(super) fn sort_semantically(items: &mut [Item]) {
items.sort_by_key(|item| match item {
Item::Type(_) => 0,
Item::Struct(_) => 1,
Item::Const(_) => 2,
Item::Fn(_) => 3,
Item::Enum(_) => 4,
Item::Union(_) => 5,
Item::Static(_) => 6,
Item::Trait(_) => 7,
Item::TraitAlias(_) => 8,
Item::Impl(_) => 9,
Item::Mod(_) => 10,
Item::Use(_) => 11,
Item::Verbatim(_) => 12,
Item::ExternCrate(_) => 13,
Item::ForeignMod(_) => 14,
Item::Macro(_) => 15,
Item::Macro2(_) => 16,
_ => 18,
});
}
113 changes: 1 addition & 112 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ use std::{env, iter};
// Some convenient typedefs for a fast hash map and hash set.
type HashMap<K, V> = ::rustc_hash::FxHashMap<K, V>;
type HashSet<K> = ::rustc_hash::FxHashSet<K>;
use quote::ToTokens;
pub(crate) use std::collections::hash_map::Entry;

/// Default prefix for the anon fields.
Expand Down Expand Up @@ -2118,11 +2117,6 @@ struct BindgenOptions {
impl ::std::panic::UnwindSafe for BindgenOptions {}

impl BindgenOptions {
/// Whether any of the enabled options requires `syn`.
fn require_syn(&self) -> bool {
self.sort_semantically || self.merge_extern_blocks
}

fn build(&mut self) {
let mut regex_sets = [
&mut self.allowlisted_vars,
Expand Down Expand Up @@ -2555,112 +2549,7 @@ impl Bindings {
parse(&mut context)?;
}

let (items, options, warnings) = codegen::codegen(context);

let module = if options.require_syn() {
let module_wrapped_tokens =
quote!(mod wrapper_for_sorting_hack { #( #items )* });

// This syn business is a hack, for now. This means that we are re-parsing already
// generated code using `syn` (as opposed to `quote`) because `syn` provides us more
// control over the elements.
// One caveat is that some of the items coming from `quote`d output might have
// multiple items within them. Hence, we have to wrap the incoming in a `mod`.
// The two `unwrap`s here are deliberate because
// The first one won't panic because we build the `mod` and know it is there
// The second one won't panic because we know original output has something in
// it already.
let mut syn_parsed_items =
syn::parse2::<syn::ItemMod>(module_wrapped_tokens)
.unwrap()
.content
.unwrap()
.1;

if options.merge_extern_blocks {
// Here we will store all the items after deduplication.
let mut items = Vec::new();

// Keep all the extern blocks in a different `Vec` for faster search.
let mut foreign_mods = Vec::<syn::ItemForeignMod>::new();
for item in syn_parsed_items {
match item {
syn::Item::ForeignMod(syn::ItemForeignMod {
attrs,
abi,
brace_token,
items: foreign_items,
}) => {
let mut exists = false;
for foreign_mod in &mut foreign_mods {
// Check if there is a extern block with the same ABI and
// attributes.
if foreign_mod.attrs == attrs &&
foreign_mod.abi == abi
{
// Merge the items of the two blocks.
foreign_mod
.items
.extend_from_slice(&foreign_items);
exists = true;
break;
}
}
// If no existing extern block had the same ABI and attributes, store
// it.
if !exists {
foreign_mods.push(syn::ItemForeignMod {
attrs,
abi,
brace_token,
items: foreign_items,
});
}
}
// If the item is not an extern block, we don't have to do anything.
_ => items.push(item),
}
}

// Move all the extern blocks alongiside the rest of the items.
for foreign_mod in foreign_mods {
items.push(syn::Item::ForeignMod(foreign_mod));
}

syn_parsed_items = items;
}

if options.sort_semantically {
syn_parsed_items.sort_by_key(|item| match item {
syn::Item::Type(_) => 0,
syn::Item::Struct(_) => 1,
syn::Item::Const(_) => 2,
syn::Item::Fn(_) => 3,
syn::Item::Enum(_) => 4,
syn::Item::Union(_) => 5,
syn::Item::Static(_) => 6,
syn::Item::Trait(_) => 7,
syn::Item::TraitAlias(_) => 8,
syn::Item::Impl(_) => 9,
syn::Item::Mod(_) => 10,
syn::Item::Use(_) => 11,
syn::Item::Verbatim(_) => 12,
syn::Item::ExternCrate(_) => 13,
syn::Item::ForeignMod(_) => 14,
syn::Item::Macro(_) => 15,
syn::Item::Macro2(_) => 16,
_ => 18,
});
}

let synful_items = syn_parsed_items
.into_iter()
.map(|item| item.into_token_stream());

quote! { #( #synful_items )* }
} else {
quote! { #( #items )* }
};
let (module, options, warnings) = codegen::codegen(context);

Ok(Bindings {
options,
Expand Down