use proc_macro2::TokenStream;
use quote::quote;
use syn::{
parse::Parser,
parse_quote,
punctuated::Punctuated,
token::{Comma, Unsafe},
Attribute, FnArg, ForeignItem, Ident, ImplItem, Item, ReturnType,
};
use super::{
unqualify::{unqualify_params, unqualify_ret_type},
RsCodegenResult, Use,
};
use crate::{
conversion::{
analysis::fun::{
ArgumentAnalysis, FnAnalysis, FnKind, MethodKind, RustRenameStrategy, UnsafetyNeeded,
},
api::ImplBlockDetails,
codegen_rs::lifetime::add_lifetime_to_all_params,
},
types::{Namespace, QualifiedName},
};
use crate::{
conversion::{api::FuncToConvert, codegen_rs::lifetime::add_explicit_lifetime_if_necessary},
types::make_ident,
};
impl UnsafetyNeeded {
fn bridge_token(&self) -> Option<Unsafe> {
match self {
UnsafetyNeeded::None => None,
_ => Some(parse_quote! { unsafe }),
}
}
fn wrapper_token(&self) -> Option<Unsafe> {
match self {
UnsafetyNeeded::Always => Some(parse_quote! { unsafe }),
_ => None,
}
}
}
pub(super) fn gen_function(
ns: &Namespace,
fun: FuncToConvert,
analysis: FnAnalysis,
cpp_call_name: String,
) -> RsCodegenResult {
if !analysis.generate_code {
return RsCodegenResult::default();
}
let cxxbridge_name = analysis.cxxbridge_name;
let rust_name = analysis.rust_name;
let ret_type = analysis.ret_type;
let param_details = analysis.param_details;
let wrapper_function_needed = analysis.cpp_wrapper.is_some();
let params = analysis.params;
let vis = analysis.vis;
let kind = analysis.kind;
let doc_attr = fun.doc_attr;
let mut cpp_name_attr = Vec::new();
let mut impl_entry = None;
let rust_name_attr: Vec<_> = match &analysis.rust_rename_strategy {
RustRenameStrategy::RenameUsingRustAttr => Attribute::parse_outer
.parse2(quote!(
#[rust_name = #rust_name]
))
.unwrap(),
_ => Vec::new(),
};
let wrapper_unsafety = analysis.requires_unsafe.wrapper_token();
let fn_generator = FnGenerator {
param_details: ¶m_details,
cxxbridge_name: &cxxbridge_name,
rust_name: &rust_name,
unsafety: &wrapper_unsafety,
doc_attr: &doc_attr,
};
let mut materialization = match kind {
FnKind::Method(..) => None,
FnKind::Function => match analysis.rust_rename_strategy {
RustRenameStrategy::RenameInOutputMod(alias) => {
Some(Use::UsedFromCxxBridgeWithAlias(alias))
}
_ => Some(Use::UsedFromCxxBridge),
},
FnKind::TraitMethod {
impl_for: _,
ref impl_for_specifics,
ref trait_signature,
ref method_name,
} => fn_generator.generate_trait_impl(
impl_for_specifics,
trait_signature,
method_name,
&ret_type,
),
};
let any_param_needs_rust_conversion = param_details
.iter()
.any(|pd| pd.conversion.rust_work_needed());
let rust_wrapper_needed = any_param_needs_rust_conversion
|| (cxxbridge_name != rust_name && matches!(kind, FnKind::Method(..)));
if rust_wrapper_needed {
match kind {
FnKind::Method(ref type_name, MethodKind::Constructor) => {
impl_entry = Some(fn_generator.generate_constructor_impl(type_name));
}
FnKind::Method(ref type_name, ref method_kind) => {
impl_entry = Some(fn_generator.generate_method_impl(
matches!(
method_kind,
MethodKind::MakeUnique | MethodKind::Constructor
),
type_name,
&ret_type,
));
}
_ => {
materialization = Some(Use::Custom(fn_generator.generate_function_impl(&ret_type)));
}
}
}
if cxxbridge_name != cpp_call_name && !wrapper_function_needed {
cpp_name_attr = Attribute::parse_outer
.parse2(quote!(
#[cxx_name = #cpp_call_name]
))
.unwrap();
}
let (lifetime_tokens, params, ret_type) =
add_explicit_lifetime_if_necessary(¶m_details, params, &ret_type);
let params = unqualify_params(params);
let ret_type = unqualify_ret_type(ret_type.into_owned());
let namespace_attr = if ns.is_empty() || wrapper_function_needed {
Vec::new()
} else {
let namespace_string = ns.to_string();
Attribute::parse_outer
.parse2(quote!(
#[namespace = #namespace_string]
))
.unwrap()
};
let bridge_unsafety = analysis.requires_unsafe.bridge_token();
let extern_c_mod_item = ForeignItem::Fn(parse_quote!(
#(#namespace_attr)*
#(#rust_name_attr)*
#(#cpp_name_attr)*
#doc_attr
#vis #bridge_unsafety fn #cxxbridge_name #lifetime_tokens ( #params ) #ret_type;
));
RsCodegenResult {
extern_c_mod_items: vec![extern_c_mod_item],
bridge_items: Vec::new(),
global_items: Vec::new(),
bindgen_mod_items: Vec::new(),
impl_entry,
materializations: materialization.into_iter().collect(),
extern_rust_mod_items: Vec::new(),
}
}
#[derive(Clone)]
struct FnGenerator<'a> {
param_details: &'a [ArgumentAnalysis],
cxxbridge_name: &'a Ident,
rust_name: &'a str,
unsafety: &'a Option<Unsafe>,
doc_attr: &'a Option<Attribute>,
}
impl<'a> FnGenerator<'a> {
fn generate_arg_lists(&self, avoid_self: bool) -> (Punctuated<FnArg, Comma>, Vec<TokenStream>) {
let mut wrapper_params: Punctuated<FnArg, Comma> = Punctuated::new();
let mut arg_list = Vec::new();
for pd in self.param_details {
let type_name = pd.conversion.rust_wrapper_unconverted_type();
let wrapper_arg_name = if pd.self_type.is_some() && !avoid_self {
parse_quote!(self)
} else {
pd.name.clone()
};
wrapper_params.push(parse_quote!(
#wrapper_arg_name: #type_name
));
arg_list.push(pd.conversion.rust_conversion(wrapper_arg_name));
}
(wrapper_params, arg_list)
}
fn generate_method_impl(
&self,
avoid_self: bool,
impl_block_type_name: &QualifiedName,
ret_type: &ReturnType,
) -> Box<ImplBlockDetails> {
let (wrapper_params, arg_list) = self.generate_arg_lists(avoid_self);
let (lifetime_tokens, wrapper_params, ret_type) =
add_explicit_lifetime_if_necessary(self.param_details, wrapper_params, ret_type);
let rust_name = make_ident(self.rust_name);
let unsafety = self.unsafety;
let doc_attr = self.doc_attr;
let cxxbridge_name = self.cxxbridge_name;
Box::new(ImplBlockDetails {
item: ImplItem::Method(parse_quote! {
#doc_attr
pub #unsafety fn #rust_name #lifetime_tokens ( #wrapper_params ) #ret_type {
cxxbridge::#cxxbridge_name ( #(#arg_list),* )
}
}),
ty: impl_block_type_name.get_final_ident(),
})
}
fn generate_trait_impl(
&self,
impl_for_specifics: &TokenStream,
trait_signature: &TokenStream,
method_name: &Ident,
ret_type: &ReturnType,
) -> Option<Use> {
let (wrapper_params, arg_list) = self.generate_arg_lists(false);
let (lifetime_tokens, wrapper_params, ret_type) =
add_explicit_lifetime_if_necessary(self.param_details, wrapper_params, ret_type);
let doc_attr = self.doc_attr;
let unsafety = self.unsafety;
let cxxbridge_name = self.cxxbridge_name;
Some(Use::Custom(Box::new(parse_quote! {
impl #lifetime_tokens #trait_signature for #impl_for_specifics {
#doc_attr
#unsafety fn #method_name ( #wrapper_params ) #ret_type {
cxxbridge::#cxxbridge_name ( #(#arg_list),* )
}
}
})))
}
fn generate_constructor_impl(
&self,
impl_block_type_name: &QualifiedName,
) -> Box<ImplBlockDetails> {
let (wrapper_params, arg_list) = self.generate_arg_lists(true);
let mut wrapper_params: Punctuated<FnArg, Comma> =
wrapper_params.into_iter().skip(1).collect();
let ptr_arg_name = &arg_list[0];
let rust_name = make_ident(&self.rust_name);
let any_references = self.param_details.iter().any(|pd| pd.was_reference);
let (lifetime_param, lifetime_addition) = if any_references {
add_lifetime_to_all_params(&mut wrapper_params);
(quote! { <'a> }, quote! { + 'a })
} else {
(quote! {}, quote! {})
};
let cxxbridge_name = self.cxxbridge_name;
let body = quote! {
autocxx::moveit::new::by_raw(move |#ptr_arg_name| {
let #ptr_arg_name = #ptr_arg_name.get_unchecked_mut().as_mut_ptr();
cxxbridge::#cxxbridge_name(#(#arg_list),* )
})
};
let body = if self.unsafety.is_some() {
body
} else {
quote! {
unsafe { #body }
}
};
let doc_attr = self.doc_attr;
let unsafety = self.unsafety;
Box::new(ImplBlockDetails {
item: ImplItem::Method(parse_quote! {
#doc_attr
pub #unsafety fn #rust_name #lifetime_param ( #wrapper_params ) -> impl autocxx::moveit::new::New<Output=Self> #lifetime_addition {
#body
}
}),
ty: impl_block_type_name.get_final_ident(),
})
}
fn generate_function_impl(&self, ret_type: &ReturnType) -> Box<Item> {
let (wrapper_params, arg_list) = self.generate_arg_lists(false);
let rust_name = make_ident(self.rust_name);
let doc_attr = self.doc_attr;
let unsafety = self.unsafety;
Box::new(Item::Fn(parse_quote! {
#doc_attr
pub #unsafety fn #rust_name ( #wrapper_params ) #ret_type {
cxxbridge::#rust_name ( #(#arg_list),* )
}
}))
}
}