Skip to main content

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 ident = &receiver.field_ident;
41            let ty = &receiver.without_self_keyword;
42            quote! {
43                /// Value of `self` passed to the starting method, that created the builder
44                #ident: #ty,
45            }
46        });
47
48        let must_use_message = format!(
49            "the builder does nothing until you call `{}()` on it to finish building",
50            self.finish_fn.ident
51        );
52
53        let allows = super::allow_warnings_on_member_types();
54
55        let start_fn_args_fields = self.start_fn_args().map(|member| {
56            let ident = &member.ident;
57            let ty = &member.ty.norm;
58            let doc = format!(
59                "Value of `{ident}` passed as an argument to the starting function,
60                that created the builder",
61            );
62            quote! {
63                #[doc = #doc]
64                #ident: #ty
65            }
66        });
67
68        let named_members_types = self.named_members().map(NamedMember::underlying_norm_ty);
69
70        let docs = &self.builder_type.docs;
71        let state_var = &self.state_var;
72
73        let custom_fields_idents = self.custom_fields().map(|field| &field.ident);
74        let custom_fields_types = self.custom_fields().map(|field| &field.norm_ty);
75
76        quote! {
77            #[must_use = #must_use_message]
78            #(#docs)*
79            #allows
80            #[allow(
81                // We use `__private` prefix for all fields intentionally to hide them
82                clippy::struct_field_names,
83
84                // This lint doesn't emerge until you manually expand the macro. Just
85                // because `bon` developers need to expand the macros a lot it makes
86                // sense to just silence it to avoid some noise. This lint is triggered
87                // by the big PhantomData type generated by the macro
88                clippy::type_complexity
89            )]
90            #builder_vis struct #builder_ident<
91                #(#generics_decl,)*
92                // Having the `State` trait bound on the struct declaration is important
93                // for future proofing. It will allow us to use this bound in the `Drop`
94                // implementation of the builder if we ever add one. @Veetaha already did
95                // some experiments with `MaybeUninit` that requires a custom drop impl,
96                // so this could be useful in the future.
97                //
98                // On the flip side, if we have a custom `Drop` impl, then partially moving
99                // the builder will be impossible. So.. it's a trade-off, and it's probably
100                // not a big deal to remove this bound from here if we feel like it.
101                #state_var: #state_mod::State = #state_mod::Empty
102            >
103            #where_clause
104            {
105                #private_field_attrs
106                __unsafe_private_phantom: #phantom_data,
107
108                #receiver_field
109
110                #( #start_fn_args_fields, )*
111
112                #( #custom_fields_idents: #custom_fields_types, )*
113
114                #private_field_attrs
115                __unsafe_private_named: (
116                    #(
117                        ::core::option::Option<#named_members_types>,
118                    )*
119                ),
120            }
121        }
122    }
123}