extendr_macros/
list_struct.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DeriveInput};
4
5/// Implementation of the TryFromList macro. Refer to the documentation there
6pub fn derive_try_from_list(item: TokenStream) -> syn::parse::Result<TokenStream> {
7    // Parse the tokens into a Struct
8    let ast = syn::parse::<DeriveInput>(item)?;
9    let inside = if let Data::Struct(inner) = ast.data {
10        inner
11    } else {
12        return Err(syn::Error::new_spanned(ast, "Only struct is supported"));
13    };
14    let struct_name = ast.ident;
15
16    // Iterate each struct field and capture a conversion from Robj for each field
17    let mut tokens = Vec::<_>::with_capacity(inside.fields.len());
18    let is_tuple_struct = inside
19        .fields
20        .iter()
21        .next()
22        .map(|x| x.ident.is_none())
23        .unwrap_or(false);
24    for (id_field, field) in inside.fields.iter().enumerate() {
25        if is_tuple_struct {
26            let field_name = syn::Index::from(id_field);
27            let field_str = format!(".{id_field}");
28            // This is like `value$.0` in R
29            tokens.push(quote!(
30                #field_name: value.dollar(#field_str)?.try_into()?
31            ));
32        } else {
33            let field_name = field.ident.as_ref().unwrap();
34            let field_str = field_name.to_string();
35            // This is like `value$foo` in R
36            tokens.push(quote!(
37                #field_name: value.dollar(#field_str)?.try_into()?
38            ));
39        }
40    }
41
42    // Emit the conversion trait impl
43    Ok(TokenStream::from(quote!(
44        impl std::convert::TryFrom<&extendr_api::Robj> for #struct_name {
45            type Error = extendr_api::Error;
46
47            fn try_from(value: &extendr_api::Robj) -> extendr_api::Result<Self> {
48                Ok(#struct_name {
49                    #(#tokens),*
50                })
51            }
52        }
53
54        impl std::convert::TryFrom<extendr_api::Robj> for #struct_name {
55            type Error = extendr_api::Error;
56
57            fn try_from(value: extendr_api::Robj) -> extendr_api::Result<Self> {
58                Ok(#struct_name {
59                    #(#tokens),*
60                })
61            }
62        }
63    )))
64}
65
66/// Implementation of the `IntoList` macro. Refer to the documentation there
67pub fn derive_into_list(item: TokenStream) -> syn::parse::Result<TokenStream> {
68    // Parse the tokens into a Struct
69    let ast = syn::parse::<DeriveInput>(item)?;
70    let inside = if let Data::Struct(inner) = ast.data {
71        inner
72    } else {
73        return Err(syn::Error::new_spanned(ast, "Only `struct` is supported"));
74    };
75    let struct_name = ast.ident;
76
77    // Iterate each struct field and capture a token that creates a KeyValue pair (tuple) for
78    // each field
79    let mut tokens = Vec::<_>::with_capacity(inside.fields.len());
80
81    for (id_field, field) in inside.fields.iter().enumerate() {
82        let mut ignore = false;
83
84        let field_attributes = &field.attrs;
85        for attrib in field_attributes {
86            if !attrib.path().is_ident("into_list") {
87                continue;
88            }
89            let ignore_flag: syn::Meta = attrib.parse_args()?;
90            match ignore_flag {
91                syn::Meta::Path(path) => {
92                    if path.is_ident("ignore") {
93                        ignore = true;
94                    }
95                }
96                _ => {
97                    return Err(syn::Error::new_spanned(
98                        ignore_flag,
99                        "unrecognized attribute for `IntoList`",
100                    ))
101                }
102            }
103        }
104
105        if ignore {
106            continue;
107        }
108
109        let is_tuple_struct = field.colon_token.is_none();
110        if is_tuple_struct {
111            let dot_field_name = format!(".{id_field}");
112            let id_field_index = syn::Index::from(id_field);
113            tokens.push(quote!(
114                (#dot_field_name, (&value.#id_field_index).into())
115            ));
116        } else {
117            let field_name = field.ident.as_ref().unwrap();
118            let field_str = field_name.to_string();
119            tokens.push(quote!(
120                (#field_str, (&value.#field_name).into())
121            ));
122        }
123    }
124
125    // The only thing we emit from this macro is the conversion trait impl
126    Ok(TokenStream::from(quote!(
127        impl std::convert::From<&#struct_name> for extendr_api::Robj {
128            fn from(value: &#struct_name) -> Self {
129                extendr_api::List::from_pairs([#(#tokens),*]).into()
130            }
131        }
132        impl std::convert::From<#struct_name> for extendr_api::Robj {
133            fn from(value: #struct_name) -> Self {
134                (&value).into()
135            }
136        }
137    )))
138}