use proc_macro2::Ident;
use quote::{format_ident, quote};
use std::{collections::HashMap, sync::Mutex};
use syn::{parse_quote, punctuated::Punctuated, Expr, ExprLit, FnArg, ItemFn, Token, Type};
use crate::extendr_options::ExtendrOptions;
pub const META_PREFIX: &str = "meta__";
pub const WRAP_PREFIX: &str = "wrap__";
lazy_static::lazy_static! {
static ref STRUCT_DOCS: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
}
pub fn register_struct_doc(name: &str, doc: &str) {
STRUCT_DOCS
.lock()
.unwrap()
.insert(name.to_string(), doc.to_string());
}
pub fn get_struct_doc(name: &str) -> String {
STRUCT_DOCS
.lock()
.unwrap()
.get(name)
.cloned()
.unwrap_or_default()
}
pub(crate) fn make_function_wrappers(
opts: &ExtendrOptions,
wrappers: &mut Vec<ItemFn>,
prefix: &str,
attrs: &[syn::Attribute],
sig: &mut syn::Signature,
self_ty: Option<&syn::Type>,
) -> syn::Result<()> {
let rust_name = sig.ident.clone();
let r_name_str = if let Some(r_name) = opts.r_name.as_ref() {
r_name.clone()
} else {
sig.ident.to_string()
};
let mod_name = if let Some(mod_name) = opts.mod_name.as_ref() {
format_ident!("{}", mod_name)
} else {
sig.ident.clone()
};
let mod_name = sanitize_identifier(mod_name);
let wrap_name = format_ident!("{}{}{}", WRAP_PREFIX, prefix, mod_name);
let meta_name = format_ident!("{}{}{}", META_PREFIX, prefix, mod_name);
let rust_name_str = format!("{}", rust_name);
let c_name_str = format!("{}", mod_name);
let wrap_name_str = format!("{}", wrap_name);
let doc_string = get_doc_string(attrs);
let return_type_string = get_return_type(sig);
let opts_invisible = match opts.invisible {
Some(true) => quote!(Some(true)),
Some(false) => quote!(Some(false)),
None => quote!(None),
};
let inputs = &mut sig.inputs;
let has_self = matches!(inputs.iter().next(), Some(FnArg::Receiver(_)));
let call_name = if has_self {
let is_mut = match inputs.iter().next() {
Some(FnArg::Receiver(ref receiver)) => receiver.mutability.is_some(),
_ => false,
};
if is_mut {
quote! { extendr_api::unwrap_or_throw_error(
<&mut #self_ty>::try_from(&mut _self_robj)
).#rust_name }
} else {
quote! { extendr_api::unwrap_or_throw_error(
<&#self_ty>::try_from(&_self_robj)
).#rust_name }
}
} else if let Some(ref self_ty) = &self_ty {
quote! { <#self_ty>::#rust_name }
} else {
quote! { #rust_name }
};
let formal_args = inputs
.iter()
.map(|input| translate_formal(input, self_ty))
.collect::<syn::Result<Punctuated<FnArg, Token![,]>>>()?;
let sexp_args = formal_args
.clone()
.into_iter()
.map(|x| match x {
FnArg::Receiver(_) => unreachable!(),
FnArg::Typed(ref typed) => match typed.pat.as_ref() {
syn::Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),
_ => unreachable!(),
},
})
.collect::<Vec<Ident>>();
let convert_args: Vec<syn::Stmt> = inputs
.iter()
.map(translate_to_robj)
.collect::<syn::Result<Vec<syn::Stmt>>>()?;
let actual_args: Punctuated<Expr, Token![,]> =
inputs.iter().filter_map(translate_actual).collect();
let meta_args: Vec<Expr> = inputs
.iter_mut()
.map(|input| translate_meta_arg(input, self_ty))
.collect::<syn::Result<Vec<Expr>>>()?;
let rng_start = if opts.use_rng {
{
quote!(single_threaded(|| unsafe {
extendr_api::GetRNGstate();
});)
}
} else {
Default::default()
};
let rng_end = if opts.use_rng {
{
quote!(single_threaded(|| unsafe {
extendr_api::PutRNGstate();
});)
}
} else {
Default::default()
};
let return_is_ref_self = {
match sig.output {
syn::ReturnType::Default => false,
syn::ReturnType::Type(_, ref return_type) => match return_type.as_ref() {
Type::Reference(ref reference_type) => {
if let Type::Path(path) = reference_type.elem.as_ref() {
let is_typename_impl_type = self_ty
.map(|x| x == reference_type.elem.as_ref())
.unwrap_or(false);
path.path.is_ident("Self") || is_typename_impl_type
} else {
false
}
}
_ => false,
},
}
};
let return_type_conversion = if return_is_ref_self {
quote!(
let return_ref_to_self = #call_name(#actual_args);
#(
let arg_ref = extendr_api::R_ExternalPtrAddr(#sexp_args)
.cast::<Box<dyn std::any::Any>>()
.as_ref()
.unwrap()
.downcast_ref::<#self_ty>()
.unwrap();
if std::ptr::addr_eq(
arg_ref,
std::ptr::from_ref(return_ref_to_self)) {
return Ok(extendr_api::Robj::from_sexp(#sexp_args))
}
)*
Err(Error::ExpectedExternalPtrReference.into())
)
} else {
quote!(Ok(extendr_api::Robj::from(#call_name(#actual_args))))
};
wrappers.push(parse_quote!(
#[no_mangle]
#[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn #wrap_name(#formal_args) -> extendr_api::SEXP {
use extendr_api::robj::*;
#rng_start
let wrap_result_state: std::result::Result<
std::result::Result<extendr_api::Robj, Box<dyn std::error::Error>>,
Box<dyn std::any::Any + Send>
> = unsafe {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || -> std::result::Result<extendr_api::Robj, Box<dyn std::error::Error>> {
#(#convert_args)*
#return_type_conversion
}))
};
#rng_end
match wrap_result_state {
Ok(Ok(zz)) => {
return unsafe { zz.get() };
}
Ok(Err(conversion_err)) => {
let err_string = conversion_err.to_string();
drop(conversion_err); extendr_api::throw_r_error(&err_string);
}
Err(unwind_err) => {
let panic_msg = if let Some(s) = unwind_err.downcast_ref::<&str>() {
(*s).to_string()
} else if let Some(s) = unwind_err.downcast_ref::<String>() {
s.clone()
} else {
format!("User function panicked: {}", #r_name_str)
};
extendr_api::throw_r_error(&panic_msg);
}
}
}
));
wrappers.push(parse_quote!(
#[allow(non_snake_case)]
fn #meta_name(metadata: &mut Vec<extendr_api::metadata::Func>) {
let mut args = vec![#(#meta_args,)*];
metadata.push(extendr_api::metadata::Func {
doc: #doc_string,
rust_name: #rust_name_str,
r_name: #r_name_str,
c_name: #wrap_name_str,
mod_name: #c_name_str,
args: args,
return_type: #return_type_string,
func_ptr: #wrap_name as * const u8,
hidden: false,
invisible: #opts_invisible,
})
}
));
Ok(())
}
pub fn get_doc_string(attrs: &[syn::Attribute]) -> String {
let mut res = String::new();
for attr in attrs {
if !attr.path().is_ident("doc") {
continue;
}
if let syn::Meta::NameValue(ref nv) = attr.meta {
if let Expr::Lit(ExprLit {
lit: syn::Lit::Str(ref litstr),
..
}) = nv.value
{
if !res.is_empty() {
res.push('\n');
}
res.push_str(&litstr.value());
}
}
}
res
}
pub fn get_return_type(sig: &syn::Signature) -> String {
match &sig.output {
syn::ReturnType::Default => "()".into(),
syn::ReturnType::Type(_, ref rettype) => type_name(rettype),
}
}
pub fn mangled_type_name(type_: &Type) -> String {
let src = quote!( #type_ ).to_string();
let mut res = String::new();
for c in src.chars() {
if c != ' ' {
if c.is_alphanumeric() {
res.push(c)
} else {
let f = format!("_{:02x}", c as u32);
res.push_str(&f);
}
}
}
res
}
pub fn type_name(type_: &Type) -> String {
match type_ {
Type::Path(syn::TypePath { path, .. }) => {
if let Some(ident) = path.get_ident() {
ident.to_string()
} else if path.segments.len() == 1 {
let seg = path.segments.clone().into_iter().next().unwrap();
seg.ident.to_string()
} else {
mangled_type_name(type_)
}
}
Type::Group(syn::TypeGroup { elem, .. }) => type_name(elem),
Type::Reference(syn::TypeReference { elem, .. }) => type_name(elem),
Type::Paren(syn::TypeParen { elem, .. }) => type_name(elem),
Type::Ptr(syn::TypePtr { elem, .. }) => type_name(elem),
_ => mangled_type_name(type_),
}
}
pub fn translate_formal(input: &FnArg, self_ty: Option<&syn::Type>) -> syn::Result<FnArg> {
match input {
FnArg::Typed(ref pattype) => {
let pat = pattype.pat.as_ref();
let pat_ident = translate_only_alias(pat)?;
Ok(parse_quote! { #pat_ident: extendr_api::SEXP })
}
FnArg::Receiver(ref receiver) => {
if !receiver.attrs.is_empty() || receiver.reference.is_none() {
return Err(syn::Error::new_spanned(
input,
"expected &self or &mut self",
));
}
if self_ty.is_none() {
return Err(syn::Error::new_spanned(
input,"found &self in non-impl function - have you missed the #[extendr] before the impl?"
));
}
Ok(parse_quote! { _self : extendr_api::SEXP })
}
}
}
fn translate_only_alias(pat: &syn::Pat) -> Result<&Ident, syn::Error> {
Ok(match pat {
syn::Pat::Ident(ref pat_ident) => &pat_ident.ident,
_ => {
return Err(syn::Error::new_spanned(
pat,
"failed to translate name of argument",
));
}
})
}
fn translate_meta_arg(input: &mut FnArg, self_ty: Option<&syn::Type>) -> syn::Result<Expr> {
match input {
FnArg::Typed(ref mut pattype) => {
let pat = pattype.pat.as_ref();
let ty = pattype.ty.as_ref();
let pat_ident = translate_only_alias(pat)?;
let name_string = quote! { #pat_ident }.to_string();
let type_string = type_name(ty);
let default = if let Some(default) = get_defaults(&mut pattype.attrs) {
quote!(Some(#default))
} else if let Some(default) = get_named_lit(&mut pattype.attrs, "default") {
quote!(Some(#default))
} else {
quote!(None)
};
Ok(parse_quote! {
extendr_api::metadata::Arg {
name: #name_string,
arg_type: #type_string,
default: #default
}
})
}
FnArg::Receiver(ref receiver) => {
if !receiver.attrs.is_empty() || receiver.reference.is_none() {
return Err(syn::Error::new_spanned(
input,
"expected &self or &mut self",
));
}
if self_ty.is_none() {
return Err(syn::Error::new_spanned(
input,
"found &self in non-impl function - have you missed the #[extendr] before the impl?"
)
);
}
let type_string = type_name(self_ty.unwrap());
Ok(parse_quote! {
extendr_api::metadata::Arg {
name: "self",
arg_type: #type_string,
default: None
}
})
}
}
}
fn get_defaults(attrs: &mut Vec<syn::Attribute>) -> Option<String> {
use syn::Lit;
let mut new_attrs = Vec::new();
let mut res = None;
for i in attrs.drain(0..) {
if let syn::Meta::List(ref meta_list) = i.meta {
if meta_list.path.is_ident("extendr") {
let mut default_value = None;
let mut theres_default = false;
let parse_result = meta_list.parse_nested_meta(|meta| {
if meta.path.is_ident("default") {
theres_default = true;
let value = meta.value()?;
if let Ok(Lit::Str(litstr)) = value.parse() {
default_value = Some(litstr.value());
}
}
Ok(())
});
if parse_result.is_ok() && theres_default {
res = default_value;
continue;
}
}
}
new_attrs.push(i);
}
*attrs = new_attrs;
res
}
fn type_needs_mut_robj(ty: &Type) -> bool {
match ty {
Type::Reference(reference) => {
reference.mutability.is_some() || type_needs_mut_robj(&reference.elem)
}
Type::Path(path) => path.path.segments.iter().any(|seg| match &seg.arguments {
syn::PathArguments::AngleBracketed(args) => args.args.iter().any(|arg| {
if let syn::GenericArgument::Type(inner_ty) = arg {
type_needs_mut_robj(inner_ty)
} else {
false
}
}),
_ => false,
}),
Type::Tuple(tuple) => tuple.elems.iter().any(type_needs_mut_robj),
_ => false,
}
}
fn translate_to_robj(input: &FnArg) -> syn::Result<syn::Stmt> {
match input {
FnArg::Typed(ref pattype) => {
let pat = &pattype.pat.as_ref();
if let syn::Pat::Ident(ref ident) = pat {
let varname = format_ident!("_{}_robj", ident.ident);
let ident = &ident.ident;
let mut_token = type_needs_mut_robj(&pattype.ty);
if mut_token {
Ok(
parse_quote! { let mut #varname = extendr_api::robj::Robj::from_sexp(#ident); },
)
} else {
Ok(parse_quote! { let #varname = extendr_api::robj::Robj::from_sexp(#ident); })
}
} else {
Err(syn::Error::new_spanned(
input,
"expect identifier as arg name",
))
}
}
FnArg::Receiver(_) => {
Ok(parse_quote! { let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self); })
}
}
}
fn translate_actual(input: &FnArg) -> Option<Expr> {
match input {
FnArg::Typed(ref pattype) => {
let pat = &pattype.pat.as_ref();
if let syn::Pat::Ident(ref ident) = pat {
let varname = format_ident!("_{}_robj", ident.ident);
if type_needs_mut_robj(&pattype.ty) {
Some(parse_quote! { (&mut #varname).try_into()? })
} else {
Some(parse_quote! { (&#varname).try_into()? })
}
} else {
None
}
}
FnArg::Receiver(_) => {
None
}
}
}
fn get_named_lit(attrs: &mut Vec<syn::Attribute>, name: &str) -> Option<String> {
let mut new_attrs = Vec::new();
let mut res = None;
for a in attrs.drain(0..) {
if let syn::Meta::NameValue(ref nv) = a.meta {
if nv.path.is_ident(name) {
if let Expr::Lit(ExprLit {
lit: syn::Lit::Str(ref litstr),
..
}) = nv.value
{
eprintln!("#[default = \"arg\"] is deprecated. Use #[extendr(default = \"arg\")] instead.");
res = Some(litstr.value());
continue;
}
}
}
new_attrs.push(a);
}
*attrs = new_attrs;
res
}
fn sanitize_identifier(ident: Ident) -> Ident {
static PREFIX: &str = "r#";
let (ident, span) = (ident.to_string(), ident.span());
let ident = match ident.strip_prefix(PREFIX) {
Some(ident) => ident.into(),
None => ident,
};
Ident::new(&ident, span)
}