use proc_macro2::Span;
use proc_macro2::TokenStream;
use procmeta::prelude::*;
use quote::format_ident;
use quote::quote;
use syn::Data;
use syn::DataEnum;
use syn::DeriveInput;
use syn::Error;
use syn::Field;
use syn::Result;
pub fn fn_impl_expand(props: Vec<Field>, data: &DataEnum) -> Result<TokenStream> {
if props.is_empty() {
return Ok(quote!());
}
let mut result_token = quote!();
let mut fn_tokens = vec![];
for field in props {
fn_tokens.push((field, quote!()));
}
for variant in &data.variants {
let mut values = vec![];
for attr in &variant.attrs {
let value_meta = attr.meta.require_list()?;
if !value_meta.path.is_ident("values") {
continue;
}
if !values.is_empty() {
return Err(Error::new(Span::call_site(), "duplicate define values"));
}
let mut parse_result = meta_list_to_expr(value_meta)?;
values.append(&mut parse_result);
}
if values.len() != fn_tokens.len() {
return Err(Error::new(
Span::call_site(),
"vaues's len not eq props's len",
));
}
let var_ident = &variant.ident;
let match_branch_code = match &variant.fields {
syn::Fields::Named(named) => {
let mut fields = quote!();
for field in &named.named {
let field_name = &field.ident;
fields = quote!(#field_name,);
}
quote!(Self::#var_ident {#fields})
}
syn::Fields::Unnamed(unamed) => {
let mut fields = quote!();
for (index, _field) in unamed.unnamed.iter().enumerate() {
let field_name = format_ident!("_{index}");
fields = quote!(#field_name,);
}
quote!(Self::#var_ident (#fields))
}
syn::Fields::Unit => {
quote!(Self::#var_ident)
}
};
for (index, fn_token) in fn_tokens.iter_mut().enumerate() {
let value_token = &values[index];
let field_token = &fn_token.1;
fn_token.1 = quote! {
#field_token
#match_branch_code => #value_token.into(),
};
}
}
for (field, token) in fn_tokens {
let field_name = field
.ident
.as_ref()
.map(|t| t.to_string())
.unwrap_or_default();
let fn_name = format_ident!("get_{field_name}");
let ret_ty = field.ty;
result_token = quote! {
#result_token
pub fn #fn_name(&self) -> #ret_ty {
match self {
#token
}
}
};
}
Ok(result_token)
}
pub fn expand(input: DeriveInput) -> Result<TokenStream> {
let ty = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let result_token = match &input.data {
Data::Struct(_) => unimplemented!(),
Data::Enum(data) => {
let mut fields_named: Option<Vec<Field>> = None;
for attr in input.attrs {
let prop_meta = attr.meta.require_list()?;
if !prop_meta.path.is_ident("props") {
continue;
}
if fields_named.is_some() {
return Err(Error::new(Span::call_site(), "duplicate define props"));
}
let parse_result = meta_list_to_fields(prop_meta)?;
fields_named = Some(parse_result);
}
let fields = match fields_named {
Some(props) => props,
None => return Err(Error::new(Span::call_site(), "miss define values")),
};
let fn_tokens = fn_impl_expand(fields, data)?;
quote! {
impl #impl_generics #ty #ty_generics #where_clause {
#fn_tokens
}
}
}
Data::Union(_) => unimplemented!(),
};
Ok(result_token)
}