builder_rs/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Ident};
6
7fn builder_methods(fields: &syn::punctuated::Punctuated<Field, syn::token::Comma>) -> proc_macro2::TokenStream {
8    let methods = fields.iter().map(|f| {
9        let name = &f.ident;
10        let ty = &f.ty;
11        quote! {
12            pub fn #name(mut self, #name: #ty) -> Self {
13                self.#name = Some(#name);
14                self
15            }
16        }
17    });
18
19    quote! { #(#methods)* }
20}
21
22fn builder_struct(fields: &syn::punctuated::Punctuated<Field, syn::token::Comma>, builder_name: &Ident) -> proc_macro2::TokenStream {
23    let fields = fields.iter().map(|f| {
24        let name = &f.ident;
25        let ty = &f.ty;
26        quote! { #name: Option<#ty> }
27    });
28
29    quote! {
30        pub struct #builder_name {
31            #(#fields),*
32        }
33    }
34}
35
36#[proc_macro_derive(Builder)]
37pub fn builder(input: TokenStream) -> TokenStream {
38    let input = parse_macro_input!(input as DeriveInput);
39
40    let name = &input.ident;
41    let builder_name = syn::Ident::new(&format!("{}Builder", name), name.span());
42
43    let fields = if let Data::Struct(data_struct) = input.data {
44        match data_struct.fields {
45            Fields::Named(fields) => fields.named,
46            _ => unimplemented!("Only named fields are supported"),
47        }
48    } else {
49        unimplemented!("Only structs are supported");
50    };
51
52    let builder_struct = builder_struct(&fields, &builder_name);
53    let builder_methods = builder_methods(&fields);
54
55    let field_names = fields.iter().map(|f| &f.ident);
56    let field_accesses = fields.iter().map(|f| {
57        let name = &f.ident;
58        quote! { self.#name.unwrap_or_default() }
59    });
60
61    let build_method = quote! {
62        pub fn build(self) -> #name {
63            #name {
64                #(#field_names: #field_accesses),*
65            }
66        }
67    };
68
69    let field_names_for_builder_impl = fields.iter().map(|f| &f.ident);
70    let builder_impl = quote! {
71        impl #name {
72            pub fn builder() -> #builder_name {
73                #builder_name {
74                    #(#field_names_for_builder_impl: None),*
75                }
76            }
77        }
78    };
79
80    let expanded = quote! {
81        #builder_impl
82
83        #builder_struct
84
85        impl #builder_name {
86            #builder_methods
87
88            #build_method
89        }
90    };
91
92    TokenStream::from(expanded)
93}