Skip to content

Commit

Permalink
support variadic arguments through tuples
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdrz committed Oct 5, 2022
1 parent 73ce4bc commit 522ab91
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 14 deletions.
15 changes: 15 additions & 0 deletions bindgen-cli/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ where
Arg::new("merge-extern-blocks")
.long("merge-extern-blocks")
.help("Deduplicates extern blocks."),
Arg::new("tuple-varargs-len").long("tuple-varargs-len").takes_value(true).help("Enables using tuples to emulate variadic arguments up to a certain tuple length"),
Arg::new("V")
.long("version")
.help("Prints the version, and exits"),
Expand Down Expand Up @@ -1088,5 +1089,19 @@ where
builder = builder.merge_extern_blocks(true);
}

if let Some(len) = matches.value_of("tuple-varargs-len") {
let len = match len.parse() {
Ok(len) => len,
Err(err) => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("invalid length: {}", err),
))
}
};

builder = builder.tuple_varargs_len(len);
}

Ok((builder, output, verbose))
}
77 changes: 77 additions & 0 deletions bindgen-tests/tests/expectations/tests/variadic-method.rs

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

1 change: 1 addition & 0 deletions bindgen-tests/tests/headers/variadic-method.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// bindgen-flags: --tuple-varargs-len 5

void foo(const char* fmt, ...);

Expand Down
135 changes: 121 additions & 14 deletions bindgen/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use quote::TokenStreamExt;
use crate::{Entry, HashMap, HashSet};
use std::borrow::Cow;
use std::cell::Cell;
use std::collections::BTreeMap;
use std::collections::VecDeque;
use std::fmt::Write;
use std::iter;
Expand Down Expand Up @@ -192,6 +193,12 @@ impl From<DerivableTraits> for Vec<&'static str> {
}
}

struct VariadicMethodInfo {
args: Vec<proc_macro2::TokenStream>,
ret: proc_macro2::TokenStream,
exprs: Vec<proc_macro2::TokenStream>,
}

struct CodegenResult<'a> {
items: Vec<proc_macro2::TokenStream>,
dynamic_items: DynamicItems,
Expand Down Expand Up @@ -239,6 +246,8 @@ struct CodegenResult<'a> {
/// function name to the number of overloads we have already codegen'd for
/// that name. This lets us give each overload a unique suffix.
overload_counters: HashMap<String, u32>,

variadic_methods: BTreeMap<Ident, VariadicMethodInfo>,
}

impl<'a> CodegenResult<'a> {
Expand All @@ -256,6 +265,7 @@ impl<'a> CodegenResult<'a> {
functions_seen: Default::default(),
vars_seen: Default::default(),
overload_counters: Default::default(),
variadic_methods: Default::default(),
}
}

Expand Down Expand Up @@ -2485,12 +2495,6 @@ impl MethodCodegen for Method {
return;
}

// Do not generate variadic methods, since rust does not allow
// implementing them, and we don't do a good job at it anyway.
if signature.is_variadic() {
return;
}

