use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput};
pub fn derive_try_from_list(item: TokenStream) -> syn::parse::Result<TokenStream> {
let ast = syn::parse::<DeriveInput>(item)?;
let inside = if let Data::Struct(inner) = ast.data {
inner
} else {
return Err(syn::Error::new_spanned(ast, "Only struct is supported"));
};
let struct_name = ast.ident;
let mut tokens = Vec::<_>::with_capacity(inside.fields.len());
let is_tuple_struct = inside
.fields
.iter()
.next()
.map(|x| x.ident.is_none())
.unwrap_or(false);
for (id_field, field) in inside.fields.iter().enumerate() {
if is_tuple_struct {
let field_name = syn::Index::from(id_field);
let field_str = format!(".{id_field}");
tokens.push(quote!(
#field_name: value.dollar(#field_str)?.try_into()?
));
} else {
let field_name = field.ident.as_ref().unwrap();
let field_str = field_name.to_string();
tokens.push(quote!(
#field_name: value.dollar(#field_str)?.try_into()?
));
}
}
Ok(TokenStream::from(quote!(
impl std::convert::TryFrom<&extendr_api::Robj> for #struct_name {
type Error = extendr_api::Error;
fn try_from(value: &extendr_api::Robj) -> extendr_api::Result<Self> {
Ok(#struct_name {
#(#tokens),*
})
}
}
impl std::convert::TryFrom<extendr_api::Robj> for #struct_name {
type Error = extendr_api::Error;
fn try_from(value: extendr_api::Robj) -> extendr_api::Result<Self> {
Ok(#struct_name {
#(#tokens),*
})
}
}
)))
}
pub fn derive_into_list(item: TokenStream) -> syn::parse::Result<TokenStream> {
let ast = syn::parse::<DeriveInput>(item)?;
let inside = if let Data::Struct(inner) = ast.data {
inner
} else {
return Err(syn::Error::new_spanned(ast, "Only `struct` is supported"));
};
let struct_name = ast.ident;
let mut tokens = Vec::<_>::with_capacity(inside.fields.len());
for (id_field, field) in inside.fields.iter().enumerate() {
let mut ignore = false;
let field_attributes = &field.attrs;
for attrib in field_attributes {
if !attrib.path().is_ident("into_list") {
continue;
}
let ignore_flag: syn::Meta = attrib.parse_args()?;
match ignore_flag {
syn::Meta::Path(path) => {
if path.is_ident("ignore") {
ignore = true;
}
}
_ => {
return Err(syn::Error::new_spanned(
ignore_flag,
"unrecognized attribute for `IntoList`",
))
}
}
}
if ignore {
continue;
}
let is_tuple_struct = field.colon_token.is_none();
if is_tuple_struct {
let dot_field_name = format!(".{id_field}");
let id_field_index = syn::Index::from(id_field);
tokens.push(quote!(
(#dot_field_name, (&value.#id_field_index).into())
));
} else {
let field_name = field.ident.as_ref().unwrap();
let field_str = field_name.to_string();
tokens.push(quote!(
(#field_str, (&value.#field_name).into())
));
}
}
Ok(TokenStream::from(quote!(
impl std::convert::From<&#struct_name> for extendr_api::Robj {
fn from(value: &#struct_name) -> Self {
extendr_api::List::from_pairs([#(#tokens),*]).into()
}
}
impl std::convert::From<#struct_name> for extendr_api::Robj {
fn from(value: #struct_name) -> Self {
(&value).into()
}
}
)))
}