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