use proc_macro2::TokenStream;
use quote::quote;
use crate::{Render, core::Attrs, params};
use super::syntax::ForwardSyntax;
#[derive(Clone, Default)]
pub struct EnumSyntax;
impl Render for EnumSyntax {
type Args = params::EnumParams;
fn render(&self, args: Self::Args) -> syn::Result<TokenStream> {
let ident = &args.input.ident;
let (impl_generics, type_generics, where_generics) = args.input.generics.split_for_impl();
let enum_attrs = Attrs::parse(&args.input.attrs)?;
let enum_fwd = ForwardSyntax::parse(&enum_attrs)?;
if enum_fwd.sigs.is_empty() {
return Ok(quote!());
}
let mut methods = Vec::new();
for s in &enum_fwd.sigs {
let method_name = &s.ident;
let output = &s.output;
let inputs = &s.inputs;
let arg_names: Vec<_> = s
.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Typed(pat) => Some(&*pat.pat),
_ => None,
})
.collect();
let mut arms = Vec::new();
for variant in &args.data.variants {
let variant_ident = &variant.ident;
let variant_attrs = Attrs::parse(&variant.attrs)?;
let v_fwd = ForwardSyntax::parse(&variant_attrs)?;
if v_fwd.skip {
let pattern = match &variant.fields {
syn::Fields::Unit => quote! { Self::#variant_ident },
syn::Fields::Unnamed(_) => quote! { Self::#variant_ident(..) },
syn::Fields::Named(_) => quote! { Self::#variant_ident { .. } },
};
arms.push(quote! { #pattern => unreachable!() });
continue;
}
match &variant.fields {
syn::Fields::Unit => {
return Err(syn::Error::new_spanned(
variant_ident,
"unit variants cannot forward methods; use #[moxy(forward(skip))]",
));
}
syn::Fields::Unnamed(fields) => {
if fields.unnamed.len() == 1 {
arms.push(quote! {
Self::#variant_ident(__v) => __v.#method_name(#(#arg_names),*)
});
} else {
arms.push(quote! {
Self::#variant_ident(__v, ..) => __v.#method_name(#(#arg_names),*)
});
}
}
syn::Fields::Named(fields) => {
let forward_field = fields
.named
.iter()
.find(|f| {
Attrs::parse(&f.attrs)
.and_then(|a| ForwardSyntax::parse(&a))
.map(|fwd| fwd.flag)
.unwrap_or(false)
})
.or_else(|| fields.named.first());
let Some(forward_field) = forward_field else {
return Err(syn::Error::new_spanned(
variant_ident,
"named variant has no fields to forward to",
));
};
let field_ident = forward_field.ident.as_ref().unwrap();
arms.push(quote! {
Self::#variant_ident { #field_ident, .. } => #field_ident.#method_name(#(#arg_names),*)
});
}
}
}
methods.push(quote! {
pub fn #method_name(#inputs) #output {
match self {
#(#arms,)*
}
}
});
}
Ok(quote! {
impl #impl_generics #ident #type_generics #where_generics {
#(#methods)*
}
})
}
}