Skip to main content

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 std::{collections::HashMap, sync::Mutex};
29use syn::{parse_quote, punctuated::Punctuated, Expr, ExprLit, FnArg, ItemFn, Token, Type};
30
31use crate::extendr_options::ExtendrOptions;
32
33pub const META_PREFIX: &str = "meta__";
34pub const WRAP_PREFIX: &str = "wrap__";
35
36lazy_static::lazy_static! {
37    static ref STRUCT_DOCS: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
38}
39
40/// Called by the struct‐level [`extendr`]-macro to register docstrings.
41///
42///
43/// [`extendr`]: macro@crate::extendr
44pub fn register_struct_doc(name: &str, doc: &str) {
45    STRUCT_DOCS
46        .lock()
47        .unwrap()
48        .insert(name.to_string(), doc.to_string());
49}
50
51/// Retrieve the struct‐level docs (or empty if none).
52pub fn get_struct_doc(name: &str) -> String {
53    STRUCT_DOCS
54        .lock()
55        .unwrap()
56        .get(name)
57        .cloned()
58        .unwrap_or_default()
59}
60
61// Generate wrappers for a specific function.
62pub(crate) fn make_function_wrappers(
63    opts: &ExtendrOptions,
64    wrappers: &mut Vec<ItemFn>,
65    prefix: &str,
66    attrs: &[syn::Attribute],
67    sig: &mut syn::Signature,
68    self_ty: Option<&syn::Type>,
69) -> syn::Result<()> {
70    let rust_name = sig.ident.clone();
71
72    let r_name_str = if let Some(r_name) = opts.r_name.as_ref() {
73        r_name.clone()
74    } else {
75        sig.ident.to_string()
76    };
77
78    let mod_name = if let Some(mod_name) = opts.mod_name.as_ref() {
79        format_ident!("{}", mod_name)
80    } else {
81        sig.ident.clone()
82    };
83
84    let mod_name = sanitize_identifier(mod_name);
85    let wrap_name = format_ident!("{}{}{}", WRAP_PREFIX, prefix, mod_name);
86    let meta_name = format_ident!("{}{}{}", META_PREFIX, prefix, mod_name);
87
88    let rust_name_str = format!("{}", rust_name);
89    let c_name_str = format!("{}", mod_name);
90    let wrap_name_str = format!("{}", wrap_name);
91    let doc_string = get_doc_string(attrs);
92    let return_type_string = get_return_type(sig);
93    let opts_invisible = match opts.invisible {
94        Some(true) => quote!(Some(true)),
95        Some(false) => quote!(Some(false)),
96        None => quote!(None),
97    };
98
99    let inputs = &mut sig.inputs;
100    let has_self = matches!(inputs.iter().next(), Some(FnArg::Receiver(_)));
101
102    let call_name = if has_self {
103        let is_mut = match inputs.iter().next() {
104            Some(FnArg::Receiver(ref receiver)) => receiver.mutability.is_some(),
105            _ => false,
106        };
107        if is_mut {
108            // eg. Person::name(&mut self)
109            quote! { extendr_api::unwrap_or_throw_error(
110                <&mut #self_ty>::try_from(&mut _self_robj)
111            ).#rust_name }
112        } else {
113            // eg. Person::name(&self)
114            quote! { extendr_api::unwrap_or_throw_error(
115                <&#self_ty>::try_from(&_self_robj)
116            ).#rust_name }
117        }
118    } else if let Some(ref self_ty) = &self_ty {
119        // eg. Person::new()
120        quote! { <#self_ty>::#rust_name }
121    } else {
122        // eg. aux_func()
123        quote! { #rust_name }
124    };
125
126    // arguments for the wrapper with type being `SEXP`
127    let formal_args = inputs
128        .iter()
129        .map(|input| translate_formal(input, self_ty))
130        .collect::<syn::Result<Punctuated<FnArg, Token![,]>>>()?;
131
132    // extract the names of the arguments only (`mut` are ignored in `formal_args` already)
133    let sexp_args = formal_args
134        .clone()
135        .into_iter()
136        .map(|x| match x {
137            // the wrapper doesn't use `self` arguments
138            FnArg::Receiver(_) => unreachable!(),
139            FnArg::Typed(ref typed) => match typed.pat.as_ref() {
140                syn::Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),
141                _ => unreachable!(),
142            },
143        })
144        .collect::<Vec<Ident>>();
145
146    // arguments from R (`SEXP`s) are converted to `Robj`
147    let convert_args: Vec<syn::Stmt> = inputs
148        .iter()
149        .map(translate_to_robj)
150        .collect::<syn::Result<Vec<syn::Stmt>>>()?;
151
152    let actual_args: Punctuated<Expr, Token![,]> =
153        inputs.iter().filter_map(translate_actual).collect();
154
155    let meta_args: Vec<Expr> = inputs
156        .iter_mut()
157        .map(|input| translate_meta_arg(input, self_ty))
158        .collect::<syn::Result<Vec<Expr>>>()?;
159
160    // Generate wrappers for rust functions to be called from R.
161    // Example:
162    // ```
163    // #[no_mangle]
164    // #[allow(non_snake_case)]
165    // pub extern "C" fn wrap__hello() -> extendr_api::SEXP {
166    //     unsafe {
167    //         use extendr_api::FromRobj;
168    //         extendr_api::Robj::from(hello()).get()
169    //     }
170    // }
171    // ```
172    let rng_start = if opts.use_rng {
173        {
174            quote!(single_threaded(|| unsafe {
175                extendr_api::GetRNGstate();
176            });)
177        }
178    } else {
179        Default::default()
180    };
181    let rng_end = if opts.use_rng {
182        {
183            quote!(single_threaded(|| unsafe {
184                extendr_api::PutRNGstate();
185            });)
186        }
187    } else {
188        Default::default()
189    };
190
191    // figure out if
192    // -> &Self
193    // -> &mut Self
194    // Or if instead of `Self` the type name is used directly
195    // -> &ImplType / &mut ImplType
196    let return_is_ref_self = {
197        match sig.output {
198            // matches -> () or no-return type
199            syn::ReturnType::Default => false,
200            // ignoring the `-> Self` or `-> ImplType`, as that is not a Reference-type
201            // matches -> &T or &mut T
202            syn::ReturnType::Type(_, ref return_type) => match return_type.as_ref() {
203                Type::Reference(ref reference_type) => {
204                    // checks if T is Self or explicit impl type name
205                    if let Type::Path(path) = reference_type.elem.as_ref() {
206                        let is_typename_impl_type = self_ty
207                            .map(|x| x == reference_type.elem.as_ref())
208                            .unwrap_or(false);
209                        path.path.is_ident("Self") || is_typename_impl_type
210                    } else {
211                        false
212                    }
213                }
214                _ => false,
215            },
216        }
217    };
218
219    let return_type_conversion = if return_is_ref_self {
220        // instead of converting &Self / &mut Self, pass on the passed
221        // ExternalPtr<Self>
222        quote!(
223            let return_ref_to_self = #call_name(#actual_args);
224
225            #(
226            let arg_ref = extendr_api::R_ExternalPtrAddr(#sexp_args)
227                .cast::<Box<dyn std::any::Any>>()
228                .as_ref()
229                .unwrap()
230                .downcast_ref::<#self_ty>()
231                .unwrap();
232            if std::ptr::addr_eq(
233                arg_ref,
234                std::ptr::from_ref(return_ref_to_self)) {
235                    return Ok(extendr_api::Robj::from_sexp(#sexp_args))
236                }
237            )*
238            Err(Error::ExpectedExternalPtrReference.into())
239        )
240    } else {
241        quote!(Ok(extendr_api::Robj::from(#call_name(#actual_args))))
242    };
243
244    // TODO: the unsafe in here is unnecessary
245    wrappers.push(parse_quote!(
246        #[no_mangle]
247        #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
248        pub extern "C" fn #wrap_name(#formal_args) -> extendr_api::SEXP {
249            use extendr_api::robj::*;
250
251            // pull RNG state before evaluation
252            #rng_start
253
254            let wrap_result_state: std::result::Result<
255                std::result::Result<extendr_api::Robj, Box<dyn std::error::Error>>,
256                Box<dyn std::any::Any + Send>
257            > = unsafe {
258                    std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || -> std::result::Result<extendr_api::Robj, Box<dyn std::error::Error>> {
259                        #(#convert_args)*
260                        #return_type_conversion
261                    }))
262                };
263
264            // return RNG state back to r after evaluation
265            #rng_end
266
267            // any obj created in above unsafe scope, which are not moved into wrap_result_state are now dropped
268            match wrap_result_state {
269                Ok(Ok(zz)) => {
270                    return unsafe { zz.get() };
271                }
272                // any conversion error bubbled from #actual_args conversions of incoming args from R.
273                Ok(Err(conversion_err)) => {
274                    let err_string = conversion_err.to_string();
275                    drop(conversion_err); // try_from=true errors contain Robj, this must be dropped to not leak
276                    extendr_api::throw_r_error(&err_string);
277                }
278                // any panic (induced by user func code or if user func yields a Result-Err as return value)
279                Err(unwind_err) => {
280                    let panic_msg = if let Some(s) = unwind_err.downcast_ref::<&str>() {
281                        (*s).to_string()
282                    } else if let Some(s) = unwind_err.downcast_ref::<String>() {
283                        s.clone()
284                    } else {
285                        format!("User function panicked: {}", #r_name_str)
286                    };
287
288                    extendr_api::throw_r_error(&panic_msg);
289                }
290            }
291        }
292    ));
293
294    // Generate a function to push the metadata for a function.
295    wrappers.push(parse_quote!(
296        #[allow(non_snake_case)]
297        fn #meta_name(metadata: &mut Vec<extendr_api::metadata::Func>) {
298            let mut args = vec![#(#meta_args,)*];
299
300            metadata.push(extendr_api::metadata::Func {
301                doc: #doc_string,
302                rust_name: #rust_name_str,
303                r_name: #r_name_str,
304                c_name: #wrap_name_str,
305                mod_name: #c_name_str,
306                args: args,
307                return_type: #return_type_string,
308                func_ptr: #wrap_name as * const u8,
309                hidden: false,
310                invisible: #opts_invisible,
311            })
312        }
313    ));
314
315    Ok(())
316}
317
318// Extract doc strings from attributes.
319pub fn get_doc_string(attrs: &[syn::Attribute]) -> String {
320    let mut res = String::new();
321    for attr in attrs {
322        if !attr.path().is_ident("doc") {
323            continue;
324        }
325
326        if let syn::Meta::NameValue(ref nv) = attr.meta {
327            if let Expr::Lit(ExprLit {
328                lit: syn::Lit::Str(ref litstr),
329                ..
330            }) = nv.value
331            {
332                if !res.is_empty() {
333                    res.push('\n');
334                }
335                res.push_str(&litstr.value());
336            }
337        }
338    }
339    res
340}
341
342pub fn get_return_type(sig: &syn::Signature) -> String {
343    match &sig.output {
344        syn::ReturnType::Default => "()".into(),
345        syn::ReturnType::Type(_, ref rettype) => type_name(rettype),
346    }
347}
348
349pub fn mangled_type_name(type_: &Type) -> String {
350    let src = quote!( #type_ ).to_string();
351    let mut res = String::new();
352    for c in src.chars() {
353        if c != ' ' {
354            if c.is_alphanumeric() {
355                res.push(c)
356            } else {
357                let f = format!("_{:02x}", c as u32);
358                res.push_str(&f);
359            }
360        }
361    }
362    res
363}
364
365/// Return a simplified type name that will be meaningful to R. Defaults to a digest.
366// For example:
367// & Fred -> Fred
368// * Fred -> Fred
369// && Fred -> Fred
370// Fred<'a> -> Fred
371// &[i32] -> _hex_hex_hex_hex
372//
373pub fn type_name(type_: &Type) -> String {
374    match type_ {
375        Type::Path(syn::TypePath { path, .. }) => {
376            if let Some(ident) = path.get_ident() {
377                ident.to_string()
378            } else if path.segments.len() == 1 {
379                let seg = path.segments.clone().into_iter().next().unwrap();
380                seg.ident.to_string()
381            } else {
382                mangled_type_name(type_)
383            }
384        }
385        Type::Group(syn::TypeGroup { elem, .. }) => type_name(elem),
386        Type::Reference(syn::TypeReference { elem, .. }) => type_name(elem),
387        Type::Paren(syn::TypeParen { elem, .. }) => type_name(elem),
388        Type::Ptr(syn::TypePtr { elem, .. }) => type_name(elem),
389        _ => mangled_type_name(type_),
390    }
391}
392
393// Generate a list of arguments for the wrapper. All arguments are SEXP for .Call in R.
394pub fn translate_formal(input: &FnArg, self_ty: Option<&syn::Type>) -> syn::Result<FnArg> {
395    match input {
396        // function argument.
397        FnArg::Typed(ref pattype) => {
398            let pat = pattype.pat.as_ref();
399            // ensure that `mut` in args are ignored in the wrapper
400            let pat_ident = translate_only_alias(pat)?;
401            Ok(parse_quote! { #pat_ident: extendr_api::SEXP })
402        }
403        // &self / &mut self
404        FnArg::Receiver(ref receiver) => {
405            if !receiver.attrs.is_empty() || receiver.reference.is_none() {
406                return Err(syn::Error::new_spanned(
407                    input,
408                    "expected &self or &mut self",
409                ));
410            }
411            if self_ty.is_none() {
412                return Err(syn::Error::new_spanned(
413                    input,"found &self in non-impl function - have you missed the #[extendr] before the impl?"
414                ));
415            }
416            Ok(parse_quote! { _self : extendr_api::SEXP })
417        }
418    }
419}
420
421/// Returns only the alias from a function argument.
422///
423/// For example `mut x: Vec<i32>`, the alias is `x`, but the `mut` would still
424/// be present if only the `Ident` of `PatType` was used.
425fn translate_only_alias(pat: &syn::Pat) -> Result<&Ident, syn::Error> {
426    Ok(match pat {
427        syn::Pat::Ident(ref pat_ident) => &pat_ident.ident,
428        _ => {
429            return Err(syn::Error::new_spanned(
430                pat,
431                "failed to translate name of argument",
432            ));
433        }
434    })
435}
436
437// Generate code to make a metadata::Arg.
438fn translate_meta_arg(input: &mut FnArg, self_ty: Option<&syn::Type>) -> syn::Result<Expr> {
439    match input {
440        // function argument.
441        FnArg::Typed(ref mut pattype) => {
442            let pat = pattype.pat.as_ref();
443            let ty = pattype.ty.as_ref();
444            // here the argument name is extracted, without the `mut` keyword,
445            // ensuring the generated r-wrappers, can use these argument names
446            let pat_ident = translate_only_alias(pat)?;
447            let name_string = quote! { #pat_ident }.to_string();
448            let type_string = type_name(ty);
449            let is_option = matches!(ty, Type::Path(p) if p.path.segments.last().is_some_and(|seg| seg.ident == "Option"));
450            let default = if let Some(default) = get_defaults(&mut pattype.attrs) {
451                quote!(Some(#default))
452            } else if let Some(default) = get_named_lit(&mut pattype.attrs, "default") {
453                quote!(Some(#default))
454            } else if is_option {
455                quote!(Some("NULL"))
456            } else {
457                quote!(None)
458            };
459            Ok(parse_quote! {
460                extendr_api::metadata::Arg {
461                    name: #name_string,
462                    arg_type: #type_string,
463                    default: #default
464                }
465            })
466        }
467        // &self
468        FnArg::Receiver(ref receiver) => {
469            if !receiver.attrs.is_empty() || receiver.reference.is_none() {
470                return Err(syn::Error::new_spanned(
471                    input,
472                    "expected &self or &mut self",
473                ));
474            }
475            if self_ty.is_none() {
476                return Err(syn::Error::new_spanned(
477                    input,
478            "found &self in non-impl function - have you missed the #[extendr] before the impl?"
479        )
480    );
481            }
482            let type_string = type_name(self_ty.unwrap());
483            Ok(parse_quote! {
484                extendr_api::metadata::Arg {
485                    name: "self",
486                    arg_type: #type_string,
487                    default: None
488                }
489            })
490        }
491    }
492}
493
494// Get defaults from #[extendr(default = "value")] attribute.
495fn get_defaults(attrs: &mut Vec<syn::Attribute>) -> Option<String> {
496    use syn::Lit;
497
498    let mut new_attrs = Vec::new();
499    let mut res = None;
500
501    for i in attrs.drain(0..) {
502        if let syn::Meta::List(ref meta_list) = i.meta {
503            if meta_list.path.is_ident("extendr") {
504                let mut default_value = None;
505                let mut theres_default = false;
506
507                let parse_result = meta_list.parse_nested_meta(|meta| {
508                    if meta.path.is_ident("default") {
509                        theres_default = true;
510                        let value = meta.value()?;
511                        if let Ok(Lit::Str(litstr)) = value.parse() {
512                            default_value = Some(litstr.value());
513                        }
514                    }
515                    Ok(())
516                });
517
518                if parse_result.is_ok() && theres_default {
519                    res = default_value;
520                    continue;
521                }
522            }
523        }
524
525        new_attrs.push(i);
526    }
527    *attrs = new_attrs;
528    res
529}
530
531fn type_needs_mut_robj(ty: &Type) -> bool {
532    match ty {
533        Type::Reference(reference) => {
534            reference.mutability.is_some() || type_needs_mut_robj(&reference.elem)
535        }
536        Type::Path(path) => path.path.segments.iter().any(|seg| match &seg.arguments {
537            syn::PathArguments::AngleBracketed(args) => args.args.iter().any(|arg| {
538                if let syn::GenericArgument::Type(inner_ty) = arg {
539                    type_needs_mut_robj(inner_ty)
540                } else {
541                    false
542                }
543            }),
544            _ => false,
545        }),
546        Type::Tuple(tuple) => tuple.elems.iter().any(type_needs_mut_robj),
547        _ => false,
548    }
549}
550
551/// Convert `SEXP` arguments into `Robj`.
552/// This maintains the lifetime of references.
553///
554/// These conversions are from R into Rust
555fn translate_to_robj(input: &FnArg) -> syn::Result<syn::Stmt> {
556    match input {
557        FnArg::Typed(ref pattype) => {
558            let pat = &pattype.pat.as_ref();
559            if let syn::Pat::Ident(ref ident) = pat {
560                let varname = format_ident!("_{}_robj", ident.ident);
561                let ident = &ident.ident;
562                let mut_token = type_needs_mut_robj(&pattype.ty);
563                // TODO: these do not need protection, as they come from R
564                if mut_token {
565                    Ok(
566                        parse_quote! { let mut #varname = extendr_api::robj::Robj::from_sexp(#ident); },
567                    )
568                } else {
569                    Ok(parse_quote! { let #varname = extendr_api::robj::Robj::from_sexp(#ident); })
570                }
571            } else {
572                Err(syn::Error::new_spanned(
573                    input,
574                    "expect identifier as arg name",
575                ))
576            }
577        }
578        FnArg::Receiver(_) => {
579            // this is `mut`, in case of a mutable reference
580            Ok(parse_quote! { let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self); })
581        }
582    }
583}
584
585// Generate actual argument list for the call (ie. a list of conversions).
586fn translate_actual(input: &FnArg) -> Option<Expr> {
587    match input {
588        FnArg::Typed(ref pattype) => {
589            let pat = &pattype.pat.as_ref();
590            if let syn::Pat::Ident(ref ident) = pat {
591                let varname = format_ident!("_{}_robj", ident.ident);
592                let arg_name = ident.ident.to_string();
593                let robj_to_try: Expr = if type_needs_mut_robj(&pattype.ty) {
594                    parse_quote! { &mut #varname }
595                } else {
596                    parse_quote! { &#varname }
597                };
598                Some(parse_quote! {
599                    (#robj_to_try)
600                        .try_into()
601                        .map_err(|error| extendr_api::error::Error::Other(format!(
602                            "failed to convert argument `{}` from R: {}",
603                            #arg_name,
604                            error
605                    )))?
606                })
607            } else {
608                None
609            }
610        }
611        FnArg::Receiver(_) => {
612            // Do not use self explicitly as an actual arg.
613            None
614        }
615    }
616}
617
618// Get a single named literal from a list of attributes.
619// eg. #[default="xyz"]
620// Remove the attribute from the list.
621fn get_named_lit(attrs: &mut Vec<syn::Attribute>, name: &str) -> Option<String> {
622    let mut new_attrs = Vec::new();
623    let mut res = None;
624    for a in attrs.drain(0..) {
625        if let syn::Meta::NameValue(ref nv) = a.meta {
626            if nv.path.is_ident(name) {
627                if let Expr::Lit(ExprLit {
628                    lit: syn::Lit::Str(ref litstr),
629                    ..
630                }) = nv.value
631                {
632                    eprintln!("#[default = \"arg\"] is deprecated. Use #[extendr(default = \"arg\")] instead.");
633                    res = Some(litstr.value());
634                    continue;
635                }
636            }
637        }
638
639        new_attrs.push(a);
640    }
641    *attrs = new_attrs;
642    res
643}
644
645// Remove the raw identifier prefix (`r#`) from an [`Ident`]
646// If the `Ident` does not start with the prefix, it is returned as is.
647fn sanitize_identifier(ident: Ident) -> Ident {
648    static PREFIX: &str = "r#";
649    let (ident, span) = (ident.to_string(), ident.span());
650    let ident = match ident.strip_prefix(PREFIX) {
651        Some(ident) => ident.into(),
652        None => ident,
653    };
654
655    Ident::new(&ident, span)
656}