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 let struct_name_str = struct_name.to_string();
16
17 let mut tokens = Vec::<_>::with_capacity(inside.fields.len());
19 let is_tuple_struct = inside
20 .fields
21 .iter()
22 .next()
23 .map(|x| x.ident.is_none())
24 .unwrap_or(false);
25 for (id_field, field) in inside.fields.iter().enumerate() {
26 if is_tuple_struct {
27 let field_name = syn::Index::from(id_field);
28 let field_str = format!(".{id_field}");
29 tokens.push(quote!(
31 #field_name: value
32 .elt(#id_field)?
33 .try_into()
34 .map_err(|error| extendr_api::error::Error::Other(format!(
35 "failed to convert tuple field `{}` on `{}`: {}",
36 #field_str,
37 #struct_name_str,
38 error
39 )))?
40 ));
41 } else {
42 let field_name = field.ident.as_ref().unwrap();
43 let field_str = field_name.to_string();
44 tokens.push(quote!(
46 #field_name: value
47 .dollar(#field_str)?
48 .try_into()
49 .map_err(|error| extendr_api::error::Error::Other(format!(
50 "failed to convert field `{}` on `{}`: {}",
51 #field_str,
52 #struct_name_str,
53 error
54 )))?
55 ));
56 }
57 }
58
59 Ok(TokenStream::from(quote!(
61 impl std::convert::TryFrom<&extendr_api::Robj> for #struct_name {
62 type Error = extendr_api::Error;
63
64 fn try_from(value: &extendr_api::Robj) -> extendr_api::Result<Self> {
65 let value: List = value.try_into()?;
66 Ok(#struct_name {
67 #(#tokens),*
68 })
69 }
70 }
71
72 impl std::convert::TryFrom<extendr_api::Robj> for #struct_name {
73 type Error = extendr_api::Error;
74
75 fn try_from(value: extendr_api::Robj) -> extendr_api::Result<Self> {
76 let value: List = value.try_into()?;
77 Ok(#struct_name {
78 #(#tokens),*
79 })
80 }
81 }
82 )))
83}
84
85pub fn derive_into_list(item: TokenStream) -> syn::parse::Result<TokenStream> {
87 let ast = syn::parse::<DeriveInput>(item)?;
89 let inside = if let Data::Struct(inner) = ast.data {
90 inner
91 } else {
92 return Err(syn::Error::new_spanned(ast, "Only `struct` is supported"));
93 };
94 let struct_name = ast.ident;
95
96 let mut tokens = Vec::<_>::with_capacity(inside.fields.len());
99
100 for (id_field, field) in inside.fields.iter().enumerate() {
101 let mut ignore = false;
102
103 let field_attributes = &field.attrs;
104 for attrib in field_attributes {
105 if !attrib.path().is_ident("into_list") {
106 continue;
107 }
108 let ignore_flag: syn::Meta = attrib.parse_args()?;
109 match ignore_flag {
110 syn::Meta::Path(path) => {
111 if path.is_ident("ignore") {
112 ignore = true;
113 }
114 }
115 _ => {
116 return Err(syn::Error::new_spanned(
117 ignore_flag,
118 "unrecognized attribute for `IntoList`",
119 ))
120 }
121 }
122 }
123
124 if ignore {
125 continue;
126 }
127
128 let is_tuple_struct = field.colon_token.is_none();
129 if is_tuple_struct {
130 let dot_field_name = format!(".{id_field}");
131 let id_field_index = syn::Index::from(id_field);
132 tokens.push(quote!(
133 (#dot_field_name, (&value.#id_field_index).into())
134 ));
135 } else {
136 let field_name = field.ident.as_ref().unwrap();
137 let field_str = field_name.to_string();
138 tokens.push(quote!(
139 (#field_str, (&value.#field_name).into())
140 ));
141 }
142 }
143
144 Ok(TokenStream::from(quote!(
146 impl std::convert::From<&#struct_name> for extendr_api::Robj {
147 fn from(value: &#struct_name) -> Self {
148 extendr_api::List::from_pairs([#(#tokens),*]).into()
149 }
150 }
151 impl std::convert::From<#struct_name> for extendr_api::Robj {
152 fn from(value: #struct_name) -> Self {
153 (&value).into()
154 }
155 }
156 )))
157}