bon_macros/builder/builder_gen/
start_fn.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use crate::util::prelude::*;

impl super::BuilderGenCtx {
    pub(super) fn start_fn(&self) -> syn::ItemFn {
        let builder_ident = &self.builder_type.ident;
        let docs = &self.start_fn.docs;
        let vis = &self.start_fn.vis;

        let start_fn_ident = &self.start_fn.ident;

        // TODO: we can use a shorter syntax with anonymous lifetimes to make
        // the generated code and function signature displayed by rust-analyzer
        // a bit shorter and easier to read. However, the caveat is that we can
        // do this only for lifetimes that have no bounds and if they don't appear
        // in the where clause. Research `darling`'s lifetime tracking API and
        // maybe implement this in the future

        let generics = self.start_fn.generics.as_ref().unwrap_or(&self.generics);

        let generics_decl = &generics.decl_without_defaults;
        let where_clause = &generics.where_clause;
        let generic_args = &self.generics.args;

        let phantom_field = &self.ident_pool.phantom;
        let receiver_field = &self.ident_pool.receiver;
        let start_fn_args_field = &self.ident_pool.start_fn_args;
        let named_members_field = &self.ident_pool.named_members;

        let receiver = self.receiver();

        let receiver_field_init = receiver.map(|receiver| {
            let self_token = &receiver.with_self_keyword.self_token;
            quote! {
                #receiver_field: #self_token,
            }
        });

        let receiver = receiver.map(|receiver| {
            let mut receiver = receiver.with_self_keyword.clone();

            if receiver.reference.is_none() {
                receiver.mutability = None;
            }

            quote! { #receiver, }
        });

        let start_fn_params = self
            .start_fn_args()
            .map(|member| member.base.fn_input_param());

        // Assign `start_fn_args` to intermediate variables, which may be used
        // by custom fields init expressions. This is needed only if there is
        // a conversion configured for the `start_fn` members, otherwise these
        // are already available in scope as function arguments directly.
        let start_fn_vars = self.start_fn_args().filter_map(|member| {
            let ident = &member.base.ident;
            let ty = &member.base.ty.orig;
            let conversion = member.base.conversion()?;

            Some(quote! {
                let #ident: #ty = #conversion;
            })
        });

        let mut start_fn_args = self.start_fn_args().peekable();

        let start_fn_args_field_init = start_fn_args.peek().is_some().then(|| {
            let idents = start_fn_args.map(|member| &member.base.ident);
            quote! {
                #start_fn_args_field: (#(#idents,)*),
            }
        });

        // Create custom fields in separate variables. This way custom fields
        // declared lower in the struct definition can reference custom fields
        // declared higher in their init expressions.
        let custom_fields_vars = self.custom_fields().map(|field| {
            let ident = &field.ident;
            let ty = &field.norm_ty;
            let init = field
                .init
                .as_ref()
                .map(|init| quote! { (|| #init)() })
                .unwrap_or_else(|| quote! { ::core::default::Default::default() });

            quote! {
                let #ident: #ty = #init;
            }
        });

        let custom_fields_idents = self.custom_fields().map(|field| &field.ident);

        let ide_hints = self.ide_hints();

        // `Default` trait implementation is provided only for tuples up to 12
        // elements in the standard library 😳:
        // https://github.com/rust-lang/rust/blob/67bb749c2e1cf503fee64842963dd3e72a417a3f/library/core/src/tuple.rs#L213
        let named_members_field_init = if self.named_members().take(13).count() <= 12 {
            quote!(::core::default::Default::default())
        } else {
            let none = format_ident!("None");
            let nones = self.named_members().map(|_| &none);
            quote! {
                (#(#nones,)*)
            }
        };

        syn::parse_quote! {
            #(#docs)*
            #[inline(always)]
            #[allow(
                // This is intentional. We want the builder syntax to compile away
                clippy::inline_always,
                // We normalize `Self` references intentionally to simplify code generation
                clippy::use_self,
                // Let's keep it as non-const for now to avoid restricting ourselfves to only
                // const operations.
                clippy::missing_const_for_fn,
            )]
            #vis fn #start_fn_ident< #(#generics_decl),* >(
                #receiver
                #(#start_fn_params,)*
            ) -> #builder_ident< #(#generic_args,)* >
            #where_clause
            {
                #ide_hints
                #( #start_fn_vars )*
                #( #custom_fields_vars )*

                #builder_ident {
                    #phantom_field: ::core::marker::PhantomData,
                    #( #custom_fields_idents, )*
                    #receiver_field_init
                    #start_fn_args_field_init
                    #named_members_field: #named_members_field_init,
                }
            }
        }
    }
}