extendr_macros/
lib.rs

1//!
2//! Macros for generating wrappers for rust functions.
3
4//
5// We can invoke the #[extendr] macro on functions or struct impls.
6//
7// eg.
8//
9// ```ignore
10// #[extendr]
11// fn hello() -> &'static str {
12//     "hello"
13// }
14// ```
15//
16// These macros add additional functions which you can see using the
17// `cargo expand` extension.
18//
19// Invoking the #[extendr_module] macro generates an entrypoint for the
20// library that will be called by R. Note that we add a postfix
21// `_extendr` to the init function because we need to forward routine
22// registration from C to Rust, and the C function will be called
23// `R_init_hello()`.
24//
25// ```ignore
26// #[no_mangle]
27// #[allow(non_snake_case)]
28// pub extern "C" fn R_init_hello_extendr(info: *mut extendr_api::DllInfo) {
29//     let mut call_methods = Vec::new();
30//     init__hello(info, &mut call_methods);
31//     unsafe { extendr_api::register_call_methods(info, call_methods.as_ref()) };
32// }
33// ```
34//
35// The module also generates the `init__` functions that provide metadata
36// to R to register the wrappers.
37//
38// ```ignore
39// #[allow(non_snake_case)]
40// fn init__hello(info: *mut extendr_api::DllInfo, call_methods: &mut Vec<extendr_api::CallMethod>) {
41//     call_methods.push(extendr_api::CallMethod {
42//         call_symbol: std::ffi::CString::new("wrap__hello").unwrap(),
43//         func_ptr: wrap__hello as *const u8,
44//         num_args: 0i32,
45//     })
46// }
47// ```
48//
49// In the case of struct impls we also generate the following:
50//
51// * Wrappers and init functions for all methods.
52// * A single init function that calls the other init functions for the methods.
53// * An input conversion from an external pointer to a reference and a move of that type.
54// * An output conversion from that type to an owned external pointer object.
55// * A finalizer for that type to free memory allocated.
56
57#[allow(non_snake_case)]
58mod R;
59mod call;
60mod dataframe;
61mod extendr_conversion;
62mod extendr_function;
63mod extendr_impl;
64mod extendr_module;
65mod extendr_options;
66mod list;
67mod list_struct;
68mod pairlist;
69mod pairs;
70mod wrappers;
71
72use proc_macro::TokenStream;
73use quote::quote;
74use syn::{parse_macro_input, Item};
75
76/// The `#[extendr]`-macro may be placed on three items
77///
78/// - `fn` for wrapped rust-functions, see [`extendr-fn`]
79/// - `impl`-blocks, see [`extendr-impl`]
80///
81/// [`extendr-fn`]: ./extendr_function/fn.extendr_function.html
82/// [`extendr-impl`]: ./extendr_impl/fn.extendr_impl.html
83///
84/// There is also [`macro@extendr_module`], which is used for defining what rust
85/// wrapped items should be visible to the surrounding R-package.
86///
87#[proc_macro_attribute]
88pub fn extendr(attr: TokenStream, item: TokenStream) -> TokenStream {
89    let mut opts = extendr_options::ExtendrOptions::default();
90
91    let extendr_opts_parser = syn::meta::parser(|meta| opts.parse(meta));
92    parse_macro_input!(attr with extendr_opts_parser);
93
94    match parse_macro_input!(item as Item) {
95        Item::Struct(str) => {
96            let struct_name = str.ident.to_string();
97            let struct_doc = crate::wrappers::get_doc_string(&str.attrs);
98            crate::wrappers::register_struct_doc(&struct_name, &struct_doc);
99            extendr_conversion::extendr_type_conversion(Item::Struct(str), &opts)
100        }
101        Item::Enum(enm) => extendr_conversion::extendr_type_conversion(Item::Enum(enm), &opts),
102        Item::Fn(func) => extendr_function::extendr_function(func, &opts),
103        Item::Impl(item_impl) => match extendr_impl::extendr_impl(item_impl, &opts) {
104            Ok(result) => result,
105            Err(e) => e.into_compile_error().into(),
106        },
107        other_item => TokenStream::from(quote! {#other_item}),
108    }
109}
110
111/// Define a module and export symbols to R
112/// Example:
113///```dont_run
114/// extendr_module! {
115///     mod name;
116///     fn my_func1;
117///     fn my_func2;
118///     impl MyTrait;
119/// }
120/// ```
121/// Outputs:
122///
123/// ```dont_run
124/// #[no_mangle]
125/// #[allow(non_snake_case)]
126/// pub extern "C" fn R_init_hello_extendr(info: *mut extendr_api::DllInfo) {
127///     let mut call_methods = Vec::new();
128///     init__hello(info, &mut call_methods);
129///     unsafe { extendr_api::register_call_methods(info, call_methods.as_ref()) };
130/// }
131/// ```
132#[proc_macro]
133pub fn extendr_module(item: TokenStream) -> TokenStream {
134    extendr_module::extendr_module(item)
135}
136
137/// Create a Pairlist R object from a list of name-value pairs.
138/// ```ignore
139///     assert_eq!(pairlist!(a=1, 2, 3), Pairlist::from_pairs(&[("a", 1), ("", 2), ("", 3)]));
140/// ```
141#[proc_macro]
142pub fn pairlist(item: TokenStream) -> TokenStream {
143    pairlist::pairlist(item)
144}
145
146/// Create a List R object from a list of name-value pairs.
147/// ```ignore
148///     assert_eq!(list!(a=1, 2, 3), List::from_pairs(&[("a", 1), ("", 2), ("", 3)]));
149/// ```
150#[proc_macro]
151pub fn list(item: TokenStream) -> TokenStream {
152    list::list(item)
153}
154
155/// Call a function or primitive defined by a text expression with arbitrary parameters.
156/// This currently works by parsing and evaluating the string in R, but will probably acquire
157/// some shortcuts for simple expressions, for example by caching symbols and constant values.
158///
159/// ```ignore
160///     assert_eq!(call!("`+`", 1, 2), r!(3));
161///     assert_eq!(call!("list", 1, 2), r!([r!(1), r!(2)]));
162/// ```
163#[proc_macro]
164pub fn call(item: TokenStream) -> TokenStream {
165    call::call(item)
166}
167
168/// Execute R code by parsing and evaluating tokens.
169///
170/// ```ignore
171///     R!("c(1, 2, 3)");
172///     R!("{{(0..3).collect_robj()}} + 1");
173///     R!(r#"
174///       print("hello")
175///     "#);
176/// ```
177#[proc_macro]
178#[allow(non_snake_case)]
179pub fn R(item: TokenStream) -> TokenStream {
180    R::R(item.into(), true).into()
181}
182
183/// Execute R code by parsing and evaluating tokens
184/// but without expanding parameters.
185///
186/// ```ignore
187/// // c.f. https://dplyr.tidyverse.org/articles/programming.html
188/// Rraw!(r#"
189/// var_summary <- function(data, var) {
190///   data %>%
191///     summarise(n = n(), min = min({{ var }}), max = max({{ var }}))
192/// }
193/// "#)
194/// ```
195#[proc_macro]
196#[allow(non_snake_case)]
197pub fn Rraw(item: TokenStream) -> TokenStream {
198    R::R(item.into(), false).into()
199}
200
201/// Derives an implementation of `TryFrom<Robj> for Struct` and `TryFrom<&Robj> for Struct` on this struct.
202///
203/// This allows any R object supporting the `$` operator (generally a list or an
204/// environment) to be converted into that struct, as long as the corresponding fields on the R object are
205/// of a compatible type to those on the Rust struct.
206///
207/// # Examples
208/// In the below example, `foo_from_list` is an instance of the `Foo` struct, that has been converted
209/// from an R list:
210/// ```ignore
211/// use extendr_api::prelude::*;
212/// use extendr_macros::TryFromRobj;
213/// # use extendr_api::test;
214/// # test!{
215///
216/// #[derive(TryFromRobj, PartialEq, Debug)]
217/// struct Foo {
218///     a: u64,
219///     b: String
220/// }
221/// let native_foo = Foo { a: 5, b: "bar".into() };
222/// let foo_from_list: Foo = R!("list(a = 5, b = 'bar')")?.try_into()?;
223/// assert_eq!(native_foo, foo_from_list);
224/// # }
225/// # Ok::<(), extendr_api::Error>(())
226/// ```
227///
228/// See [`IntoRobj`] for converting arbitrary Rust types into R type by using
229/// R's list / `List`.
230///
231#[proc_macro_derive(TryFromRobj)]
232pub fn derive_try_from_robj(item: TokenStream) -> TokenStream {
233    match list_struct::derive_try_from_robj(item) {
234        Ok(result) => result,
235        Err(e) => e.into_compile_error().into(),
236    }
237}
238
239/// Derives an implementation of `From<Struct> for Robj` and `From<&Struct> for Robj` on this struct.
240///
241/// This allows the struct to be converted to a named list in R,
242/// where the list names correspond to the field names of the Rust struct.
243///
244/// # Examples
245/// In the below example, `converted` contains an R list object with the same fields as the
246/// `Foo` struct.
247/// ```ignore
248/// use extendr_api::prelude::*;
249/// use extendr_macros::IntoList;
250///
251/// # use extendr_api::test;
252/// # test!{
253/// #[derive(IntoList)]
254/// struct Foo {
255///     a: u32,
256///     b: String
257/// }
258/// let converted: Robj = Foo {
259///     a: 5,
260///     b: String::from("bar")
261/// }.into();
262/// assert_eq!(converted, R!(r"list(a=5, b='bar')")?);
263/// # }
264/// # Ok::<(), extendr_api::Error>(())
265/// ```
266///
267/// See [`TryFromRobj`] for a `derive`-macro in the other direction, i.e.
268/// instantiation of a rust type, by an R list with fields corresponding to
269/// said type.
270///
271/// Supported field attributes
272///
273/// - `#[into_list(ignore)]` omits the field from being added to the R `list()`
274///
275/// # Details
276///
277/// Note, the `From<Struct> for Robj` behaviour is different from what is obtained by applying the standard `#[extendr]` macro
278/// to an `impl` block. The `#[extendr]` behaviour returns to R a **pointer** to Rust memory, and generates wrapper functions for calling
279/// Rust functions on that pointer. The implementation from `#[derive(IntoList)]` actually converts the Rust structure
280/// into a native R list, which allows manipulation and access to internal fields, but it's a one-way conversion,
281/// and converting it back to Rust will produce a copy of the original struct.
282#[proc_macro_derive(IntoList, attributes(into_list))]
283pub fn derive_into_list(item: TokenStream) -> TokenStream {
284    match list_struct::derive_into_list(item) {
285        Ok(result) => result,
286        Err(e) => e.into_compile_error().into(),
287    }
288}
289
290/// Deprecated: Use [`IntoList`] instead.
291///
292/// This is an alias for `IntoList` maintained for backward compatibility.
293/// `IntoRobj` is too generic - this macro specifically creates a named list from a struct.
294#[deprecated(
295    since = "0.8.1",
296    note = "Use `IntoList` instead. `IntoRobj` is too generic - this specifically creates a named list."
297)]
298#[proc_macro_derive(IntoRobj)]
299pub fn derive_into_robj(item: TokenStream) -> TokenStream {
300    match list_struct::derive_into_list(item) {
301        Ok(result) => result,
302        Err(e) => e.into_compile_error().into(),
303    }
304}
305
306/// Enable the construction of dataframes from arrays of structures.
307///
308/// # Example
309///
310/// ```ignore
311/// use extendr_api::prelude::*;
312///
313/// #[derive(Debug, IntoDataFrameRow)]
314/// struct MyStruct {
315///     x: i32,
316///     y: String,
317/// }
318///
319/// let v = vec![MyStruct { x: 0, y: "abc".into() }, MyStruct { x: 1, y: "xyz".into() }];
320/// let df = v.into_dataframe()?;
321///
322/// assert!(df.inherits("data.frame"));
323/// assert_eq!(df[0], r!([0, 1]));
324/// assert_eq!(df[1], r!(["abc", "xyz"]));
325/// ```
326#[proc_macro_derive(IntoDataFrameRow)]
327pub fn derive_into_dataframe(item: TokenStream) -> TokenStream {
328    dataframe::derive_into_dataframe(item)
329}
330
331#[proc_macro]
332pub fn impl_try_from_robj_tuples(input: TokenStream) -> TokenStream {
333    let range = parse_macro_input!(input as syn::ExprTuple);
334    let start = match &range.elems[0] {
335        syn::Expr::Lit(syn::ExprLit {
336            lit: syn::Lit::Int(lit),
337            ..
338        }) => lit.base10_parse::<usize>().unwrap(),
339        _ => {
340            return TokenStream::from(quote!(compile_error!(
341                "Expected integer literal for `start`"
342            )))
343        }
344    };
345    let end = match &range.elems[1] {
346        syn::Expr::Lit(syn::ExprLit {
347            lit: syn::Lit::Int(lit),
348            ..
349        }) => lit.base10_parse::<usize>().unwrap(),
350        _ => {
351            return TokenStream::from(quote!(compile_error!("Expected integer literal for `end`")))
352        }
353    };
354
355    TokenStream::from_iter((start..=end).map(|n| {
356        let types: Vec<_> = (0..n).map(|i| quote::format_ident!("T{}", i)).collect();
357        let indices = 0..n;
358        let element_extraction = indices.map(|idx| {
359            quote! {
360                (&list.elt(#idx)?).try_into()?
361            }
362        });
363
364        TokenStream::from(quote! {
365            impl<#(#types),*> TryFrom<&Robj> for (#(#types,)*)
366            where
367                #(#types: for<'a> TryFrom<&'a Robj, Error = extendr_api::Error>),*
368            {
369                type Error = Error;
370
371                fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
372                    let list: List = robj.try_into()?;
373                    if list.len() != #n {
374                        return Err(Error::ExpectedLength(#n));
375                    }
376                    Ok((
377                        #(#element_extraction),*
378                    ))
379                }
380            }
381
382            // TODO: the following impls are borrowed from `impl_try_from_robj`
383            // find a way to reuse that code, possibly
384
385            impl<#(#types),*> TryFrom<Robj> for (#(#types,)*)
386            where
387                #(#types: for<'a> TryFrom<&'a Robj, Error = extendr_api::Error>),* {
388                type Error = Error;
389
390                fn try_from(robj: Robj) -> extendr_api::Result<Self> {
391                    Self::try_from(&robj)
392                }
393            }
394
395            impl<#(#types),*> TryFrom<&Robj> for Option<(#(#types,)*)>
396            where
397            #(#types: for<'a> TryFrom<&'a Robj, Error = extendr_api::Error>),*{
398                type Error = Error;
399
400                fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
401                    if robj.is_null() || robj.is_na() {
402                        Ok(None)
403                    } else {
404                        <(#(#types,)*)>::try_from(robj).map(Some)
405                    }
406                }
407            }
408
409            impl<#(#types),*> TryFrom<Robj> for Option<(#(#types,)*)>
410            where
411            #(#types: for<'a> TryFrom<&'a Robj, Error = extendr_api::Error>),*{
412                type Error = Error;
413
414                fn try_from(robj: Robj) -> extendr_api::Result<Self> {
415                    Self::try_from(&robj)
416                }
417            }
418        })
419    }))
420}