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
128    // Generate wrappers for rust functions to be called from R.
129    // Example:
130    // ```
131    // #[no_mangle]
132    // #[allow(non_snake_case)]
133    // pub extern "C" fn wrap__hello() -> extendr_api::SEXP {
134    //     unsafe {
135    //         use extendr_api::FromRobj;
136    //         extendr_api::Robj::from(hello()).get()
137    //     }
138    // }
139    // ```
140    let rng_start = opts
141        .use_rng
142        .then(|| {
143            quote!(single_threaded(|| unsafe {
144                extendr_api::GetRNGstate();
145            });)
146        })
147        .unwrap_or_default();
148    let rng_end = opts
149        .use_rng
150        .then(|| {
151            quote!(single_threaded(|| unsafe {
152                extendr_api::PutRNGstate();
153            });)
154        })
155        .unwrap_or_default();
156
157    // figure out if
158    // -> &Self
159    // -> &mut Self
160    // Or if instead of `Self` the type name is used directly
161    // -> &ImplType / &mut ImplType
162    let return_is_ref_self = {
163        match sig.output {
164            // matches -> () or no-return type
165            syn::ReturnType::Default => false,
166            // ignoring the `-> Self` or `-> ImplType`, as that is not a Reference-type
167            // matches -> &T or &mut T
168            syn::ReturnType::Type(_, ref return_type) => match return_type.as_ref() {
169                Type::Reference(ref reference_type) => {
170                    // checks if T is Self or explicit impl type name
171                    if let Type::Path(path) = reference_type.elem.as_ref() {
172                        let is_typename_impl_type = self_ty
173                            .map(|x| x == reference_type.elem.as_ref())
174                            .unwrap_or(false);
175                        path.path.is_ident("Self") || is_typename_impl_type
176                    } else {
177                        false
178                    }
179                }
180                _ => false,
181            },
182        }
183    };
184
185    let return_type_conversion = if return_is_ref_self {
186        // instead of converting &Self / &mut Self, pass on the passed
187        // ExternalPtr<Self>
188        quote!(
189            let return_ref_to_self = #call_name(#actual_args);
190
191            #(
192            let arg_ref = extendr_api::R_ExternalPtrAddr(#sexp_args)
193                .cast::<Box<dyn std::any::Any>>()
194                .as_ref()
195                .unwrap()
196                .downcast_ref::<#self_ty>()
197                .unwrap();
198            if std::ptr::addr_eq(
199                arg_ref,
200                std::ptr::from_ref(return_ref_to_self)) {
201                    return Ok(extendr_api::Robj::from_sexp(#sexp_args))
202                }
203            )*
204            Err(Error::ExpectedExternalPtrReference)
205        )
206    } else {
207        quote!(Ok(extendr_api::Robj::from(#call_name(#actual_args))))
208    };
209
210    // TODO: the unsafe in here is unnecessary
211    wrappers.push(parse_quote!(
212        #[no_mangle]
213        #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
214        pub extern "C" fn #wrap_name(#formal_args) -> extendr_api::SEXP {
215            use extendr_api::robj::*;
216
217            // pull RNG state before evaluation
218            #rng_start
219
220            let wrap_result_state: std::result::Result<
221                std::result::Result<extendr_api::Robj, extendr_api::Error>,
222                Box<dyn std::any::Any + Send>
223            > = unsafe {
224                std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || -> std::result::Result<extendr_api::Robj, extendr_api::Error> {
225                    #(#convert_args)*
226                    #return_type_conversion
227                }))
228            };
229
230            // return RNG state back to r after evaluation
231            #rng_end
232
233            // any obj created in above unsafe scope, which are not moved into wrap_result_state are now dropped
234            match wrap_result_state {
235                Ok(Ok(zz)) => {
236                    return unsafe { zz.get() };
237                }
238                // any conversion error bubbled from #actual_args conversions of incoming args from R.
239                Ok(Err(conversion_err)) => {
240                    let err_string = conversion_err.to_string();
241                    drop(conversion_err); // try_from=true errors contain Robj, this must be dropped to not leak
242                    extendr_api::throw_r_error(&err_string);
243                }
244                // any panic (induced by user func code or if user func yields a Result-Err as return value)
245                Err(unwind_err) => {
246                    drop(unwind_err); //did not notice any difference if dropped or not.
247                    // It should be possible to downcast the unwind_err Any type to the error
248                    // included in panic. The advantage would be the panic cause could be included
249                    // in the R terminal error message and not only via std-err.
250                    // but it should be handled in a separate function and not in-lined here.
251                    let err_string = format!("User function panicked: {}", #r_name_str);
252                    // cannot use throw_r_error here for some reason.
253                    // handle_panic() exports err string differently than throw_r_error.
254                    extendr_api::handle_panic(err_string.as_str(), || panic!());
255                }
256            }
257            unreachable!("internal extendr error, this should never happen.")
258        }
259    ));
260
261    // Generate a function to push the metadata for a function.
262    wrappers.push(parse_quote!(
263        #[allow(non_snake_case)]
264        fn #meta_name(metadata: &mut Vec<extendr_api::metadata::Func>) {
265            let args = vec![
266                #( #meta_args, )*
267            ];
268
269            metadata.push(extendr_api::metadata::Func {
270                doc: #doc_string,
271                rust_name: #rust_name_str,
272                r_name: #r_name_str,
273                mod_name: #c_name_str,
274                args: args,
275                return_type: #return_type_string,
276                func_ptr: #wrap_name as * const u8,
277                hidden: false,
278            })
279        }
280    ));
281
282    Ok(())
283}
284
285// Extract doc strings from attributes.
286pub fn get_doc_string(attrs: &[syn::Attribute]) -> String {
287    let mut res = String::new();
288    for attr in attrs {
289        if !attr.path().is_ident("doc") {
290            continue;
291        }
292
293        if let syn::Meta::NameValue(ref nv) = attr.meta {
294            if let Expr::Lit(ExprLit {
295                lit: syn::Lit::Str(ref litstr),
296                ..
297            }) = nv.value
298            {
299                if !res.is_empty() {
300                    res.push('\n');
301                }
302                res.push_str(&litstr.value());
303            }
304        }
305    }
306    res
307}
308
309pub fn get_return_type(sig: &syn::Signature) -> String {
310    match &sig.output {
311        syn::ReturnType::Default => "()".into(),
312        syn::ReturnType::Type(_, ref rettype) => type_name(rettype),
313    }
314}
315
316pub fn mangled_type_name(type_: &Type) -> String {
317    let src = quote!( #type_ ).to_string();
318    let mut res = String::new();
319    for c in src.chars() {
320        if c != ' ' {
321            if c.is_alphanumeric() {
322                res.push(c)
323            } else {
324                let f = format!("_{:02x}", c as u32);
325                res.push_str(&f);
326            }
327        }
328    }
329    res
330}
331
332/// Return a simplified type name that will be meaningful to R. Defaults to a digest.
333// For example:
334// & Fred -> Fred
335// * Fred -> Fred
336// && Fred -> Fred
337// Fred<'a> -> Fred
338// &[i32] -> _hex_hex_hex_hex
339//
340pub fn type_name(type_: &Type) -> String {
341    match type_ {
342        Type::Path(syn::TypePath { path, .. }) => {
343            if let Some(ident) = path.get_ident() {
344                ident.to_string()
345            } else if path.segments.len() == 1 {
346                let seg = path.segments.clone().into_iter().next().unwrap();
347                seg.ident.to_string()
348            } else {
349                mangled_type_name(type_)
350            }
351        }
352        Type::Group(syn::TypeGroup { elem, .. }) => type_name(elem),
353        Type::Reference(syn::TypeReference { elem, .. }) => type_name(elem),
354        Type::Paren(syn::TypeParen { elem, .. }) => type_name(elem),
355        Type::Ptr(syn::TypePtr { elem, .. }) => type_name(elem),
356        _ => mangled_type_name(type_),
357    }
358}
359
360// Generate a list of arguments for the wrapper. All arguments are SEXP for .Call in R.
361pub fn translate_formal(input: &FnArg, self_ty: Option<&syn::Type>) -> syn::Result<FnArg> {
362    match input {
363        // function argument.
364        FnArg::Typed(ref pattype) => {
365            let pat = pattype.pat.as_ref();
366            // ensure that `mut` in args are ignored in the wrapper
367            let pat_ident = translate_only_alias(pat)?;
368            Ok(parse_quote! { #pat_ident: extendr_api::SEXP })
369        }
370        // &self / &mut self
371        FnArg::Receiver(ref receiver) => {
372            if !receiver.attrs.is_empty() || receiver.reference.is_none() {
373                return Err(syn::Error::new_spanned(
374                    input,
375                    "expected &self or &mut self",
376                ));
377            }
378            if self_ty.is_none() {
379                return Err(syn::Error::new_spanned(
380                    input,"found &self in non-impl function - have you missed the #[extendr] before the impl?"
381                ));
382            }
383            Ok(parse_quote! { _self : extendr_api::SEXP })
384        }
385    }
386}
387
388/// Returns only the alias from a function argument.
389///
390/// For example `mut x: Vec<i32>`, the alias is `x`, but the `mut` would still
391/// be present if only the `Ident` of `PatType` was used.
392fn translate_only_alias(pat: &syn::Pat) -> Result<&Ident, syn::Error> {
393    Ok(match pat {
394        syn::Pat::Ident(ref pat_ident) => &pat_ident.ident,
395        _ => {
396            return Err(syn::Error::new_spanned(
397                pat,
398                "failed to translate name of argument",
399            ));
400        }
401    })
402}
403
404// Generate code to make a metadata::Arg.
405fn translate_meta_arg(input: &mut FnArg, self_ty: Option<&syn::Type>) -> syn::Result<Expr> {
406    match input {
407        // function argument.
408        FnArg::Typed(ref mut pattype) => {
409            let pat = pattype.pat.as_ref();
410            let ty = pattype.ty.as_ref();
411            // here the argument name is extracted, without the `mut` keyword,
412            // ensuring the generated r-wrappers, can use these argument names
413            let pat_ident = translate_only_alias(pat)?;
414            let name_string = quote! { #pat_ident }.to_string();
415            let type_string = type_name(ty);
416            let default = if let Some(default) = get_named_lit(&mut pattype.attrs, "default") {
417                quote!(Some(#default))
418            } else {
419                quote!(None)
420            };
421            Ok(parse_quote! {
422                extendr_api::metadata::Arg {
423                    name: #name_string,
424                    arg_type: #type_string,
425                    default: #default
426                }
427            })
428        }
429        // &self
430        FnArg::Receiver(ref receiver) => {
431            if !receiver.attrs.is_empty() || receiver.reference.is_none() {
432                return Err(syn::Error::new_spanned(
433                    input,
434                    "expected &self or &mut self",
435                ));
436            }
437            if self_ty.is_none() {
438                return Err(syn::Error::new_spanned(
439                    input,
440            "found &self in non-impl function - have you missed the #[extendr] before the impl?"
441        )
442    );
443            }
444            let type_string = type_name(self_ty.unwrap());
445            Ok(parse_quote! {
446                extendr_api::metadata::Arg {
447                    name: "self",
448                    arg_type: #type_string,
449                    default: None
450                }
451            })
452        }
453    }
454}
455
456/// Convert `SEXP` arguments into `Robj`.
457/// This maintains the lifetime of references.
458///
459/// These conversions are from R into Rust
460fn translate_to_robj(input: &FnArg) -> syn::Result<syn::Stmt> {
461    match input {
462        FnArg::Typed(ref pattype) => {
463            let pat = &pattype.pat.as_ref();
464            if let syn::Pat::Ident(ref ident) = pat {
465                let varname = format_ident!("_{}_robj", ident.ident);
466                let ident = &ident.ident;
467                // TODO: these do not need protection, as they come from R
468                Ok(parse_quote! { let #varname = extendr_api::robj::Robj::from_sexp(#ident); })
469            } else {
470                Err(syn::Error::new_spanned(
471                    input,
472                    "expect identifier as arg name",
473                ))
474            }
475        }
476        FnArg::Receiver(_) => {
477            // this is `mut`, in case of a mutable reference
478            Ok(parse_quote! { let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self); })
479        }
480    }
481}
482
483// Generate actual argument list for the call (ie. a list of conversions).
484fn translate_actual(input: &FnArg) -> Option<Expr> {
485    match input {
486        FnArg::Typed(ref pattype) => {
487            let pat = &pattype.pat.as_ref();
488            if let syn::Pat::Ident(ref ident) = pat {
489                let varname = format_ident!("_{}_robj", ident.ident);
490                Some(parse_quote! {
491                    #varname.try_into()?
492                })
493            } else {
494                None
495            }
496        }
497        FnArg::Receiver(_) => {
498            // Do not use self explicitly as an actual arg.
499            None
500        }
501    }
502}
503
504// Get a single named literal from a list of attributes.
505// eg. #[default="xyz"]
506// Remove the attribute from the list.
507fn get_named_lit(attrs: &mut Vec<syn::Attribute>, name: &str) -> Option<String> {
508    let mut new_attrs = Vec::new();
509    let mut res = None;
510    for a in attrs.drain(0..) {
511        if let syn::Meta::NameValue(ref nv) = a.meta {
512            if nv.path.is_ident(name) {
513                if let Expr::Lit(ExprLit {
514                    lit: syn::Lit::Str(ref litstr),
515                    ..
516                }) = nv.value
517                {
518                    res = Some(litstr.value());
519                    continue;
520                }
521            }
522        }
523
524        new_attrs.push(a);
525    }
526    *attrs = new_attrs;
527    res
528}
529
530// Remove the raw identifier prefix (`r#`) from an [`Ident`]
531// If the `Ident` does not start with the prefix, it is returned as is.
532fn sanitize_identifier(ident: Ident) -> Ident {
533    static PREFIX: &str = "r#";
534    let (ident, span) = (ident.to_string(), ident.span());
535    let ident = match ident.strip_prefix(PREFIX) {
536        Some(ident) => ident.into(),
537        None => ident,
538    };
539
540    Ident::new(&ident, span)
541}