bon_macros/builder/builder_gen/
start_fn.rs

1use crate::util::prelude::*;
2
3impl super::BuilderGenCtx {
4    pub(super) fn start_fn(&self) -> syn::ItemFn {
5        let builder_ident = &self.builder_type.ident;
6        let docs = &self.start_fn.docs;
7        let vis = &self.start_fn.vis;
8
9        let start_fn_ident = &self.start_fn.ident;
10
11        // TODO: we can use a shorter syntax with anonymous lifetimes to make
12        // the generated code and function signature displayed by rust-analyzer
13        // a bit shorter and easier to read. However, the caveat is that we can
14        // do this only for lifetimes that have no bounds and if they don't appear
15        // in the where clause. Research `darling`'s lifetime tracking API and
16        // maybe implement this in the future
17
18        let generics = self.start_fn.generics.as_ref().unwrap_or(&self.generics);
19
20        let generics_decl = &generics.decl_without_defaults;
21        let where_clause = &generics.where_clause;
22        let generic_args = &self.generics.args;
23
24        let receiver = self.receiver();
25
26        let receiver_field_init = receiver.map(|receiver| {
27            let self_token = &receiver.with_self_keyword.self_token;
28            quote! {
29                __unsafe_private_receiver: #self_token,
30            }
31        });
32
33        let receiver = receiver.map(|receiver| {
34            let mut receiver = receiver.with_self_keyword.clone();
35
36            if receiver.reference.is_none() {
37                receiver.mutability = None;
38            }
39
40            quote! { #receiver, }
41        });
42
43        let start_fn_params = self
44            .start_fn_args()
45            .map(|member| member.base.fn_input_param());
46
47        // Assign `start_fn_args` to intermediate variables, which may be used
48        // by custom fields init expressions. This is needed only if there is
49        // a conversion configured for the `start_fn` members, otherwise these
50        // are already available in scope as function arguments directly.
51        let start_fn_vars = self.start_fn_args().filter_map(|member| {
52            let ident = &member.base.ident;
53            let ty = &member.base.ty.orig;
54            let conversion = member.base.conversion()?;
55
56            Some(quote! {
57                let #ident: #ty = #conversion;
58            })
59        });
60
61        let mut start_fn_args = self.start_fn_args().peekable();
62
63        let start_fn_args_field_init = start_fn_args.peek().is_some().then(|| {
64            let idents = start_fn_args.map(|member| &member.base.ident);
65            quote! {
66                __unsafe_private_start_fn_args: (#(#idents,)*),
67            }
68        });
69
70        // Create custom fields in separate variables. This way custom fields
71        // declared lower in the struct definition can reference custom fields
72        // declared higher in their init expressions.
73        let custom_fields_vars = self.custom_fields().map(|field| {
74            let ident = &field.ident;
75            let ty = &field.norm_ty;
76            let init = field
77                .init
78                .as_ref()
79                .map(|init| quote! { (|| #init)() })
80                .unwrap_or_else(|| quote! { ::core::default::Default::default() });
81
82            quote! {
83                let #ident: #ty = #init;
84            }
85        });
86
87        let custom_fields_idents = self.custom_fields().map(|field| &field.ident);
88
89        let ide_hints = self.ide_hints();
90
91        // `Default` trait implementation is provided only for tuples up to 12
92        // elements in the standard library 😳:
93        // https://github.com/rust-lang/rust/blob/67bb749c2e1cf503fee64842963dd3e72a417a3f/library/core/src/tuple.rs#L213
94        let named_members_field_init = if self.named_members().take(13).count() <= 12 {
95            quote!(::core::default::Default::default())
96        } else {
97            let none = format_ident!("None");
98            let nones = self.named_members().map(|_| &none);
99            quote! {
100                (#(#nones,)*)
101            }
102        };
103
104        syn::parse_quote! {
105            #(#docs)*
106            #[inline(always)]
107            #[allow(
108                // This is intentional. We want the builder syntax to compile away
109                clippy::inline_always,
110                // We normalize `Self` references intentionally to simplify code generation
111                clippy::use_self,
112                // Let's keep it as non-const for now to avoid restricting ourselfves to only
113                // const operations.
114                clippy::missing_const_for_fn,
115            )]
116            #vis fn #start_fn_ident< #(#generics_decl),* >(
117                #receiver
118                #(#start_fn_params,)*
119            ) -> #builder_ident< #(#generic_args,)* >
120            #where_clause
121            {
122                #ide_hints
123                #( #start_fn_vars )*
124                #( #custom_fields_vars )*
125
126                #builder_ident {
127                    __unsafe_private_phantom: ::core::marker::PhantomData,
128                    #( #custom_fields_idents, )*
129                    #receiver_field_init
130                    #start_fn_args_field_init
131                    __unsafe_private_named: #named_members_field_init,
132                }
133            }
134        }
135    }
136}