bon_macros/builder/builder_gen/
builder_decl.rs

1use crate::builder::builder_gen::NamedMember;
2use crate::util::prelude::*;
3
4impl super::BuilderGenCtx {
5    pub(super) fn builder_decl(&self) -> TokenStream {
6        let builder_vis = &self.builder_type.vis;
7        let builder_ident = &self.builder_type.ident;
8        let generics_decl = &self.generics.decl_with_defaults;
9        let where_clause = &self.generics.where_clause;
10        let phantom_data = self.phantom_data();
11        let state_mod = &self.state_mod.ident;
12
13        // The fields can't be hidden using Rust's privacy syntax.
14        // The details about this are described in the blog post:
15        // https://bon-rs.com/blog/the-weird-of-function-local-types-in-rust.
16        //
17        // We could use `#[cfg(not(rust_analyzer))]` to hide the private fields in IDE.
18        // However, RA would then not be able to type-check the generated code, which
19        // may or may not be a problem, because the main thing is that the type signatures
20        // would still work in RA.
21        let private_field_attrs = {
22            // The message is defined separately to make it single-line in the
23            // generated code. This simplifies the task of removing unnecessary
24            // attributes from the generated code when preparing for demo purposes.
25            let deprecated_msg = "\
26                this field should not be used directly; it's an implementation detail, and \
27                if you access it directly, you may break some internal unsafe invariants; \
28                if you found yourself needing it, then you are probably doing something wrong; \
29                feel free to open an issue/discussion in our GitHub repository \
30                (https://github.com/elastio/bon) or ask for help in our Discord server \
31                (https://bon-rs.com/discord)";
32
33            quote! {
34                #[doc(hidden)]
35                #[deprecated = #deprecated_msg]
36            }
37        };
38
39        let receiver_field = self.receiver().map(|receiver| {
40            let ty = &receiver.without_self_keyword;
41            quote! {
42                #private_field_attrs
43                __unsafe_private_receiver: #ty,
44            }
45        });
46
47        let must_use_message = format!(
48            "the builder does nothing until you call `{}()` on it to finish building",
49            self.finish_fn.ident
50        );
51
52        let allows = super::allow_warnings_on_member_types();
53
54        let mut start_fn_arg_types = self
55            .start_fn_args()
56            .map(|member| &member.base.ty.norm)
57            .peekable();
58
59        let start_fn_args_field = start_fn_arg_types.peek().is_some().then(|| {
60            quote! {
61                #private_field_attrs
62                __unsafe_private_start_fn_args: (#(#start_fn_arg_types,)*),
63            }
64        });
65
66        let named_members_types = self.named_members().map(NamedMember::underlying_norm_ty);
67
68        let docs = &self.builder_type.docs;
69        let state_var = &self.state_var;
70
71        let custom_fields_idents = self.custom_fields().map(|field| &field.ident);
72        let custom_fields_types = self.custom_fields().map(|field| &field.norm_ty);
73
74        quote! {
75            #[must_use = #must_use_message]
76            #(#docs)*
77            #allows
78            #[allow(
79                // We use `__private` prefix for all fields intentionally to hide them
80                clippy::struct_field_names,
81
82                // This lint doesn't emerge until you manually expand the macro. Just
83                // because `bon` developers need to expand the macros a lot it makes
84                // sense to just silence it to avoid some noise. This lint is triggered
85                // by the big PhantomData type generated by the macro
86                clippy::type_complexity
87            )]
88            #builder_vis struct #builder_ident<
89                #(#generics_decl,)*
90                // Having the `State` trait bound on the struct declaration is important
91                // for future proofing. It will allow us to use this bound in the `Drop`
92                // implementation of the builder if we ever add one. @Veetaha already did
93                // some experiments with `MaybeUninit` that requires a custom drop impl,
94                // so this could be useful in the future.
95                //
96                // On the flip side, if we have a custom `Drop` impl, then partially moving
97                // the builder will be impossible. So.. it's a trade-off, and it's probably
98                // not a big deal to remove this bound from here if we feel like it.
99                #state_var: #state_mod::State = #state_mod::Empty
100            >
101            #where_clause
102            {
103                #private_field_attrs
104                __unsafe_private_phantom: #phantom_data,
105
106                #receiver_field
107                #start_fn_args_field
108
109                #( #custom_fields_idents: #custom_fields_types, )*
110
111                #private_field_attrs
112                __unsafe_private_named: (
113                    #(
114                        ::core::option::Option<#named_members_types>,
115                    )*
116                ),
117            }
118        }
119    }
120}