moxy-derive 0.0.4

derive macros for moxy crate
Documentation
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)*
            }
        })
    }
}