iter_variants_derive 0.1.2

Derive macros for iterating over all variants of a rust struct/enum
Documentation
use proc_macro as pm;
use proc_macro2::{self as pm2, Span};
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DeriveInput, Fields, Ident};

fn do_fields(fields: &Fields, ident: pm2::TokenStream) -> pm2::TokenStream {
    match fields {
        Fields::Unnamed(fields_unnamed) => {
            let vs = (0..fields_unnamed.unnamed.len()).map(|i| format_ident!("v{}", i));
            let mut result = {
                let vs = vs.clone();
                quote! {
                    f(#ident(#(#vs,)*))
                }
            };
            for (field, v) in fields_unnamed.unnamed.iter().zip(vs) {
                let ty = &field.ty;
                result = quote! {
                    #ty::iter_variants(|#v| #result)
                }
            }
            result
        }
        Fields::Named(fields_named) => {
            let idents: Vec<_> = fields_named
                .named
                .iter()
                .map(|field| field.ident.clone())
                .collect();
            let mut result = quote! {
                f(#ident {
                    #(#idents,)*
                })
            };
            for field in &fields_named.named {
                let ident = field.ident.clone();
                let ty = field.ty.clone();
                result = quote! {
                    #ty::iter_variants(|#ident| #result)
                }
            }
            result
        }
        Fields::Unit => quote! {
            f(#ident)
        },
    }
}

#[proc_macro_derive(IterVariants)]
pub fn iter_variants_derive(input: pm::TokenStream) -> pm::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let ident = input.ident;

    let output = match input.data {
        Data::Enum(data_enum) => {
            let arms = data_enum.variants.iter().map(|variant| {
                let variant_ident = &variant.ident;
                do_fields(&variant.fields, quote! {Self::#variant_ident})
            });

            quote! {
                #(#arms;)*
            }
        }
        Data::Struct(data_struct) => {
            let ident = ident.clone();
            do_fields(&data_struct.fields, quote! { #ident })
        }
        _ => syn::Error::new_spanned(&ident, "`Name` can only be derived for enums or structs")
            .to_compile_error(),
    };

    let expanded = quote! {
        impl IterVariants for #ident {
            type IterVariantsInput = Self;
            fn iter_variants<F: Fn(Self::IterVariantsInput)>(f: F) {
                #output
            }
        }
    };

    pm::TokenStream::from(expanded)
}

#[proc_macro]
pub fn impl_iter_variants_tuple(_input: pm::TokenStream) -> pm::TokenStream {
    let output = (0..=12).map(|n| {
        let vs = (0..n).map(|i| Ident::new(&format!("v{}", i), Span::call_site()));
        let v_types: Vec<_> = (0..n).map(|i| Ident::new(&format!("V{}", i), Span::call_site())).collect();
        let mut result = {
            let vs = vs.clone();
            quote! {
                f((#(#vs,)*))
            }
        };
        {
            let v_types = v_types.clone();
            for (v_type, v) in v_types.iter().zip(vs) {
                result = quote! {
                    #v_type::iter_variants(|#v| #result)
                }
            }
        }
        quote! {
            impl<#(#v_types: IterVariants,)*> IterVariants for (#(#v_types,)*)
            where
                #(<#v_types as IterVariants>::IterVariantsInput: IterVariants + Copy,)*
            {
                type IterVariantsInput = (#(<#v_types as IterVariants>::IterVariantsInput,)*);
                fn iter_variants<F: Fn(Self::IterVariantsInput)>(f: F) {
                    #result
                }
            }
        }
    });

    pm::TokenStream::from(quote! {
        #(#output)*
    })
}