derive_constructor 0.1.1

Construct enums without naming them
Documentation
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use synstructure::{Structure, decl_derive};
use syn::{ConstParam, GenericParam, LifetimeDef, TypeParam, parse_str};

fn constructor_derive(s: Structure) -> TokenStream {
    let self_ident = &s.ast().ident;
    let generics_decl = &s.ast().generics;
    let generics = s.ast().generics.params.iter().map(|g| match g {
            GenericParam::Type(TypeParam { ident, .. }) => quote! { #ident },
            GenericParam::Lifetime(LifetimeDef { lifetime, .. }) => quote! { #lifetime },
            GenericParam::Const(ConstParam { ident, .. }) => quote! { #ident },
    });
    let generics = quote! { <#(#generics),*> };
    let where_clause = &s.ast().generics.where_clause;
    let trait_ident = format!("{}Constructor", self_ident);
    let trait_ident = parse_str::<Ident>(&trait_ident).unwrap();

    let trait_decl_items = s.variants().iter().map(|v| {
        let ident = &v.ast().ident;
        
        if v.bindings().is_empty() {
            return quote! {
                const #ident: Self;
            }
        }

        let arg_decls = v.bindings().iter().map(|b| {
            let ident = b.ast().ident.as_ref().unwrap_or(&b.binding);
            let ty = &b.ast().ty;

            quote! {
                #ident: #ty
            }
        });

        quote! {
            #[allow(non_snake_case)]
            fn #ident(#(#arg_decls),*) -> Self;
        }
    });
    let trait_decl = quote! {
        pub trait #trait_ident #generics_decl #where_clause {
            #(#trait_decl_items)*
        }
    };

    let trait_impl_items = s.variants().iter().map(|v| {
        let ident = &v.ast().ident;

        if v.bindings().is_empty() {
            return quote! {
                const #ident: Self = Self::#ident;
            }
        }

        let arg_decls = v.bindings().iter().map(|b| {
            let ident = b.ast().ident.as_ref().unwrap_or(&b.binding);
            let ty = &b.ast().ty;

            quote! {
                #ident: #ty
            }
        });

        let construct = v.construct(|_, i| {
            let b = &v.bindings()[i];
            let ident = b.ast().ident.as_ref().unwrap_or(&b.binding);

            ident
        });

        quote! {
            fn #ident(#(#arg_decls),*) -> Self {
                #construct
            }
        }
    });
    let trait_impl = quote! {
        impl #generics_decl #trait_ident #generics for #self_ident #generics #where_clause {
            #(#trait_impl_items)*
        }
    };

    quote! {
        #trait_decl
        #trait_impl
    }
}

decl_derive!([Constructor] => constructor_derive);

#[cfg(test)]
mod tests {
    #![allow(clippy::blacklisted_name, clippy::redundant_field_names)]
    use super::*;
    use synstructure::test_derive;

    #[test]
    fn test() {
        test_derive! {
            constructor_derive {
                enum Foo<'a, T: Clone> where T: Copy {
                    A,
                    B,
                    X(i32, &'a str, T),
                    Y { foo: i32, bar: &'a str },
                }
            }

            expands to {
                pub trait FooConstructor<'a, T: Clone> where T: Copy {
                    const A: Self;
                    const B: Self;

                    #[allow(non_snake_case)]
                    fn X(__binding_0: i32, __binding_1: &'a str, __binding_2: T) -> Self;
                    #[allow(non_snake_case)]
                    fn Y(foo: i32, bar: &'a str) -> Self;
                }

                impl<'a, T: Clone> FooConstructor<'a, T> for Foo<'a, T> where T: Copy {
                    const A: Self = Self::A;
                    const B: Self = Self::B;

                    fn X(__binding_0: i32, __binding_1: &'a str, __binding_2: T) -> Self {
                        Foo::X(__binding_0, __binding_1, __binding_2,)
                    }

                    fn Y(foo: i32, bar: &'a str) -> Self {
                        Foo::Y {
                            foo: foo,
                            bar: bar,
                        }
                    }
                }
            }
        }
    }
}