bon_macros/builder/builder_gen/
state_mod.rs

1use super::BuilderGenCtx;
2use crate::util::prelude::*;
3
4pub(super) struct StateModGenCtx<'a> {
5    base: &'a BuilderGenCtx,
6    stateful_members_snake: Vec<&'a syn::Ident>,
7    stateful_members_pascal: Vec<&'a syn::Ident>,
8    sealed_item_decl: TokenStream,
9    sealed_item_impl: TokenStream,
10}
11
12impl<'a> StateModGenCtx<'a> {
13    pub(super) fn new(builder_gen: &'a BuilderGenCtx) -> Self {
14        Self {
15            base: builder_gen,
16
17            stateful_members_snake: builder_gen
18                .stateful_members()
19                .map(|member| &member.name.snake)
20                .collect(),
21
22            stateful_members_pascal: builder_gen
23                .stateful_members()
24                .map(|member| &member.name.pascal)
25                .collect(),
26
27            // A const item in a trait makes it non-object safe, which is convenient,
28            // because we want that restriction in this case.
29            sealed_item_decl: quote! {
30                #[doc(hidden)]
31                const SEALED: sealed::Sealed;
32            },
33
34            sealed_item_impl: quote! {
35                const SEALED: sealed::Sealed = sealed::Sealed;
36            },
37        }
38    }
39
40    pub(super) fn state_mod(&self) -> TokenStream {
41        let bon = &self.base.bon;
42        let vis = &self.base.state_mod.vis;
43        let vis_child = &self.base.state_mod.vis_child;
44        let vis_child_child = &self.base.state_mod.vis_child_child;
45
46        let state_mod_docs = &self.base.state_mod.docs;
47        let state_mod_ident = &self.base.state_mod.ident;
48
49        let state_trait = self.state_trait();
50        let is_complete_trait = self.is_complete_trait();
51        let members_names_mod = self.members_names_mod();
52        let state_transitions = self.state_transitions();
53
54        quote! {
55            #[allow(
56                // These are intentional. By default, the builder module is private
57                // and can't be accessed outside of the module where the builder
58                // type is defined. This makes the builder type "anonymous" to
59                // the outside modules, which is a good thing if users don't want
60                // to expose this API surface.
61                //
62                // Also, there are some genuinely private items like the `Sealed`
63                // enum and members "name" enums that we don't want to expose even
64                // to the module that defines the builder. These APIs are not
65                // public, and users instead should only reference the traits
66                // and state transition type aliases from here.
67                unnameable_types, unreachable_pub, clippy::redundant_pub_crate
68            )]
69            #( #state_mod_docs )*
70            #vis mod #state_mod_ident {
71                #[doc(inline)]
72                #vis_child use #bon::__::{IsSet, IsUnset};
73                use #bon::__::{Set, Unset};
74
75                mod sealed {
76                    #vis_child_child struct Sealed;
77                }
78
79                #state_trait
80                #is_complete_trait
81                #members_names_mod
82                #state_transitions
83            }
84        }
85    }
86
87    fn state_transitions(&self) -> TokenStream {
88        // Not using `Iterator::zip` here to make it possible to scale this in
89        // case if we add more vecs here. We are not using `Itertools`, so
90        // its `multiunzip` is not available.
91        let mut set_members_structs = Vec::with_capacity(self.stateful_members_snake.len());
92        let mut state_impls = Vec::with_capacity(self.stateful_members_snake.len());
93
94        let vis_child = &self.base.state_mod.vis_child;
95        let sealed_item_impl = &self.sealed_item_impl;
96
97        for member in self.base.stateful_members() {
98            let member_pascal = &member.name.pascal;
99
100            let docs = format!(
101                "Represents a [`State`] that has [`IsSet`] implemented for [`State::{member_pascal}`].\n\n\
102                The state for all other members is left the same as in the input state.",
103            );
104
105            let struct_ident = format_ident!("Set{}", member.name.pascal_str);
106
107            set_members_structs.push(quote! {
108                #[doc = #docs]
109                #vis_child struct #struct_ident<S: State = Empty>(
110                    // We `S` in an `fn() -> ...` to make the compiler think
111                    // that the builder doesn't "own" an instance of `S`.
112                    // This removes unnecessary requirements when evaluating the
113                    // applicability of the auto traits.
114                    ::core::marker::PhantomData<fn() -> S>
115                );
116            });
117
118            let states = self.base.stateful_members().map(|other_member| {
119                if other_member.is(member) {
120                    let member_snake = &member.name.snake;
121                    quote! {
122                        Set<members::#member_snake>
123                    }
124                } else {
125                    let member_pascal = &other_member.name.pascal;
126                    quote! {
127                        S::#member_pascal
128                    }
129                }
130            });
131
132            let stateful_members_pascal = &self.stateful_members_pascal;
133
134            state_impls.push(quote! {
135                #[doc(hidden)]
136                impl<S: State> State for #struct_ident<S> {
137                    #(
138                        type #stateful_members_pascal = #states;
139                    )*
140                    #sealed_item_impl
141                }
142            });
143        }
144
145        let stateful_members_snake = &self.stateful_members_snake;
146        let stateful_members_pascal = &self.stateful_members_pascal;
147
148        quote! {
149            /// Represents a [`State`] that has [`IsUnset`] implemented for all members.
150            ///
151            /// This is the initial state of the builder before any setters are called.
152            #vis_child struct Empty(());
153
154            #( #set_members_structs )*
155
156            #[doc(hidden)]
157            impl State for Empty {
158                #(
159                    type #stateful_members_pascal = Unset<members::#stateful_members_snake>;
160                )*
161                #sealed_item_impl
162            }
163
164            #( #state_impls )*
165
166        }
167    }
168
169    fn state_trait(&self) -> TokenStream {
170        let assoc_types_docs = self.stateful_members_snake.iter().map(|member_snake| {
171            format!(
172                "Type state of the member `{member_snake}`.\n\
173                \n\
174                It can implement either [`IsSet`] or [`IsUnset`]",
175            )
176        });
177
178        let vis_child = &self.base.state_mod.vis_child;
179        let sealed_item_decl = &self.sealed_item_decl;
180        let stateful_members_pascal = &self.stateful_members_pascal;
181
182        let docs_suffix = if stateful_members_pascal.is_empty() {
183            ""
184        } else {
185            "\n\n\
186            You can use the associated types of this trait to control the state of individual members \
187            with the [`IsSet`] and [`IsUnset`] traits. You can change the state of the members with \
188            the `Set*` structs available in this module."
189        };
190
191        let docs = format!(
192            "Builder's type state specifies if members are set or not (unset).{docs_suffix}"
193        );
194
195        quote! {
196            #[doc = #docs]
197            #vis_child trait State: ::core::marker::Sized {
198                #(
199                    #[doc = #assoc_types_docs]
200                    type #stateful_members_pascal;
201                )*
202                #sealed_item_decl
203            }
204        }
205    }
206
207    fn is_complete_trait(&self) -> TokenStream {
208        let required_members_pascal = self
209            .base
210            .named_members()
211            .filter(|member| member.is_required())
212            .map(|member| &member.name.pascal)
213            .collect::<Vec<_>>();
214
215        // Associated types bounds syntax that provides implied bounds for them
216        // is available only since Rust 1.79.0. So this is an opt-in feature that
217        // bumps the MSRV of the crate. See more details in the comment on this
218        // cargo feature's declaration in `bon/Cargo.toml`.
219        let maybe_assoc_type_bounds = cfg!(feature = "implied-bounds").then(|| {
220            quote! {
221                < #( #required_members_pascal: IsSet, )* >
222            }
223        });
224
225        let vis_child = &self.base.state_mod.vis_child;
226        let sealed_item_decl = &self.sealed_item_decl;
227        let sealed_item_impl = &self.sealed_item_impl;
228
229        let builder_ident = &self.base.builder_type.ident;
230        let finish_fn = &self.base.finish_fn.ident;
231
232        let docs = format!(
233            "Marker trait that indicates that all required members are set.\n\n\
234            In this state, you can finish building by calling the method \
235            [`{builder_ident}::{finish_fn}()`](super::{builder_ident}::{finish_fn}())",
236        );
237
238        quote! {
239            #[doc = #docs]
240            #vis_child trait IsComplete: State #maybe_assoc_type_bounds {
241                #sealed_item_decl
242            }
243
244            #[doc(hidden)]
245            impl<S: State> IsComplete for S
246            where
247                #(
248                    S::#required_members_pascal: IsSet,
249                )*
250            {
251                #sealed_item_impl
252            }
253        }
254    }
255
256    fn members_names_mod(&self) -> TokenStream {
257        let vis_child_child = &self.base.state_mod.vis_child_child;
258        let stateful_members_snake = &self.stateful_members_snake;
259
260        // The message is defined separately to make it single-line in the
261        // generated code. This simplifies the task of removing unnecessary
262        // attributes from the generated code when preparing for demo purposes.
263        let deprecated_msg = "\
264            this should not be used directly; it is an implementation detail; \
265            use the Set* type aliases to control the \
266            state of members instead";
267
268        quote! {
269            #[deprecated = #deprecated_msg]
270            #[doc(hidden)]
271            #[allow(non_camel_case_types)]
272            mod members {
273                #(
274                    #vis_child_child struct #stateful_members_snake(());
275                )*
276            }
277        }
278    }
279}