let count = {
let count = method_names.entry(name.clone()).or_insert(0);
*count += 1;
Expand All @@ -2508,13 +2512,10 @@ impl MethodCodegen for Method {
let function_name = ctx.rust_ident(function_name);
let mut args = utils::fnsig_arguments(ctx, signature);
let mut ret = utils::fnsig_return_ty(ctx, signature);
let is_variadic = signature.is_variadic();

if !self.is_static() && !self.is_constructor() {
args[0] = if self.is_const() {
quote! { &self }
} else {
quote! { &mut self }
};
if is_variadic && ctx.options().tuple_varargs_len.is_none() {
return;
}

// If it's a constructor, we always return `Self`, and we inject the
Expand All @@ -2530,6 +2531,28 @@ impl MethodCodegen for Method {
let mut exprs =
helpers::ast_ty::arguments_from_signature(signature, ctx);

if is_variadic {
let (last_arg, args) = args.split_last_mut().unwrap();
// FIXME (pvdrz): what if this identifier is already being used?
*last_arg = quote!(var_args: impl VarArgs);
result.variadic_methods.insert(
function_name.clone(),
VariadicMethodInfo {
args: args.to_owned(),
ret: ret.clone(),
exprs: exprs.clone(),
},
);
}

if !self.is_static() && !self.is_constructor() {
args[0] = if self.is_const() {
quote! { &self }
} else {
quote! { &mut self }
};
}

let mut stmts = vec![];

// If it's a constructor, we need to insert an extra parameter with a
Expand Down Expand Up @@ -2563,8 +2586,15 @@ impl MethodCodegen for Method {
};
};

let call = quote! {
#function_name (#( #exprs ),* )
let call = if is_variadic {
let function_name = quote::format_ident!("call_{}", function_name);
quote! {
var_args.#function_name(#( #exprs ),* )
}
} else {
quote! {
#function_name (#( #exprs ),* )
}
};

stmts.push(call);
Expand Down Expand Up @@ -4508,6 +4538,10 @@ pub(crate) fn codegen(
result.push(dynamic_items_tokens);
}

if let Some(max_len) = context.options().tuple_varargs_len {
utils::generate_varargs_trait(max_len, &mut result);
}

postprocessing::postprocessing(result.items, context.options())
})
}
Expand Down Expand Up @@ -5047,4 +5081,77 @@ pub mod utils {

true
}

pub(super) fn generate_varargs_trait(
max_len: usize,
result: &mut super::CodegenResult,
) {
// This will hold the identifiers to be used for the fields of the tuples `t0, ..., tn` as
// well as the identifiers of the type parameters for each field type of the tuples `T0, ..., TN`.
// FIXME (pvdrz): what if these identifiers are already in use?
let (fields, params): (Vec<_>, Vec<_>) = (0..max_len)
.map(|len| {
(
quote::format_ident!("t{}", len),
quote::format_ident!("T{}", len),
)
})
.unzip();

// This will hold the methods to be declared in the `VarArgs` trait as well as the
// bodies of the implementations of such methods for each tuple length.
let (trait_method_decls, trait_method_impl_fns): (Vec<_>, Vec<_>) =
std::mem::take(&mut result.variadic_methods)
.into_iter()
.map(|(name, info)| {
let super::VariadicMethodInfo { args, ret, exprs } = info;

// The name of the `VarArgs` trait method associated with this method.
// FIXME (pvdrz): what these identifiers are already in use?
let trait_method_name =
quote::format_ident!("call_{}", name);

// The declaration of the `VarArgs` trait method associated with this method.
let trait_method_decl = quote! {
unsafe fn #trait_method_name(self, #(#args),*) #ret
};

// The implementations of the `VarArgs` trait method associated with this
// method for each tuple length.
let trait_method_impls = (0..=fields.len())
.map(|index| {
let fields = &fields[..index];
quote! {
#trait_method_decl {
let (#(#fields,)*) = self;
#name(#(#exprs),*, #(#fields),*)
}
}
})
.collect::<Vec<_>>();

(trait_method_decl, trait_method_impls)
})
.unzip();

// Push the trait with the method declarations.
result.items.push(quote! {
pub trait VarArgs {
#(#trait_method_decls;)*
}
});

for index in 0..=params.len() {
let params = &params[..index];
let methods =
trait_method_impl_fns.iter().map(|impls| &impls[index]);

// Push the implementation the trait for each tuple.
result.items.push(quote! {
impl<#(#params),*> VarArgs for (#(#params,)*) {
#(#methods)*
}
});
}
}
}
14 changes: 14 additions & 0 deletions bindgen/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,10 @@ impl Builder {
output_vector.push("--merge-extern-blocks".into());
}

if let Some(len) = self.options.tuple_varargs_len {
output_vector.push(format!("--tuple-varargs-len={}", len));
}

// Add clang arguments

output_vector.push("--".into());
Expand Down Expand Up @@ -1560,6 +1564,12 @@ impl Builder {
self
}

/// Sets the maximum tuple length that can be used to emulate variadic arguments,
pub fn tuple_varargs_len(mut self, len: usize) -> Self {
self.options.tuple_varargs_len = Some(len);
self
}

/// Generate the Rust bindings using the options built up thus far.
pub fn generate(mut self) -> Result<Bindings, BindgenError> {
// Add any extra arguments from the environment to the clang command line.
Expand Down Expand Up @@ -2105,6 +2115,9 @@ struct BindgenOptions {

/// Deduplicate `extern` blocks.
merge_extern_blocks: bool,

/// The maximum tuple length that can be used to emulate variadic arguments.
tuple_varargs_len: Option<usize>,
}

/// TODO(emilio): This is sort of a lie (see the error message that results from
Expand Down Expand Up @@ -2261,6 +2274,7 @@ impl Default for BindgenOptions {
vtable_generation: false,
sort_semantically: false,
merge_extern_blocks: false,
tuple_varargs_len: Default::default(),
}
}
}
Expand Down

0 comments on commit 522ab91

Please sign in to comment.