use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{
parse::{Parse, ParseStream},
Result,
};
struct NamedTupleAttrs(syn::punctuated::Punctuated<NamedTupleAttr, syn::Token![,]>);
impl Parse for NamedTupleAttrs {
fn parse(input: ParseStream) -> Result<Self> {
input
.parse_terminated::<NamedTupleAttr, syn::Token![,]>(NamedTupleAttr::parse)
.map(NamedTupleAttrs)
}
}
struct NamedTupleAttr {
ident: syn::Ident,
_eq_token: syn::Token![=],
value: syn::Lit,
}
impl Parse for NamedTupleAttr {
fn parse(input: ParseStream) -> Result<Self> {
Ok(NamedTupleAttr {
ident: input.parse()?,
_eq_token: input.parse()?,
value: input.parse()?,
})
}
}
struct NamedTupleConfig {
constructor: bool,
tuple_methods: bool,
}
impl Default for NamedTupleConfig {
fn default() -> Self {
Self {
constructor: true,
tuple_methods: true,
}
}
}
impl TryFrom<NamedTupleAttrs> for NamedTupleConfig {
type Error = syn::Error;
fn try_from(attrs: NamedTupleAttrs) -> syn::Result<Self> {
attrs.0.iter().try_fold(Self::default(), |mut acc, attr| {
match (attr.ident.to_string().as_str(), &attr.value) {
("constructor", syn::Lit::Bool(val)) => acc.constructor = val.value,
("tuple_methods", syn::Lit::Bool(val)) => acc.tuple_methods = val.value,
_ => {
return Err(syn::Error::new(
attr.value.span(),
"invalid or unknown parameter",
))
}
}
Ok(acc)
})
}
}
pub fn named_tuple(attrs: TokenStream, item: TokenStream) -> TokenStream {
let attrs = syn::parse_macro_input!(attrs as NamedTupleAttrs);
let config = NamedTupleConfig::try_from(attrs)
.map_err(syn::Error::into_compile_error)
.unwrap();
let strct = syn::parse_macro_input!(item as syn::ItemStruct);
if let syn::Fields::Named(ref fields) = strct.fields {
let name = &strct.ident;
let (impl_generics, type_generics, where_clause) = &strct.generics.split_for_impl();
let (names, types): (Vec<_>, Vec<_>) = fields
.named
.iter()
.cloned()
.map(|it| (it.ident.unwrap(), it.ty))
.unzip();
let mut stream = proc_macro2::TokenStream::new();
strct.to_tokens(&mut stream);
if config.constructor {
quote::quote! {
impl #impl_generics #name #type_generics #where_clause {
pub fn new(#(#names: #types),*) -> Self {
Self {
#(#names),*
}
}
}
}
.to_tokens(&mut stream);
}
if config.tuple_methods {
quote::quote! {
impl #impl_generics From<(#(#types),*)> for #name #type_generics #where_clause {
fn from((#(#names),*): (#(#types),*)) -> Self {
Self { #(#names),* }
}
}
impl #impl_generics From<#name #type_generics> for (#(#types),*) #where_clause {
fn from(it: #name #type_generics) -> (#(#types),*) {
(#(it.#names),*)
}
}
impl #impl_generics #name #type_generics #where_clause {
pub fn as_tuple(self) -> (#(#types),*) {
<(#(#types),*)>::from(self)
}
}
}
.to_tokens(&mut stream);
}
stream.into()
} else {
syn::Error::new(
strct.struct_token.span,
"Named tuples must have named fields!",
)
.into_compile_error();
unreachable!();
}
}