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