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}