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) => extendr_conversion::extendr_type_conversion(Item::Struct(str), &opts),
96 Item::Enum(enm) => extendr_conversion::extendr_type_conversion(Item::Enum(enm), &opts),
97 Item::Fn(func) => extendr_function::extendr_function(func, &opts),
98 Item::Impl(item_impl) => match extendr_impl::extendr_impl(item_impl, &opts) {
99 Ok(result) => result,
100 Err(e) => e.into_compile_error().into(),
101 },
102 other_item => TokenStream::from(quote! {#other_item}),
103 }
104}
105
106/// Define a module and export symbols to R
107/// Example:
108///```dont_run
109/// extendr_module! {
110/// mod name;
111/// fn my_func1;
112/// fn my_func2;
113/// impl MyTrait;
114/// }
115/// ```
116/// Outputs:
117///
118/// ```dont_run
119/// #[no_mangle]
120/// #[allow(non_snake_case)]
121/// pub extern "C" fn R_init_hello_extendr(info: *mut extendr_api::DllInfo) {
122/// let mut call_methods = Vec::new();
123/// init__hello(info, &mut call_methods);
124/// unsafe { extendr_api::register_call_methods(info, call_methods.as_ref()) };
125/// }
126/// ```
127#[proc_macro]
128pub fn extendr_module(item: TokenStream) -> TokenStream {
129 extendr_module::extendr_module(item)
130}
131
132/// Create a Pairlist R object from a list of name-value pairs.
133/// ```ignore
134/// assert_eq!(pairlist!(a=1, 2, 3), Pairlist::from_pairs(&[("a", 1), ("", 2), ("", 3)]));
135/// ```
136#[proc_macro]
137pub fn pairlist(item: TokenStream) -> TokenStream {
138 pairlist::pairlist(item)
139}
140
141/// Create a List R object from a list of name-value pairs.
142/// ```ignore
143/// assert_eq!(list!(a=1, 2, 3), List::from_pairs(&[("a", 1), ("", 2), ("", 3)]));
144/// ```
145#[proc_macro]
146pub fn list(item: TokenStream) -> TokenStream {
147 list::list(item)
148}
149
150/// Call a function or primitive defined by a text expression with arbitrary parameters.
151/// This currently works by parsing and evaluating the string in R, but will probably acquire
152/// some shortcuts for simple expressions, for example by caching symbols and constant values.
153///
154/// ```ignore
155/// assert_eq!(call!("`+`", 1, 2), r!(3));
156/// assert_eq!(call!("list", 1, 2), r!([r!(1), r!(2)]));
157/// ```
158#[proc_macro]
159pub fn call(item: TokenStream) -> TokenStream {
160 call::call(item)
161}
162
163/// Execute R code by parsing and evaluating tokens.
164///
165/// ```ignore
166/// R!("c(1, 2, 3)");
167/// R!("{{(0..3).collect_robj()}} + 1");
168/// R!(r#"
169/// print("hello")
170/// "#);
171/// ```
172#[proc_macro]
173#[allow(non_snake_case)]
174pub fn R(item: TokenStream) -> TokenStream {
175 R::R(item.into(), true).into()
176}
177
178/// Execute R code by parsing and evaluating tokens
179/// but without expanding parameters.
180///
181/// ```ignore
182/// // c.f. https://dplyr.tidyverse.org/articles/programming.html
183/// Rraw!(r#"
184/// var_summary <- function(data, var) {
185/// data %>%
186/// summarise(n = n(), min = min({{ var }}), max = max({{ var }}))
187/// }
188/// "#)
189/// ```
190#[proc_macro]
191#[allow(non_snake_case)]
192pub fn Rraw(item: TokenStream) -> TokenStream {
193 R::R(item.into(), false).into()
194}
195
196/// Derives an implementation of `TryFrom<Robj> for Struct` and `TryFrom<&Robj> for Struct` on this struct.
197///
198/// This allows any R object supporting the `$` operator (generally a list or an
199/// environment) to be converted into that struct, as long as the corresponding fields on the R object are
200/// of a compatible type to those on the Rust struct.
201///
202/// # Examples
203/// In the below example, `foo_from_list` is an instance of the `Foo` struct, that has been converted
204/// from an R list:
205/// ```ignore
206/// use extendr_api::prelude::*;
207/// use extendr_macros::TryFromRobj;
208/// # use extendr_api::test;
209/// # test!{
210///
211/// #[derive(TryFromRobj, PartialEq, Debug)]
212/// struct Foo {
213/// a: u64,
214/// b: String
215/// }
216/// let native_foo = Foo { a: 5, b: "bar".into() };
217/// let foo_from_list: Foo = R!("list(a = 5, b = 'bar')")?.try_into()?;
218/// assert_eq!(native_foo, foo_from_list);
219/// # }
220/// # Ok::<(), extendr_api::Error>(())
221/// ```
222///
223/// See [`IntoRobj`] for converting arbitrary Rust types into R type by using
224/// R's list / `List`.
225///
226#[proc_macro_derive(TryFromRobj)]
227pub fn derive_try_from_robj(item: TokenStream) -> TokenStream {
228 match list_struct::derive_try_from_robj(item) {
229 Ok(result) => result,
230 Err(e) => e.into_compile_error().into(),
231 }
232}
233
234/// Derives an implementation of `From<Struct> for Robj` and `From<&Struct> for Robj` on this struct.
235///
236/// This allows the struct to be converted to a named list in R,
237/// where the list names correspond to the field names of the Rust struct.
238///
239/// # Examples
240/// In the below example, `converted` contains an R list object with the same fields as the
241/// `Foo` struct.
242/// ```ignore
243/// use extendr_api::prelude::*;
244/// use extendr_macros::IntoRobj;
245///
246/// # use extendr_api::test;
247/// # test!{
248/// #[derive(IntoRobj)]
249/// struct Foo {
250/// a: u32,
251/// b: String
252/// }
253/// let converted: Robj = Foo {
254/// a: 5,
255/// b: String::from("bar")
256/// }.into();
257/// assert_eq!(converted, R!(r"list(a=5, b='bar')")?);
258/// # }
259/// # Ok::<(), extendr_api::Error>(())
260/// ```
261///
262/// See [`TryFromRobj`] for a `derive`-macro in the other direction, i.e.
263/// instantiation of a rust type, by an R list with fields corresponding to
264/// said type.
265///
266/// # Details
267///
268/// Note, the `From<Struct> for Robj` behaviour is different from what is obtained by applying the standard `#[extendr]` macro
269/// to an `impl` block. The `#[extendr]` behaviour returns to R a **pointer** to Rust memory, and generates wrapper functions for calling
270/// Rust functions on that pointer. The implementation from `#[derive(IntoRobj)]` actually converts the Rust structure
271/// into a native R list, which allows manipulation and access to internal fields, but it's a one-way conversion,
272/// and converting it back to Rust will produce a copy of the original struct.
273#[proc_macro_derive(IntoRobj)]
274pub fn derive_into_robj(item: TokenStream) -> TokenStream {
275 match list_struct::derive_into_robj(item) {
276 Ok(result) => result,
277 Err(e) => e.into_compile_error().into(),
278 }
279}
280
281/// Enable the construction of dataframes from arrays of structures.
282///
283/// # Example
284///
285/// ```ignore
286/// use extendr_api::prelude::*;
287///
288/// #[derive(Debug, IntoDataFrameRow)]
289/// struct MyStruct {
290/// x: i32,
291/// y: String,
292/// }
293///
294/// let v = vec![MyStruct { x: 0, y: "abc".into() }, MyStruct { x: 1, y: "xyz".into() }];
295/// let df = v.into_dataframe()?;
296///
297/// assert!(df.inherits("data.frame"));
298/// assert_eq!(df[0], r!([0, 1]));
299/// assert_eq!(df[1], r!(["abc", "xyz"]));
300/// ```
301#[proc_macro_derive(IntoDataFrameRow)]
302pub fn derive_into_dataframe(item: TokenStream) -> TokenStream {
303 dataframe::derive_into_dataframe(item)
304}
305
306#[proc_macro]
307pub fn impl_try_from_robj_tuples(input: TokenStream) -> TokenStream {
308 let range = parse_macro_input!(input as syn::ExprTuple);
309 let start = match &range.elems[0] {
310 syn::Expr::Lit(syn::ExprLit {
311 lit: syn::Lit::Int(lit),
312 ..
313 }) => lit.base10_parse::<usize>().unwrap(),
314 _ => {
315 return TokenStream::from(quote!(compile_error!(
316 "Expected integer literal for `start`"
317 )))
318 }
319 };
320 let end = match &range.elems[1] {
321 syn::Expr::Lit(syn::ExprLit {
322 lit: syn::Lit::Int(lit),
323 ..
324 }) => lit.base10_parse::<usize>().unwrap(),
325 _ => {
326 return TokenStream::from(quote!(compile_error!("Expected integer literal for `end`")))
327 }
328 };
329
330 TokenStream::from_iter((start..=end).map(|n| {
331 let types: Vec<_> = (0..n).map(|i| quote::format_ident!("T{}", i)).collect();
332 let indices = 0..n;
333 let element_extraction = indices.map(|idx| {
334 quote! {
335 (&list.elt(#idx)?).try_into()?
336 }
337 });
338
339 TokenStream::from(quote! {
340 impl<#(#types),*> TryFrom<&Robj> for (#(#types,)*)
341 where
342 #(#types: for<'a> TryFrom<&'a Robj, Error = extendr_api::Error>),*
343 {
344 type Error = Error;
345
346 fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
347 let list: List = robj.try_into()?;
348 if list.len() != #n {
349 return Err(Error::ExpectedLength(#n));
350 }
351 Ok((
352 #(#element_extraction),*
353 ))
354 }
355 }
356
357 // TODO: the following impls are borrowed from `impl_try_from_robj`
358 // find a way to reuse that code, possibly
359
360 impl<#(#types),*> TryFrom<Robj> for (#(#types,)*)
361 where
362 #(#types: for<'a> TryFrom<&'a Robj, Error = extendr_api::Error>),* {
363 type Error = Error;
364
365 fn try_from(robj: Robj) -> extendr_api::Result<Self> {
366 Self::try_from(&robj)
367 }
368 }
369
370 impl<#(#types),*> TryFrom<&Robj> for Option<(#(#types,)*)>
371 where
372 #(#types: for<'a> TryFrom<&'a Robj, Error = extendr_api::Error>),*{
373 type Error = Error;
374
375 fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
376 if robj.is_null() || robj.is_na() {
377 Ok(None)
378 } else {
379 Ok(Some(<(#(#types,)*)>::try_from(robj)?))
380 }
381 }
382 }
383
384 impl<#(#types),*> TryFrom<Robj> for Option<(#(#types,)*)>
385 where
386 #(#types: for<'a> TryFrom<&'a Robj, Error = extendr_api::Error>),*{
387 type Error = Error;
388
389 fn try_from(robj: Robj) -> extendr_api::Result<Self> {
390 Self::try_from(&robj)
391 }
392 }
393 })
394 }))
395}