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