Skip to content

Emulate variadic methods using tuples #2276

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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