bon_macros/builder/builder_gen/
builder_decl.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
use crate::builder::builder_gen::NamedMember;
use crate::util::prelude::*;

impl super::BuilderGenCtx {
    pub(super) fn builder_decl(&self) -> TokenStream {
        let builder_vis = &self.builder_type.vis;
        let builder_ident = &self.builder_type.ident;
        let generics_decl = &self.generics.decl_with_defaults;
        let where_clause = &self.generics.where_clause;
        let phantom_data = self.phantom_data();
        let state_mod = &self.state_mod.ident;
        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;

        // The fields can't be hidden using Rust's privacy syntax.
        // The details about this are described in the blog post:
        // https://bon-rs.com/blog/the-weird-of-function-local-types-in-rust.
        //
        // We could use `#[cfg(not(rust_analyzer))]` to hide the private fields in IDE.
        // However, RA would then not be able to type-check the generated code, which
        // may or may not be a problem, because the main thing is that the type signatures
        // would still work in RA.
        let private_field_attrs = {
            // The message is defined separately to make it single-line in the
            // generated code. This simplifies the task of removing unnecessary
            // attributes from the generated code when preparing for demo purposes.
            let deprecated_msg = "\
                this field should not be used directly; it's an implementation detail \
                if you found yourself needing it, then you are probably doing something wrong; \
                feel free to open an issue/discussion in our GitHub repository \
                (https://github.com/elastio/bon) or ask for help in our Discord server \
                (https://bon-rs.com/discord)";

            quote! {
                #[doc(hidden)]
                #[deprecated = #deprecated_msg]
            }
        };

        let receiver_field = self.receiver().map(|receiver| {
            let ty = &receiver.without_self_keyword;
            quote! {
                #private_field_attrs
                #receiver_field: #ty,
            }
        });

        let must_use_message = format!(
            "the builder does nothing until you call `{}()` on it to finish building",
            self.finish_fn.ident
        );

        let allows = super::allow_warnings_on_member_types();

        let mut start_fn_arg_types = self
            .start_fn_args()
            .map(|member| &member.base.ty.norm)
            .peekable();

        let start_fn_args_field = start_fn_arg_types.peek().is_some().then(|| {
            quote! {
                #private_field_attrs
                #start_fn_args_field: (#(#start_fn_arg_types,)*),
            }
        });

        let named_members_types = self.named_members().map(NamedMember::underlying_norm_ty);

        let docs = &self.builder_type.docs;
        let state_var = &self.state_var;

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

        quote! {
            #[must_use = #must_use_message]
            #(#docs)*
            #allows
            #[allow(
                // We use `__private` prefix for all fields intentionally to hide them
                clippy::struct_field_names,

                // This lint doesn't emerge until you manually expand the macro. Just
                // because `bon` developers need to expand the macros a lot it makes
                // sense to just silence it to avoid some noise. This lint is triggered
                // by the big PhantomData type generated by the macro
                clippy::type_complexity
            )]
            #builder_vis struct #builder_ident<
                #(#generics_decl,)*
                // Having the `State` trait bound on the struct declaration is important
                // for future proofing. It will allow us to use this bound in the `Drop`
                // implementation of the builder if we ever add one. @Veetaha already did
                // some experiments with `MaybeUninit` that requires a custom drop impl,
                // so this could be useful in the future.
                //
                // On the flip side, if we have a custom `Drop` impl, then partially moving
                // the builder will be impossible. So.. it's a trade-off, and it's probably
                // not a big deal to remove this bound from here if we feel like it.
                #state_var: #state_mod::State = #state_mod::Empty
            >
            #where_clause
            {
                #private_field_attrs
                #phantom_field: #phantom_data,

                #receiver_field
                #start_fn_args_field

                #( #custom_fields_idents: #custom_fields_types, )*

                #private_field_attrs
                #named_members_field: (
                    #(
                        ::core::option::Option<#named_members_types>,
                    )*
                ),
            }
        }
    }
}