1use super::models::FinishFnParams;
2use super::top_level_config::TopLevelConfig;
3use super::{
4 AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFnBody, Generics, Member,
5 MemberOrigin, RawMember,
6};
7use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams};
8use crate::normalization::{GenericsNamespace, NormalizeSelfTy, SyntaxVariant};
9use crate::parsing::{ItemSigConfig, SpannedKey};
10use crate::util::prelude::*;
11use std::borrow::Cow;
12use std::rc::Rc;
13use syn::punctuated::Punctuated;
14use syn::visit::Visit;
15use syn::visit_mut::VisitMut;
1617pub(crate) struct FnInputCtx<'a> {
18 namespace: &'a GenericsNamespace,
19 fn_item: SyntaxVariant<syn::ItemFn>,
20 impl_ctx: Option<Rc<ImplCtx>>,
21 config: TopLevelConfig,
2223 start_fn: StartFnParams,
24 self_ty_prefix: Option<String>,
25}
2627pub(crate) struct FnInputCtxParams<'a> {
28pub(crate) namespace: &'a GenericsNamespace,
29pub(crate) fn_item: SyntaxVariant<syn::ItemFn>,
30pub(crate) impl_ctx: Option<Rc<ImplCtx>>,
31pub(crate) config: TopLevelConfig,
32}
3334pub(crate) struct ImplCtx {
35pub(crate) self_ty: Box<syn::Type>,
36pub(crate) generics: syn::Generics,
3738/// Lint suppressions from the original item that will be inherited by all items
39 /// generated by the macro. If the original syntax used `#[expect(...)]`,
40 /// then it must be represented as `#[allow(...)]` here.
41pub(crate) allow_attrs: Vec<syn::Attribute>,
42}
4344impl<'a> FnInputCtx<'a> {
45pub(crate) fn new(params: FnInputCtxParams<'a>) -> Self {
46let start_fn = params.config.start_fn.clone();
4748let start_fn_ident = start_fn
49 .name
50 .map(SpannedKey::into_value)
51 .unwrap_or_else(|| {
52let fn_ident = ¶ms.fn_item.norm.sig.ident;
5354// Special case for the method named `new`. We rename it to `builder`
55 // since this is the name that is conventionally used by starting
56 // function in the builder pattern. We also want to make
57 // the `#[builder]` attribute on the method `new` fully compatible
58 // with deriving a builder from a struct.
59if params.impl_ctx.is_some() && fn_ident == "new" {
60 syn::Ident::new("builder", fn_ident.span())
61 } else {
62 fn_ident.clone()
63 }
64 });
6566let start_fn = StartFnParams {
67 ident: start_fn_ident,
6869 vis: start_fn.vis.map(SpannedKey::into_value),
7071 docs: start_fn
72 .docs
73 .map(SpannedKey::into_value)
74 .unwrap_or_else(|| {
75 params
76 .fn_item
77 .norm
78 .attrs
79 .iter()
80 .filter(|attr| attr.is_doc_expr())
81 .cloned()
82 .collect()
83 }),
8485// Override on the start fn to use the generics from the
86 // target function itself. We must not duplicate the generics
87 // from the impl block here
88generics: Some(Generics::new(
89 params
90 .fn_item
91 .norm
92 .sig
93 .generics
94 .params
95 .iter()
96 .cloned()
97 .collect(),
98 params.fn_item.norm.sig.generics.where_clause.clone(),
99 )),
100 };
101102let self_ty_prefix = params.impl_ctx.as_deref().and_then(|impl_ctx| {
103let prefix = impl_ctx
104 .self_ty
105 .as_path()?
106.path
107 .segments
108 .last()?
109.ident
110 .to_string();
111112Some(prefix)
113 });
114115Self {
116 namespace: params.namespace,
117 fn_item: params.fn_item,
118 impl_ctx: params.impl_ctx,
119 config: params.config,
120 self_ty_prefix,
121 start_fn,
122 }
123 }
124125fn assoc_method_ctx(&self) -> Result<Option<AssocMethodCtx>> {
126let self_ty = match self.impl_ctx.as_deref() {
127Some(impl_ctx) => impl_ctx.self_ty.clone(),
128None => return Ok(None),
129 };
130131Ok(Some(AssocMethodCtx {
132 self_ty,
133 receiver: self.assoc_method_receiver_ctx()?,
134 }))
135 }
136137fn assoc_method_receiver_ctx(&self) -> Result<Option<AssocMethodReceiverCtx>> {
138let receiver = match self.fn_item.norm.sig.receiver() {
139Some(receiver) => receiver,
140None => return Ok(None),
141 };
142143let builder_attr_on_receiver = receiver
144 .attrs
145 .iter()
146 .find(|attr| attr.path().is_ident("builder"));
147148if let Some(attr) = builder_attr_on_receiver {
149bail!(
150 attr,
151"#[builder] attributes on the receiver are not supported"
152);
153 }
154155let self_ty = match self.impl_ctx.as_deref() {
156Some(impl_ctx) => &impl_ctx.self_ty,
157None => return Ok(None),
158 };
159160let mut without_self_keyword = receiver.ty.clone();
161162 NormalizeSelfTy { self_ty }.visit_type_mut(&mut without_self_keyword);
163164Ok(Some(AssocMethodReceiverCtx {
165 with_self_keyword: receiver.clone(),
166 without_self_keyword,
167 }))
168 }
169170fn generics(&self) -> Generics {
171let impl_ctx = self.impl_ctx.as_ref();
172let norm_fn_params = &self.fn_item.norm.sig.generics.params;
173let params = impl_ctx
174 .map(|impl_ctx| merge_generic_params(&impl_ctx.generics.params, norm_fn_params))
175 .unwrap_or_else(|| norm_fn_params.iter().cloned().collect());
176177let where_clauses = [
178self.fn_item.norm.sig.generics.where_clause.clone(),
179 impl_ctx.and_then(|impl_ctx| impl_ctx.generics.where_clause.clone()),
180 ];
181182let where_clause = where_clauses
183 .into_iter()
184 .flatten()
185 .reduce(|mut combined, clause| {
186 combined.predicates.extend(clause.predicates);
187 combined
188 });
189190 Generics::new(params, where_clause)
191 }
192193pub(crate) fn adapted_fn(&self) -> Result<syn::ItemFn> {
194let mut orig = self.fn_item.orig.clone();
195196if let Some(name) = self.config.start_fn.name.as_deref() {
197if *name == orig.sig.ident {
198bail!(
199&name,
200"the starting function name must be different from the name \
201 of the positional function under the #[builder] attribute"
202)
203 }
204 } else {
205// By default the original positional function becomes hidden.
206orig.vis = syn::Visibility::Inherited;
207208// We don't use a random name here because the name of this function
209 // can be used by other macros that may need a stable identifier.
210 // For example, if `#[tracing::instrument]` is placed on the function,
211 // the function name will be used as a span name. The name of the span
212 // may be indexed in some logs database (e.g. Grafana Loki). If the name
213 // of the span changes the DB index may grow and also log queries won't
214 // be stable.
215orig.sig.ident = format_ident!("__orig_{}", orig.sig.ident.raw_name());
216217// Remove all doc comments from the function itself to avoid docs duplication
218 // which may lead to duplicating doc tests, which in turn implies repeated doc
219 // tests execution, which means worse tests performance.
220 //
221 // We don't do this for the case when the positional function is exposed
222 // alongside the builder which implies that the docs should be visible
223 // as the function itself is visible.
224orig.attrs.retain(|attr| !attr.is_doc_expr());
225226 orig.attrs.extend([syn::parse_quote!(#[doc(hidden)])]);
227 }
228229// Remove any `#[builder]` attributes that were meant for this proc macro.
230orig.attrs.retain(|attr| !attr.path().is_ident("builder"));
231232// Remove all doc comments attributes from function arguments, because they are
233 // not valid in that position in regular Rust code. The cool trick is that they
234 // are still valid syntactically when a proc macro like this one pre-processes
235 // them and removes them from the expanded code. We use the doc comments to put
236 // them on the generated setter methods.
237 //
238 // We also strip all `builder(...)` attributes because this macro processes them
239 // and they aren't needed in the output.
240for arg in &mut orig.sig.inputs {
241 arg.attrs_mut()
242 .retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder"));
243 }
244245 orig.attrs.push(syn::parse_quote!(#[allow(
246// It's fine if there are too many positional arguments in the function
247 // because the whole purpose of this macro is to fight with this problem
248 // at the call site by generating a builder, while keeping the fn definition
249 // site the same with tons of positional arguments which don't harm readability
250 // there because their names are explicitly specified at the definition site.
251clippy::too_many_arguments,
252253// It's fine to use many bool arguments in the function signature because
254 // all of them will be named at the call site when the builder is used.
255clippy::fn_params_excessive_bools,
256 )]));
257258Ok(orig)
259 }
260261pub(crate) fn into_builder_gen_ctx(self) -> Result<BuilderGenCtx> {
262let assoc_method_ctx = self.assoc_method_ctx()?;
263264if self.impl_ctx.is_none() {
265let explanation = "\
266 but #[bon] attribute is absent on top of the impl block; this \
267 additional #[bon] attribute on the impl block is required for \
268 the macro to see the type of `Self` and properly generate \
269 the builder struct definition adjacently to the impl block.";
270271if let Some(receiver) = &self.fn_item.orig.sig.receiver() {
272bail!(
273&receiver.self_token,
274"function contains a `self` parameter {explanation}"
275);
276 }
277278let mut ctx = FindSelfReference::default();
279 ctx.visit_item_fn(&self.fn_item.orig);
280if let Some(self_span) = ctx.self_span {
281bail!(
282&self_span,
283"function contains a `Self` type reference {explanation}"
284);
285 }
286 }
287288let members = self
289.fn_item
290 .apply_ref(|fn_item| fn_item.sig.inputs.iter().filter_map(syn::FnArg::as_typed))
291 .into_iter()
292 .map(|arg| {
293let pat = match arg.norm.pat.as_ref() {
294 syn::Pat::Ident(pat) => pat,
295_ => bail!(
296&arg.orig.pat,
297"use a simple `identifier: type` syntax for the function argument; \
298 destructuring patterns in arguments aren't supported by the `#[builder]`",
299 ),
300 };
301302let ty = SyntaxVariant {
303 norm: arg.norm.ty.clone(),
304 orig: arg.orig.ty.clone(),
305 };
306307Ok(RawMember {
308 attrs: &arg.norm.attrs,
309 ident: pat.ident.clone(),
310 ty,
311 })
312 })
313 .collect::<Result<Vec<_>>>()?;
314315let members = Member::from_raw(&self.config.on, MemberOrigin::FnArg, members)?;
316317let generics = self.generics();
318319let finish_fn_body = FnCallBody {
320 sig: self.adapted_fn()?.sig,
321 impl_ctx: self.impl_ctx.clone(),
322 };
323324let ItemSigConfig {
325 name: finish_fn_ident,
326 vis: finish_fn_vis,
327 docs: finish_fn_docs,
328 } = self.config.finish_fn;
329330let is_special_builder_method = self.impl_ctx.is_some()
331 && (self.fn_item.norm.sig.ident == "new" || self.fn_item.norm.sig.ident == "builder");
332333let finish_fn_ident = finish_fn_ident
334 .map(SpannedKey::into_value)
335 .unwrap_or_else(|| {
336// For `builder` methods the `build` finisher is more conventional
337if is_special_builder_method {
338format_ident!("build")
339 } else {
340format_ident!("call")
341 }
342 });
343344let finish_fn_docs = finish_fn_docs
345 .map(SpannedKey::into_value)
346 .unwrap_or_else(|| {
347vec![syn::parse_quote! {
348/// Finishes building and performs the requested action.
349}]
350 });
351352let finish_fn = FinishFnParams {
353 ident: finish_fn_ident,
354 vis: finish_fn_vis.map(SpannedKey::into_value),
355 unsafety: self.fn_item.norm.sig.unsafety,
356 asyncness: self.fn_item.norm.sig.asyncness,
357 must_use: get_must_use_attribute(&self.fn_item.norm.attrs)?,
358 body: Box::new(finish_fn_body),
359 output: self.fn_item.norm.sig.output,
360 attrs: finish_fn_docs,
361 };
362363let fn_allows = self
364.fn_item
365 .norm
366 .attrs
367 .iter()
368 .filter_map(syn::Attribute::to_allow);
369370let allow_attrs = self
371.impl_ctx
372 .as_ref()
373 .into_iter()
374 .flat_map(|impl_ctx| impl_ctx.allow_attrs.iter().cloned())
375 .chain(fn_allows)
376 .collect();
377378let builder_ident = || {
379let user_override = self.config.builder_type.name.map(SpannedKey::into_value);
380381if let Some(user_override) = user_override {
382return user_override;
383 }
384385let ty_prefix = self.self_ty_prefix.unwrap_or_default();
386387// A special case for the `new` or `builder` method.
388 // We don't insert the `Builder` suffix in this case because
389 // this special case should be compatible with deriving
390 // a builder from a struct.
391 //
392 // We can arrive inside of this branch only if the function under
393 // the macro is called `new` or `builder`.
394if is_special_builder_method {
395return format_ident!("{ty_prefix}Builder");
396 }
397398let pascal_case_fn = self.fn_item.norm.sig.ident.snake_to_pascal_case();
399400format_ident!("{ty_prefix}{pascal_case_fn}Builder")
401 };
402403let builder_type = BuilderTypeParams {
404 ident: builder_ident(),
405 derives: self.config.derive,
406 docs: self.config.builder_type.docs.map(SpannedKey::into_value),
407 vis: self.config.builder_type.vis.map(SpannedKey::into_value),
408 };
409410 BuilderGenCtx::new(BuilderGenCtxParams {
411 bon: self.config.bon,
412 namespace: Cow::Borrowed(self.namespace),
413 members,
414415 allow_attrs,
416417 on: self.config.on,
418419 assoc_method_ctx,
420 generics,
421 orig_item_vis: self.fn_item.norm.vis,
422423 builder_type,
424 state_mod: self.config.state_mod,
425 start_fn: self.start_fn,
426 finish_fn,
427 })
428 }
429}
430431struct FnCallBody {
432 sig: syn::Signature,
433 impl_ctx: Option<Rc<ImplCtx>>,
434}
435436impl FinishFnBody for FnCallBody {
437fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream {
438let asyncness = &self.sig.asyncness;
439let maybe_await = asyncness.is_some().then(|| quote!(.await));
440441// Filter out lifetime generic arguments, because they are not needed
442 // to be specified explicitly when calling the function. This also avoids
443 // the problem that it's not always possible to specify lifetimes in
444 // the turbofish syntax. See the problem of late-bound lifetimes specification
445 // in the issue https://github.com/rust-lang/rust/issues/42868
446let generic_args = self
447.sig
448 .generics
449 .params
450 .iter()
451 .filter(|arg| !matches!(arg, syn::GenericParam::Lifetime(_)))
452 .map(syn::GenericParam::to_generic_argument);
453454let prefix = self
455.sig
456 .receiver()
457 .map(|_| quote!(self.__unsafe_private_receiver.))
458 .or_else(|| {
459let self_ty = &self.impl_ctx.as_deref()?.self_ty;
460Some(quote!(<#self_ty>::))
461 });
462463let fn_ident = &self.sig.ident;
464465// The variables with values of members are in scope for this expression.
466let member_vars = ctx.members.iter().map(Member::orig_ident);
467468quote! {
469 #prefix #fn_ident::<#(#generic_args,)*>(
470 #( #member_vars ),*
471 )
472 #maybe_await
473 }
474 }
475}
476477/// To merge generic params we need to make sure lifetimes are always the first
478/// in the resulting list according to Rust syntax restrictions.
479fn merge_generic_params(
480 left: &Punctuated<syn::GenericParam, syn::Token![,]>,
481 right: &Punctuated<syn::GenericParam, syn::Token![,]>,
482) -> Vec<syn::GenericParam> {
483let is_lifetime = |param: &&_| matches!(param, &&syn::GenericParam::Lifetime(_));
484485let (left_lifetimes, left_rest): (Vec<_>, Vec<_>) = left.iter().partition(is_lifetime);
486let (right_lifetimes, right_rest): (Vec<_>, Vec<_>) = right.iter().partition(is_lifetime);
487488 left_lifetimes
489 .into_iter()
490 .chain(right_lifetimes)
491 .chain(left_rest)
492 .chain(right_rest)
493 .cloned()
494 .collect()
495}
496497#[derive(Default)]
498struct FindSelfReference {
499 self_span: Option<Span>,
500}
501502impl Visit<'_> for FindSelfReference {
503fn visit_item(&mut self, _: &syn::Item) {
504// Don't recurse into nested items. We are interested in the reference
505 // to `Self` on the current item level
506}
507508fn visit_path(&mut self, path: &syn::Path) {
509if self.self_span.is_some() {
510return;
511 }
512 syn::visit::visit_path(self, path);
513514let first_segment = match path.segments.first() {
515Some(first_segment) => first_segment,
516_ => return,
517 };
518519if first_segment.ident == "Self" {
520self.self_span = Some(first_segment.ident.span());
521 }
522 }
523}
524525fn get_must_use_attribute(attrs: &[syn::Attribute]) -> Result<Option<syn::Attribute>> {
526let mut iter = attrs
527 .iter()
528 .filter(|attr| attr.meta.path().is_ident("must_use"));
529530let result = iter.next();
531532if let Some(second) = iter.next() {
533bail!(
534 second,
535"found multiple #[must_use], but bon only works with exactly one or zero."
536);
537 }
538539if let Some(attr) = result {
540if let syn::AttrStyle::Inner(_) = attr.style {
541bail!(
542 attr,
543"#[must_use] attribute must be placed on the function itself, \
544 not inside it."
545);
546 }
547 }
548549Ok(result.cloned())
550}