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