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}