extendr_macros/
wrappers.rs

1//! This is responsible for generating the C functions that act as wrappers of
2//! the exported Rust functions.
3//!
4//! extendr relies on the [`.Call`-interface](https://cran.r-project.org/doc/manuals/R-exts.html#Calling-_002eCall)
5//! In short, it is necessary the the signature of the C-function have [`SEXP`]
6//! as the type for return type, and argument types.
7//!
8//! For instance, if your function returns nothing, the return type is not
9//! allowed to be `void`, instead `SEXP` must be used, and one should return
10//! [`R_NilValue`].
11//!
12//! ## R wrappers
13//!
14//! Within R, you may call `rextendr::document()` to generate R functions,
15//! that use the `.Call`-interface, to call the wrapped Rust functions.
16//!
17//! You may also manually implement these wrappers, in order to do special
18//! type-checking, or other annotation, that could be more convenient to do
19//! on the R-side. The C-functions are named according to `"{WRAP_PREFIX}{prefix}{mod_name}"`.
20//! See [`WRAP_PREFIX`], and note that `prefix` is set specifically for methods in
21//! `extendr`-impl blocks, while for functions have no prefix.
22//!
23//! [`R_NilValue`]: https://extendr.github.io/libR-sys/libR_sys/static.R_NilValue.html
24//! [`SEXP`]: https://extendr.github.io/libR-sys/libR_sys/type.SEXP.html
25
26use proc_macro2::Ident;
27use quote::{format_ident, quote};
28use syn::{parse_quote, punctuated::Punctuated, Expr, ExprLit, FnArg, ItemFn, Token, Type};
29
30use crate::extendr_options::ExtendrOptions;
31
32pub const META_PREFIX: &str = "meta__";
33pub const WRAP_PREFIX: &str = "wrap__";
34
35// Generate wrappers for a specific function.
36pub(crate) fn make_function_wrappers(
37    opts: &ExtendrOptions,
38    wrappers: &mut Vec<ItemFn>,
39    prefix: &str,
40    attrs: &[syn::Attribute],
41    sig: &mut syn::Signature,
42    self_ty: Option<&syn::Type>,
43) -> syn::Result<()> {
44    let rust_name = sig.ident.clone();
45
46    let r_name_str = if let Some(r_name) = opts.r_name.as_ref() {
47        r_name.clone()
48    } else {
49        sig.ident.to_string()
50    };
51
52    let mod_name = if let Some(mod_name) = opts.mod_name.as_ref() {
53        format_ident!("{}", mod_name)
54    } else {
55        sig.ident.clone()
56    };
57
58    let mod_name = sanitize_identifier(mod_name);
59    let wrap_name = format_ident!("{}{}{}", WRAP_PREFIX, prefix, mod_name);
60    let meta_name = format_ident!("{}{}{}", META_PREFIX, prefix, mod_name);
61
62    let rust_name_str = format!("{}", rust_name);
63    let c_name_str = format!("{}", mod_name);
64    let doc_string = get_doc_string(attrs);
65    let return_type_string = get_return_type(sig);
66
67    let inputs = &mut sig.inputs;
68    let has_self = matches!(inputs.iter().next(), Some(FnArg::Receiver(_)));
69
70    let call_name = if has_self {
71        let is_mut = match inputs.iter().next() {
72            Some(FnArg::Receiver(ref receiver)) => receiver.mutability.is_some(),
73            _ => false,
74        };
75        if is_mut {
76            // eg. Person::name(&mut self)
77            quote! { extendr_api::unwrap_or_throw_error(
78                <&mut #self_ty>::try_from(&mut _self_robj)
79            ).#rust_name }
80        } else {
81            // eg. Person::name(&self)
82            quote! { extendr_api::unwrap_or_throw_error(
83                <&#self_ty>::try_from(&_self_robj)
84            ).#rust_name }
85        }
86    } else if let Some(ref self_ty) = &self_ty {
87        // eg. Person::new()
88        quote! { <#self_ty>::#rust_name }
89    } else {
90        // eg. aux_func()
91        quote! { #rust_name }
92    };
93
94    // arguments for the wrapper with type being `SEXP`
95    let formal_args = inputs
96        .iter()
97        .map(|input| translate_formal(input, self_ty))
98        .collect::<syn::Result<Punctuated<FnArg, Token![,]>>>()?;
99
100    // extract the names of the arguments only (`mut` are ignored in `formal_args` already)
101    let sexp_args = formal_args
102        .clone()
103        .into_iter()
104        .map(|x| match x {
105            // the wrapper doesn't use `self` arguments
106            FnArg::Receiver(_) => unreachable!(),
107            FnArg::Typed(ref typed) => match typed.pat.as_ref() {
108                syn::Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),
109                _ => unreachable!(),
110            },
111        })
112        .collect::<Vec<Ident>>();
113
114    // arguments from R (`SEXP`s) are converted to `Robj`
115    let convert_args: Vec<syn::Stmt> = inputs
116        .iter()
117        .map(translate_to_robj)
118        .collect::<syn::Result<Vec<syn::Stmt>>>()?;
119
120    let actual_args: Punctuated<Expr, Token![,]> =
121        inputs.iter().filter_map(translate_actual).collect();
122
123    let meta_args: Vec<Expr> = inputs
124        .iter_mut()
125        .map(|input| translate_meta_arg(input, self_ty))
126        .collect::<syn::Result<Vec<Expr>>>()?;
127    let len_meta_args = meta_args.len();
128
129    // Generate wrappers for rust functions to be called from R.
130    // Example:
131    // ```
132    // #[no_mangle]
133    // #[allow(non_snake_case)]
134    // pub extern "C" fn wrap__hello() -> extendr_api::SEXP {
135    //     unsafe {
136    //         use extendr_api::FromRobj;
137    //         extendr_api::Robj::from(hello()).get()
138    //     }
139    // }
140    // ```
141    let rng_start = opts
142        .use_rng
143        .then(|| {
144            quote!(single_threaded(|| unsafe {
145                extendr_api::GetRNGstate();
146            });)
147        })
148        .unwrap_or_default();
149    let rng_end = opts
150        .use_rng
151        .then(|| {
152            quote!(single_threaded(|| unsafe {
153                extendr_api::PutRNGstate();
154            });)
155        })
156        .unwrap_or_default();
157
158    // figure out if
159    // -> &Self
160    // -> &mut Self
161    // Or if instead of `Self` the type name is used directly
162    // -> &ImplType / &mut ImplType
163    let return_is_ref_self = {
164        match sig.output {
165            // matches -> () or no-return type
166            syn::ReturnType::Default => false,
167            // ignoring the `-> Self` or `-> ImplType`, as that is not a Reference-type
168            // matches -> &T or &mut T
169            syn::ReturnType::Type(_, ref return_type) => match return_type.as_ref() {
170                Type::Reference(ref reference_type) => {
171                    // checks if T is Self or explicit impl type name
172                    if let Type::Path(path) = reference_type.elem.as_ref() {
173                        let is_typename_impl_type = self_ty
174                            .map(|x| x == reference_type.elem.as_ref())
175                            .unwrap_or(false);
176                        path.path.is_ident("Self") || is_typename_impl_type
177                    } else {
178                        false
179                    }
180                }
181                _ => false,
182            },
183        }
184    };
185
186    let return_type_conversion = if return_is_ref_self {
187        // instead of converting &Self / &mut Self, pass on the passed
188        // ExternalPtr<Self>
189        quote!(
190            let return_ref_to_self = #call_name(#actual_args);
191
192            #(
193            let arg_ref = extendr_api::R_ExternalPtrAddr(#sexp_args)
194                .cast::<Box<dyn std::any::Any>>()
195                .as_ref()
196                .unwrap()
197                .downcast_ref::<#self_ty>()
198                .unwrap();
199            if std::ptr::addr_eq(
200                arg_ref,
201                std::ptr::from_ref(return_ref_to_self)) {
202                    return Ok(extendr_api::Robj::from_sexp(#sexp_args))
203                }
204            )*
205            Err(Error::ExpectedExternalPtrReference)
206        )
207    } else {
208        quote!(Ok(extendr_api::Robj::from(#call_name(#actual_args))))
209    };
210
211    // TODO: the unsafe in here is unnecessary
212    wrappers.push(parse_quote!(
213        #[no_mangle]
214        #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
215        pub extern "C" fn #wrap_name(#formal_args) -> extendr_api::SEXP {
216            use extendr_api::robj::*;
217
218            // pull RNG state before evaluation
219            #rng_start
220
221            let wrap_result_state: std::result::Result<
222                std::result::Result<extendr_api::Robj, extendr_api::Error>,
223                Box<dyn std::any::Any + Send>
224            > = unsafe {
225                std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || -> std::result::Result<extendr_api::Robj, extendr_api::Error> {
226                    #(#convert_args)*
227                    #return_type_conversion
228                }))
229            };
230
231            // return RNG state back to r after evaluation
232            #rng_end
233
234            // any obj created in above unsafe scope, which are not moved into wrap_result_state are now dropped
235            match wrap_result_state {
236                Ok(Ok(zz)) => {
237                    return unsafe { zz.get() };
238                }
239                // any conversion error bubbled from #actual_args conversions of incoming args from R.
240                Ok(Err(conversion_err)) => {
241                    let err_string = conversion_err.to_string();
242                    drop(conversion_err); // try_from=true errors contain Robj, this must be dropped to not leak
243                    extendr_api::throw_r_error(&err_string);
244                }
245                // any panic (induced by user func code or if user func yields a Result-Err as return value)
246                Err(unwind_err) => {
247                    drop(unwind_err); //did not notice any difference if dropped or not.
248                    // It should be possible to downcast the unwind_err Any type to the error
249                    // included in panic. The advantage would be the panic cause could be included
250                    // in the R terminal error message and not only via std-err.
251                    // but it should be handled in a separate function and not in-lined here.
252                    let err_string = format!("User function panicked: {}", #r_name_str);
253                    // cannot use throw_r_error here for some reason.
254                    // handle_panic() exports err string differently than throw_r_error.
255                    extendr_api::handle_panic(err_string.as_str(), || panic!());
256                }
257            }
258            unreachable!("internal extendr error, this should never happen.")
259        }
260    ));
261
262    // Generate a function to push the metadata for a function.
263    wrappers.push(parse_quote!(
264        #[allow(non_snake_case)]
265        fn #meta_name(metadata: &mut Vec<extendr_api::metadata::Func>) {
266            let mut args = Vec::with_capacity(#len_meta_args);
267            #(
268                args.push(#meta_args);
269            )*
270            let args = args;
271
272            metadata.push(extendr_api::metadata::Func {
273                doc: #doc_string,
274                rust_name: #rust_name_str,
275                r_name: #r_name_str,
276                mod_name: #c_name_str,
277                args: args,
278                return_type: #return_type_string,
279                func_ptr: #wrap_name as * const u8,
280                hidden: false,
281            })
282        }
283    ));
284
285    Ok(())
286}
287
288// Extract doc strings from attributes.
289pub fn get_doc_string(attrs: &[syn::Attribute]) -> String {
290    let mut res = String::new();
291    for attr in attrs {
292        if !attr.path().is_ident("doc") {
293            continue;
294        }
295
296        if let syn::Meta::NameValue(ref nv) = attr.meta {
297            if let Expr::Lit(ExprLit {
298                lit: syn::Lit::Str(ref litstr),
299                ..
300            }) = nv.value
301            {
302                if !res.is_empty() {
303                    res.push('\n');
304                }
305                res.push_str(&litstr.value());
306            }
307        }
308    }
309    res
310}
311
312pub fn get_return_type(sig: &syn::Signature) -> String {
313    match &sig.output {
314        syn::ReturnType::Default => "()".into(),
315        syn::ReturnType::Type(_, ref rettype) => type_name(rettype),
316    }
317}
318
319pub fn mangled_type_name(type_: &Type) -> String {
320    let src = quote!( #type_ ).to_string();
321    let mut res = String::new();
322    for c in src.chars() {
323        if c != ' ' {
324            if c.is_alphanumeric() {
325                res.push(c)
326            } else {
327                let f = format!("_{:02x}", c as u32);
328                res.push_str(&f);
329            }
330        }
331    }
332    res
333}
334
335/// Return a simplified type name that will be meaningful to R. Defaults to a digest.
336// For example:
337// & Fred -> Fred
338// * Fred -> Fred
339// && Fred -> Fred
340// Fred<'a> -> Fred
341// &[i32] -> _hex_hex_hex_hex
342//
343pub fn type_name(type_: &Type) -> String {
344    match type_ {
345        Type::Path(syn::TypePath { path, .. }) => {
346            if let Some(ident) = path.get_ident() {
347                ident.to_string()
348            } else if path.segments.len() == 1 {
349                let seg = path.segments.clone().into_iter().next().unwrap();
350                seg.ident.to_string()
351            } else {
352                mangled_type_name(type_)
353            }
354        }
355        Type::Group(syn::TypeGroup { elem, .. }) => type_name(elem),
356        Type::Reference(syn::TypeReference { elem, .. }) => type_name(elem),
357        Type::Paren(syn::TypeParen { elem, .. }) => type_name(elem),
358        Type::Ptr(syn::TypePtr { elem, .. }) => type_name(elem),
359        _ => mangled_type_name(type_),
360    }
361}
362
363// Generate a list of arguments for the wrapper. All arguments are SEXP for .Call in R.
364pub fn translate_formal(input: &FnArg, self_ty: Option<&syn::Type>) -> syn::Result<FnArg> {
365    match input {
366        // function argument.
367        FnArg::Typed(ref pattype) => {
368            let pat = pattype.pat.as_ref();
369            // ensure that `mut` in args are ignored in the wrapper
370            let pat_ident = translate_only_alias(pat)?;
371            Ok(parse_quote! { #pat_ident: extendr_api::SEXP })
372        }
373        // &self / &mut self
374        FnArg::Receiver(ref receiver) => {
375            if !receiver.attrs.is_empty() || receiver.reference.is_none() {
376                return Err(syn::Error::new_spanned(
377                    input,
378                    "expected &self or &mut self",
379                ));
380            }
381            if self_ty.is_none() {
382                return Err(syn::Error::new_spanned(
383                    input,"found &self in non-impl function - have you missed the #[extendr] before the impl?"
384                ));
385            }
386            Ok(parse_quote! { _self : extendr_api::SEXP })
387        }
388    }
389}
390
391/// Returns only the alias from a function argument.
392///
393/// For example `mut x: Vec<i32>`, the alias is `x`, but the `mut` would still
394/// be present if only the `Ident` of `PatType` was used.
395fn translate_only_alias(pat: &syn::Pat) -> Result<&Ident, syn::Error> {
396    Ok(match pat {
397        syn::Pat::Ident(ref pat_ident) => &pat_ident.ident,
398        _ => {
399            return Err(syn::Error::new_spanned(
400                pat,
401                "failed to translate name of argument",
402            ));
403        }
404    })
405}
406
407// Generate code to make a metadata::Arg.
408fn translate_meta_arg(input: &mut FnArg, self_ty: Option<&syn::Type>) -> syn::Result<Expr> {
409    match input {
410        // function argument.
411        FnArg::Typed(ref mut pattype) => {
412            let pat = pattype.pat.as_ref();
413            let ty = pattype.ty.as_ref();
414            // here the argument name is extracted, without the `mut` keyword,
415            // ensuring the generated r-wrappers, can use these argument names
416            let pat_ident = translate_only_alias(pat)?;
417            let name_string = quote! { #pat_ident }.to_string();
418            let type_string = type_name(ty);
419            let default = if let Some(default) = get_named_lit(&mut pattype.attrs, "default") {
420                quote!(Some(#default))
421            } else {
422                quote!(None)
423            };
424            Ok(parse_quote! {
425                extendr_api::metadata::Arg {
426                    name: #name_string,
427                    arg_type: #type_string,
428                    default: #default
429                }
430            })
431        }
432        // &self
433        FnArg::Receiver(ref receiver) => {
434            if !receiver.attrs.is_empty() || receiver.reference.is_none() {
435                return Err(syn::Error::new_spanned(
436                    input,
437                    "expected &self or &mut self",
438                ));
439            }
440            if self_ty.is_none() {
441                return Err(syn::Error::new_spanned(
442                    input,
443            "found &self in non-impl function - have you missed the #[extendr] before the impl?"
444        )
445    );
446            }
447            let type_string = type_name(self_ty.unwrap());
448            Ok(parse_quote! {
449                extendr_api::metadata::Arg {
450                    name: "self",
451                    arg_type: #type_string,
452                    default: None
453                }
454            })
455        }
456    }
457}
458
459/// Convert `SEXP` arguments into `Robj`.
460/// This maintains the lifetime of references.
461///
462/// These conversions are from R into Rust
463fn translate_to_robj(input: &FnArg) -> syn::Result<syn::Stmt> {
464    match input {
465        FnArg::Typed(ref pattype) => {
466            let pat = &pattype.pat.as_ref();
467            if let syn::Pat::Ident(ref ident) = pat {
468                let varname = format_ident!("_{}_robj", ident.ident);
469                let ident = &ident.ident;
470                // TODO: these do not need protection, as they come from R
471                Ok(parse_quote! { let #varname = extendr_api::robj::Robj::from_sexp(#ident); })
472            } else {
473                Err(syn::Error::new_spanned(
474                    input,
475                    "expect identifier as arg name",
476                ))
477            }
478        }
479        FnArg::Receiver(_) => {
480            // this is `mut`, in case of a mutable reference
481            Ok(parse_quote! { let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self); })
482        }
483    }
484}
485
486// Generate actual argument list for the call (ie. a list of conversions).
487fn translate_actual(input: &FnArg) -> Option<Expr> {
488    match input {
489        FnArg::Typed(ref pattype) => {
490            let pat = &pattype.pat.as_ref();
491            if let syn::Pat::Ident(ref ident) = pat {
492                let varname = format_ident!("_{}_robj", ident.ident);
493                Some(parse_quote! {
494                    #varname.try_into()?
495                })
496            } else {
497                None
498            }
499        }
500        FnArg::Receiver(_) => {
501            // Do not use self explicitly as an actual arg.
502            None
503        }
504    }
505}
506
507// Get a single named literal from a list of attributes.
508// eg. #[default="xyz"]
509// Remove the attribute from the list.
510fn get_named_lit(attrs: &mut Vec<syn::Attribute>, name: &str) -> Option<String> {
511    let mut new_attrs = Vec::new();
512    let mut res = None;
513    for a in attrs.drain(0..) {
514        if let syn::Meta::NameValue(ref nv) = a.meta {
515            if nv.path.is_ident(name) {
516                if let Expr::Lit(ExprLit {
517                    lit: syn::Lit::Str(ref litstr),
518                    ..
519                }) = nv.value
520                {
521                    res = Some(litstr.value());
522                    continue;
523                }
524            }
525        }
526
527        new_attrs.push(a);
528    }
529    *attrs = new_attrs;
530    res
531}
532
533// Remove the raw identifier prefix (`r#`) from an [`Ident`]
534// If the `Ident` does not start with the prefix, it is returned as is.
535fn sanitize_identifier(ident: Ident) -> Ident {
536    static PREFIX: &str = "r#";
537    let (ident, span) = (ident.to_string(), ident.span());
538    let ident = match ident.strip_prefix(PREFIX) {
539        Some(ident) => ident.into(),
540        None => ident,
541    };
542
543    Ident::new(&ident, span)
544}