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 #state_mod
71 #builder_decl
72 #builder_derives
73 #builder_impl
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 #[automatically_derived]
140 impl<
141 #(#generics_decl,)*
142 #state_var: #state_mod::State
143 >
144 #builder_ident<#(#generic_args,)* #state_var>
145 #where_clause
146 {
147 #finish_fn
148 #(#accessor_methods)*
149 }
150 })
151 }
152
153 /// Generates code that has no meaning to the compiler, but it helps
154 /// IDEs to provide better code highlighting, completions and other
155 /// hints.
156 fn ide_hints(&self) -> TokenStream {
157 let type_patterns = self
158 .on
159 .iter()
160 .map(|params| ¶ms.type_pattern)
161 .collect::<Vec<_>>();
162
163 if type_patterns.is_empty() {
164 return quote! {};
165 }
166
167 quote! {
168 // This is wrapped in a special cfg set by `rust-analyzer` to enable this
169 // code for rust-analyzer's analysis only, but prevent the code from being
170 // compiled by `rustc`. Rust Analyzer should be able to use the syntax
171 // provided inside of the block to figure out the semantic meaning of
172 // the tokens passed to the attribute.
173 #[allow(unexpected_cfgs)]
174 {
175 #[cfg(rust_analyzer)]
176 {
177 // Let IDEs know that these are type patterns like the ones that
178 // could be written in a type annotation for a variable. Note that
179 // we don't initialize the variable with any value because we don't
180 // have any meaningful value to assign to this variable, especially
181 // because its type may contain wildcard patterns like `_`. This is
182 // used only to signal the IDEs that these tokens are meant to be
183 // type patterns by placing them in the context where type patterns
184 // are expected.
185 let _: (#(#type_patterns,)*);
186 }
187 }
188 }
189 }
190
191 fn phantom_data(&self) -> TokenStream {
192 let member_types = self.members.iter().filter_map(|member| {
193 match member {
194 // The types of these members already appear in the struct as regular fields.
195 Member::StartFn(_) | Member::Field(_) | Member::Named(_) => None,
196 Member::FinishFn(member) => Some(member.ty.norm.as_ref()),
197 Member::Skip(member) => Some(member.norm_ty.as_ref()),
198 }
199 });
200
201 let receiver_ty = self
202 .assoc_method_ctx
203 .as_ref()
204 .map(|ctx| ctx.self_ty.as_ref());
205
206 let generic_types = self.generics.args.iter().filter_map(|arg| match arg {
207 syn::GenericArgument::Type(ty) => Some(ty),
208 _ => None,
209 });
210
211 let types = std::iter::empty()
212 .chain(receiver_ty)
213 .chain(member_types)
214 .chain(generic_types)
215 .map(|ty| {
216 // Wrap `ty` in another phantom data because it can be `?Sized`,
217 // and simply using it as a type of the tuple member would
218 // be wrong, because tuple's members must be sized.
219 //
220 // We also wrap this in an `fn() -> ...` to make the compiler think
221 // that the builder doesn't "own" an instance of the given type.
222 // This removes unnecessary requirements when evaluating the
223 // applicability of the auto traits.
224 quote!(fn() -> ::core::marker::PhantomData<#ty>)
225 });
226
227 let lifetimes = self.generics.args.iter().filter_map(|arg| match arg {
228 syn::GenericArgument::Lifetime(lifetime) => Some(lifetime),
229 _ => None,
230 });
231
232 let state_var = &self.state_var;
233
234 quote! {
235 ::core::marker::PhantomData<(
236 // We have to store the builder state in phantom data otherwise it
237 // would be reported as an unused type parameter.
238 //
239 // We also wrap this in an `fn() -> ...` to make the compiler think
240 // that the builder doesn't "own" an instance of the given type.
241 // This removes unnecessary requirements when evaluating the
242 // applicability of the auto traits.
243 fn() -> #state_var,
244
245 // Even though lifetimes will most likely be used somewhere in
246 // member types, it is not guaranteed in case of functions/methods,
247 // so we mention them all separately. This covers a special case
248 // for function builders where the lifetime can be entirely unused
249 // (the language permis that).
250 //
251 // This edge case was discovered thanks to @tonywu6 ❤️:
252 // https://github.com/elastio/bon/issues/206
253 #( &#lifetimes (), )*
254
255 // There is an interesting quirk with lifetimes in Rust, which is the
256 // reason why we thoughtlessly store all the function parameter types
257 // in phantom data here.
258 //
259 // Suppose a function was defined with an argument of type `&'a T`
260 // and then we generate an impl block (simplified):
261 //
262 // ```
263 // impl<'a, T, U> for Foo<U>
264 // where
265 // U: Into<&'a T>,
266 // {}
267 // ```
268 // Then compiler will complain with the message "the parameter type `T`
269 // may not live long enough". So we would need to manually add the bound
270 // `T: 'a` to fix this. However, it's hard to infer such a bound in macro
271 // context. A workaround for that would be to store the `&'a T` inside of
272 // the struct itself, which auto-implies this bound for us implicitly.
273 //
274 // That's a weird implicit behavior in Rust, I suppose there is a reasonable
275 // explanation for it, I just didn't care to research it yet ¯\_(ツ)_/¯.
276 #(#types,)*
277 )>
278 }
279 }
280}
281
282fn allow_warnings_on_member_types() -> TokenStream {
283 quote! {
284 // This warning may occur when the original unnormalized syntax was
285 // using parens around an `impl Trait` like that:
286 // ```
287 // &(impl Clone + Default)
288 // ```
289 // in which case the normalized version will be:
290 // ```
291 // &(T)
292 // ```
293 //
294 // And it triggers the warning. We just suppress it here.
295 #[allow(unused_parens)]
296 }
297}