bon_macros/builder/builder_gen/
mod.rs

1mod builder_decl;
2mod builder_derives;
3mod finish_fn;
4mod getters;
5mod member;
6mod models;
7mod setters;
8mod start_fn;
9mod state_mod;
10mod top_level_config;
11
12pub(crate) mod input_fn;
13pub(crate) mod input_struct;
14pub(crate) use top_level_config::TopLevelConfig;
15
16use crate::util::prelude::*;
17use getters::GettersCtx;
18use member::{CustomField, Member, MemberOrigin, NamedMember, RawMember, StartFnMember};
19use models::{AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFnBody, Generics};
20use setters::SettersCtx;
21
22pub(crate) struct MacroOutput {
23    pub(crate) start_fn: syn::ItemFn,
24    pub(crate) other_items: TokenStream,
25}
26
27impl BuilderGenCtx {
28    fn receiver(&self) -> Option<&AssocMethodReceiverCtx> {
29        self.assoc_method_ctx.as_ref()?.receiver.as_ref()
30    }
31
32    fn named_members(&self) -> impl Iterator<Item = &NamedMember> {
33        self.members.iter().filter_map(Member::as_named)
34    }
35
36    fn custom_fields(&self) -> impl Iterator<Item = &CustomField> {
37        self.members.iter().filter_map(Member::as_field)
38    }
39
40    fn start_fn_args(&self) -> impl Iterator<Item = &StartFnMember> {
41        self.members.iter().filter_map(Member::as_start_fn)
42    }
43
44    fn stateful_members(&self) -> impl Iterator<Item = &NamedMember> {
45        self.named_members().filter(|member| member.is_stateful())
46    }
47
48    pub(crate) fn output(self) -> Result<MacroOutput> {
49        let mut start_fn = self.start_fn();
50        let state_mod = state_mod::StateModGenCtx::new(&self).state_mod();
51        let builder_decl = self.builder_decl();
52        let builder_impl = self.builder_impl()?;
53        let builder_derives = self.builder_derives();
54
55        let default_allows = syn::parse_quote!(#[allow(
56            // We have a `deprecated` lint on all `bon::__` items which we
57            // use in the generated code extensively
58            deprecated
59        )]);
60
61        let allows = self.allow_attrs.iter().cloned().chain([default_allows]);
62
63        // -- Postprocessing --
64        // Here we parse all items back and add the `allow` attributes to them.
65        let other_items = quote! {
66            #state_mod
67            #builder_decl
68            #builder_derives
69            #builder_impl
70        };
71
72        let other_items_str = other_items.to_string();
73
74        let other_items: syn::File = syn::parse2(other_items).map_err(|err| {
75            err!(
76                &Span::call_site(),
77                "bug in the `bon` crate: the macro generated code that contains syntax errors; \
78                please report this issue at our Github repository: \
79                https://github.com/elastio/bon;\n\
80                syntax error in generated code: {err:#?};\n\
81                generated code:\n\
82                ```rust
83                {other_items_str}\n\
84                ```",
85            )
86        })?;
87
88        let mut other_items = other_items.items;
89
90        for item in &mut other_items {
91            if let Some(attrs) = item.attrs_mut() {
92                attrs.extend(allows.clone());
93            }
94        }
95
96        start_fn.attrs.extend(allows);
97
98        Ok(MacroOutput {
99            start_fn,
100            other_items: quote!(#(#other_items)*),
101        })
102    }
103
104    fn builder_impl(&self) -> Result<TokenStream> {
105        let finish_fn = self.finish_fn();
106        let accessor_methods = self
107            .named_members()
108            .map(|member| {
109                let setters = SettersCtx::new(self, member).setter_methods()?;
110                let getters = GettersCtx::new(self, member)
111                    .map(GettersCtx::getter_methods)
112                    .transpose()?
113                    .unwrap_or_default();
114
115                // Output all accessor methods for the same member adjecently.
116                // This is important in the generated rustdoc output, because
117                // rustdoc lists methods in the order they appear in the source.
118                Ok([setters, getters])
119            })
120            .collect::<Result<Vec<_>>>()?
121            .into_iter()
122            .flatten();
123
124        let generics_decl = &self.generics.decl_without_defaults;
125        let generic_args = &self.generics.args;
126        let where_clause = &self.generics.where_clause;
127        let builder_ident = &self.builder_type.ident;
128        let state_mod = &self.state_mod.ident;
129        let state_var = &self.state_var;
130
131        let allows = allow_warnings_on_member_types();
132
133        Ok(quote! {
134            #allows
135            #[automatically_derived]
136            impl<
137                #(#generics_decl,)*
138                #state_var: #state_mod::State
139            >
140            #builder_ident<#(#generic_args,)* #state_var>
141            #where_clause
142            {
143                #finish_fn
144                #(#accessor_methods)*
145            }
146        })
147    }
148
149    /// Generates code that has no meaning to the compiler, but it helps
150    /// IDEs to provide better code highlighting, completions and other
151    /// hints.
152    fn ide_hints(&self) -> TokenStream {
153        let type_patterns = self
154            .on
155            .iter()
156            .map(|params| &params.type_pattern)
157            .collect::<Vec<_>>();
158
159        if type_patterns.is_empty() {
160            return quote! {};
161        }
162
163        quote! {
164            // This is wrapped in a special cfg set by `rust-analyzer` to enable this
165            // code for rust-analyzer's analysis only, but prevent the code from being
166            // compiled by `rustc`. Rust Analyzer should be able to use the syntax
167            // provided inside of the block to figure out the semantic meaning of
168            // the tokens passed to the attribute.
169            #[allow(unexpected_cfgs)]
170            {
171                #[cfg(rust_analyzer)]
172                {
173                    // Let IDEs know that these are type patterns like the ones that
174                    // could be written in a type annotation for a variable. Note that
175                    // we don't initialize the variable with any value because we don't
176                    // have any meaningful value to assign to this variable, especially
177                    // because its type may contain wildcard patterns like `_`. This is
178                    // used only to signal the IDEs that these tokens are meant to be
179                    // type patterns by placing them in the context where type patterns
180                    // are expected.
181                    let _: (#(#type_patterns,)*);
182                }
183            }
184        }
185    }
186
187    fn phantom_data(&self) -> TokenStream {
188        let member_types = self.members.iter().filter_map(|member| {
189            match member {
190                // The types of these members already appear in the struct as regular fields.
191                Member::StartFn(_) | Member::Field(_) | Member::Named(_) => None,
192                Member::FinishFn(member) => Some(member.ty.norm.as_ref()),
193                Member::Skip(member) => Some(member.norm_ty.as_ref()),
194            }
195        });
196
197        let receiver_ty = self
198            .assoc_method_ctx
199            .as_ref()
200            .map(|ctx| ctx.self_ty.as_ref());
201
202        let generic_types = self.generics.args.iter().filter_map(|arg| match arg {
203            syn::GenericArgument::Type(ty) => Some(ty),
204            _ => None,
205        });
206
207        let types = std::iter::empty()
208            .chain(receiver_ty)
209            .chain(member_types)
210            .chain(generic_types)
211            .map(|ty| {
212                // Wrap `ty` in another phantom data because it can be `?Sized`,
213                // and simply using it as a type of the tuple member would
214                // be wrong, because tuple's members must be sized.
215                //
216                // We also wrap this in an `fn() -> ...` to make the compiler think
217                // that the builder doesn't "own" an instance of the given type.
218                // This removes unnecessary requirements when evaluating the
219                // applicability of the auto traits.
220                quote!(fn() -> ::core::marker::PhantomData<#ty>)
221            });
222
223        let lifetimes = self.generics.args.iter().filter_map(|arg| match arg {
224            syn::GenericArgument::Lifetime(lifetime) => Some(lifetime),
225            _ => None,
226        });
227
228        let state_var = &self.state_var;
229
230        quote! {
231            ::core::marker::PhantomData<(
232                // We have to store the builder state in phantom data otherwise it
233                // would be reported as an unused type parameter.
234                //
235                // We also wrap this in an `fn() -> ...` to make the compiler think
236                // that the builder doesn't "own" an instance of the given type.
237                // This removes unnecessary requirements when evaluating the
238                // applicability of the auto traits.
239                fn() -> #state_var,
240
241                // Even though lifetimes will most likely be used somewhere in
242                // member types, it is not guaranteed in case of functions/methods,
243                // so we mention them all separately. This covers a special case
244                // for function builders where the lifetime can be entirely unused
245                // (the language permis that).
246                //
247                // This edge case was discovered thanks to @tonywu6 ❤️:
248                // https://github.com/elastio/bon/issues/206
249                #( &#lifetimes (), )*
250
251                // There is an interesting quirk with lifetimes in Rust, which is the
252                // reason why we thoughtlessly store all the function parameter types
253                // in phantom data here.
254                //
255                // Suppose a function was defined with an argument of type `&'a T`
256                // and then we generate an impl block (simplified):
257                //
258                // ```
259                // impl<'a, T, U> for Foo<U>
260                // where
261                //     U: Into<&'a T>,
262                // {}
263                // ```
264                // Then compiler will complain with the message "the parameter type `T`
265                // may not live long enough". So we would need to manually add the bound
266                // `T: 'a` to fix this. However, it's hard to infer such a bound in macro
267                // context. A workaround for that would be to store the `&'a T` inside of
268                // the struct itself, which auto-implies this bound for us implicitly.
269                //
270                // That's a weird implicit behavior in Rust, I suppose there is a reasonable
271                // explanation for it, I just didn't care to research it yet ¯\_(ツ)_/¯.
272                #(#types,)*
273            )>
274        }
275    }
276}
277
278fn allow_warnings_on_member_types() -> TokenStream {
279    quote! {
280        // This warning may occur when the original unnormalized syntax was
281        // using parens around an `impl Trait` like that:
282        // ```
283        // &(impl Clone + Default)
284        // ```
285        // in which case the normalized version will be:
286        // ```
287        // &(T)
288        // ```
289        //
290        // And it triggers the warning. We just suppress it here.
291        #[allow(unused_parens)]
292    }
293}