typesafe-derive-builder 0.1.0

Derive builder macro
Documentation
extern crate proc_macro;
#[macro_use]
extern crate quote;
extern crate rand;
extern crate syn;

use std::iter::repeat;
use proc_macro::TokenStream;

#[proc_macro_derive(Builder)]
pub fn code_gen_builder(input: TokenStream) -> TokenStream {
    let s: String = input.to_string();
    let ast: syn::DeriveInput = syn::parse_derive_input(&s).unwrap();
    let gen: quote::Tokens = impl_builder(&ast);
    gen.parse().unwrap()
}

fn repeat_n(n: usize) -> std::iter::Take<std::iter::Repeat<()>> {
    repeat(()).take(n)
}

fn impl_builder(ast: &syn::DeriveInput) -> quote::Tokens {
    let name = &ast.ident;
    let builder_name = syn::Ident::new(format!("{}Builder", name));

    let mod_name = syn::Ident::new(format!("__{}_internal", name).to_lowercase());

    let fields: &Vec<syn::Field> = match &ast.body {
        &syn::Body::Enum(_) => panic!("Cannot derive Builder for enums"),
        &syn::Body::Struct(ref data) => match data {
            &syn::VariantData::Tuple(_) => panic!("Canont derive Builder for Tuple structs"),
            &syn::VariantData::Unit => panic!("Cannot derive Builder for unit structs"),
            &syn::VariantData::Struct(ref fields) => fields,
        },
    };

    let number_of_fields = fields.len();

    let withouts = repeat_n(number_of_fields)
        .map(|_| {
            quote! { #mod_name::Without }
        })
        .collect::<Vec<_>>();

    let field_inits = fields
        .iter()
        .map(|field| {
            //
            let name = field.clone().ident.unwrap();
            quote! { #name: #mod_name::Without }
        })
        .collect::<Vec<_>>();

    let generic_builder_fields = repeat_n(number_of_fields)
        .map(|_| {
            let r = rand::random::<u64>();
            syn::Ident::new(format!("{}Field{}", builder_name, r))
        })
        .collect::<Vec<_>>();

    let builder_fields = fields
        .iter()
        .zip(&generic_builder_fields)
        .map(|(field, ty)| {
            let name = field.clone().ident.unwrap();
            quote! { #name: #ty }
        })
        .collect::<Vec<_>>();

    let done_cons_fields = fields
        .iter()
        .map(|field| {
            let name = field.clone().ident.unwrap();
            quote! { #name: self.#name.item }
        })
        .collect::<Vec<_>>();

    let done_withs = fields
        .iter()
        .map(|field| {
            let ty = field.clone().ty;
            quote! { #mod_name::With<#ty> }
        })
        .collect::<Vec<_>>();

    let fns = fields
        .iter()
        .map(|field| {
            let name = field.clone().ident.unwrap();
            let ty = field.clone().ty;

            let with_withouts = fields
                .iter()
                .enumerate()
                .map(|(idx, other_field)| {
                    if field == other_field {
                        quote! { #mod_name::With<#ty> }
                    } else {
                        let generic_type_name = generic_builder_fields.get(idx).unwrap();
                        quote!{ #generic_type_name }
                    }
                })
                .collect::<Vec<_>>();

            let assigns = fields
                .iter()
                .map(|other_field| {
                    if field == other_field {
                        quote! { #name: #mod_name::With { item: #name.into() } }
                    } else {
                        let name = other_field.clone().ident.unwrap();
                        quote!{ #name: self.#name }
                    }
                })
                .collect::<Vec<_>>();

            quote! {
                #[inline]
                #[doc(hidden)]
                pub fn #name<T: Into<#ty>>(self, #name: T) -> #builder_name<#(#with_withouts),*> {
                    #builder_name {
                        #(#assigns),*
                    }
                }
            }
        })
        .collect::<Vec<_>>();

    let generic_builder_fields = quote! { #(#generic_builder_fields),* };
    let builder_fields = quote! { #(#builder_fields),* };

    quote! {
        mod #mod_name {
            #[derive(Debug, Copy, Clone)]
            #[doc(hidden)]
            pub struct Without;

            #[derive(Debug)]
            #[doc(hidden)]
            pub struct With<T> { pub item: T }
        }

        impl #name {
            #[inline]
            #[doc(hidden)]
            pub fn build() -> #builder_name<#(#withouts),*> {
                #builder_name {
                    #(#field_inits),*
                }
            }
        }

        #[derive(Debug)]
        #[doc(hidden)]
        pub struct #builder_name<#generic_builder_fields> {
            #builder_fields
        }

        impl<#generic_builder_fields> #builder_name<#generic_builder_fields> {
            #(#fns)*
        }

        impl #builder_name<#(#done_withs),*> {
            #[inline]
            #[doc(hidden)]
            pub fn done(self) -> #name {
                #name { #(#done_cons_fields),* }
            }
        }
    }
}