extendr_macros/
list_struct.rs1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DeriveInput};
4
5pub fn derive_try_from_list(item: TokenStream) -> syn::parse::Result<TokenStream> {
7 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 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 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 tokens.push(quote!(
37 #field_name: value.dollar(#field_str)?.try_into()?
38 ));
39 }
40 }
41
42 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
66pub fn derive_into_list(item: TokenStream) -> syn::parse::Result<TokenStream> {
68 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 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 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}