1use super::BuilderGenCtx;
2use crate::util::prelude::*;
34pub(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}
1112impl<'a> StateModGenCtx<'a> {
13pub(super) fn new(builder_gen: &'a BuilderGenCtx) -> Self {
14Self {
15 base: builder_gen,
1617 stateful_members_snake: builder_gen
18 .stateful_members()
19 .map(|member| &member.name.snake)
20 .collect(),
2122 stateful_members_pascal: builder_gen
23 .stateful_members()
24 .map(|member| &member.name.pascal)
25 .collect(),
2627// A const item in a trait makes it non-object safe, which is convenient,
28 // because we want that restriction in this case.
29sealed_item_decl: quote! {
30#[doc(hidden)]
31const SEALED: sealed::Sealed;
32 },
3334 sealed_item_impl: quote! {
35const SEALED: sealed::Sealed = sealed::Sealed;
36 },
37 }
38 }
3940pub(super) fn state_mod(&self) -> TokenStream {
41let bon = &self.base.bon;
42let vis = &self.base.state_mod.vis;
43let vis_child = &self.base.state_mod.vis_child;
44let vis_child_child = &self.base.state_mod.vis_child_child;
4546let state_mod_docs = &self.base.state_mod.docs;
47let state_mod_ident = &self.base.state_mod.ident;
4849let state_trait = self.state_trait();
50let is_complete_trait = self.is_complete_trait();
51let members_names_mod = self.members_names_mod();
52let state_transitions = self.state_transitions();
5354quote! {
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.
67unnameable_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};
73use #bon::__::{Set, Unset};
7475mod sealed {
76 #vis_child_child struct Sealed;
77 }
7879 #state_trait
80 #is_complete_trait
81 #members_names_mod
82 #state_transitions
83 }
84 }
85 }
8687fn 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.
91let mut set_members_structs = Vec::with_capacity(self.stateful_members_snake.len());
92let mut state_impls = Vec::with_capacity(self.stateful_members_snake.len());
9394let vis_child = &self.base.state_mod.vis_child;
95let sealed_item_impl = &self.sealed_item_impl;
9697for member in self.base.stateful_members() {
98let member_pascal = &member.name.pascal;
99100let 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 );
104105let struct_ident = format_ident!("Set{}", member.name.pascal_str);
106107 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 });
117118let states = self.base.stateful_members().map(|other_member| {
119if other_member.is(member) {
120let member_snake = &member.name.snake;
121quote! {
122 Set<members::#member_snake>
123 }
124 } else {
125let member_pascal = &other_member.name.pascal;
126quote! {
127 S::#member_pascal
128 }
129 }
130 });
131132let stateful_members_pascal = &self.stateful_members_pascal;
133134 state_impls.push(quote! {
135#[doc(hidden)]
136impl<S: State> State for #struct_ident<S> {
137 #(
138type #stateful_members_pascal = #states;
139 )*
140 #sealed_item_impl
141 }
142 });
143 }
144145let stateful_members_snake = &self.stateful_members_snake;
146let stateful_members_pascal = &self.stateful_members_pascal;
147148quote! {
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(());
153154 #( #set_members_structs )*
155156#[doc(hidden)]
157impl State for Empty {
158 #(
159type #stateful_members_pascal = Unset<members::#stateful_members_snake>;
160 )*
161 #sealed_item_impl
162 }
163164 #( #state_impls )*
165166 }
167 }
168169fn state_trait(&self) -> TokenStream {
170let assoc_types_docs = self.stateful_members_snake.iter().map(|member_snake| {
171format!(
172"Type state of the member `{member_snake}`.\n\
173 \n\
174 It can implement either [`IsSet`] or [`IsUnset`]",
175 )
176 });
177178let vis_child = &self.base.state_mod.vis_child;
179let sealed_item_decl = &self.sealed_item_decl;
180let stateful_members_pascal = &self.stateful_members_pascal;
181182let 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};
190191let docs = format!(
192"Builder's type state specifies if members are set or not (unset).{docs_suffix}"
193);
194195quote! {
196#[doc = #docs]
197#vis_child trait State: ::core::marker::Sized {
198 #(
199#[doc = #assoc_types_docs]
200type #stateful_members_pascal;
201 )*
202 #sealed_item_decl
203 }
204 }
205 }
206207fn is_complete_trait(&self) -> TokenStream {
208let required_members_pascal = self
209.base
210 .named_members()
211 .filter(|member| member.is_required())
212 .map(|member| &member.name.pascal)
213 .collect::<Vec<_>>();
214215// 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`.
219let maybe_assoc_type_bounds = cfg!(feature = "implied-bounds").then(|| {
220quote! {
221 < #( #required_members_pascal: IsSet, )* >
222 }
223 });
224225let vis_child = &self.base.state_mod.vis_child;
226let sealed_item_decl = &self.sealed_item_decl;
227let sealed_item_impl = &self.sealed_item_impl;
228229let builder_ident = &self.base.builder_type.ident;
230let finish_fn = &self.base.finish_fn.ident;
231232let 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 );
237238quote! {
239#[doc = #docs]
240#vis_child trait IsComplete: State #maybe_assoc_type_bounds {
241 #sealed_item_decl
242 }
243244#[doc(hidden)]
245impl<S: State> IsComplete for S
246where
247#(
248 S::#required_members_pascal: IsSet,
249 )*
250 {
251 #sealed_item_impl
252 }
253 }
254 }
255256fn members_names_mod(&self) -> TokenStream {
257let vis_child_child = &self.base.state_mod.vis_child_child;
258let stateful_members_snake = &self.stateful_members_snake;
259260// 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.
263let 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";
267268quote! {
269#[deprecated = #deprecated_msg]
270 #[doc(hidden)]
271 #[allow(non_camel_case_types)]
272mod members {
273 #(
274 #vis_child_child struct #stateful_members_snake(());
275 )*
276 }
277 }
278 }
279}