bon_macros/builder/builder_gen/
input_fn.rs1use 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;
16
17pub(crate) struct FnInputCtx<'a> {
18 namespace: &'a GenericsNamespace,
19 fn_item: SyntaxVariant<syn::ItemFn>,
20 impl_ctx: Option<Rc<ImplCtx>>,
21 config: TopLevelConfig,
22
23 start_fn: StartFnParams,
24 self_ty_prefix: Option<String>,
25}
26
27pub(crate) struct FnInputCtxParams<'a> {
28 pub(crate) namespace: &'a GenericsNamespace,
29 pub(crate) fn_item: SyntaxVariant<syn::ItemFn>,
30 pub(crate) impl_ctx: Option<Rc<ImplCtx>>,
31 pub(crate) config: TopLevelConfig,
32}
33
34pub(crate) struct ImplCtx {
35 pub(crate) self_ty: Box<syn::Type>,
36 pub(crate) generics: syn::Generics,
37
38 pub(crate) allow_attrs: Vec<syn::Attribute>,
42}
43
44impl<'a> FnInputCtx<'a> {
45 pub(crate) fn new(params: FnInputCtxParams<'a>) -> Self {
46 let start_fn = params.config.start_fn.clone();
47
48 let start_fn_ident = start_fn
49 .name
50 .map(SpannedKey::into_value)
51 .unwrap_or_else(|| {
52 let fn_ident = ¶ms.fn_item.norm.sig.ident;
53
54 if params.impl_ctx.is_some() && fn_ident == "new" {
60 syn::Ident::new("builder", fn_ident.span())
61 } else {
62 fn_ident.clone()
63 }
64 });
65
66 let start_fn = StartFnParams {
67 ident: start_fn_ident,
68
69 vis: start_fn.vis.map(SpannedKey::into_value),
70
71 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 }),
84
85 generics: 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 };
101
102 let self_ty_prefix = params.impl_ctx.as_deref().and_then(|impl_ctx| {
103 let prefix = impl_ctx
104 .self_ty
105 .as_path()?
106 .path
107 .segments
108 .last()?
109 .ident
110 .to_string();
111
112 Some(prefix)
113 });
114
115 Self {
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 }
124
125 fn assoc_method_ctx(&self) -> Result<Option<AssocMethodCtx>> {
126 let self_ty = match self.impl_ctx.as_deref() {
127 Some(impl_ctx) => impl_ctx.self_ty.clone(),
128 None => return Ok(None),
129 };
130
131 Ok(Some(AssocMethodCtx {
132 self_ty,
133 receiver: self.assoc_method_receiver_ctx()?,
134 }))
135 }
136
137 fn assoc_method_receiver_ctx(&self) -> Result<Option<AssocMethodReceiverCtx>> {
138 let receiver = match self.fn_item.norm.sig.receiver() {
139 Some(receiver) => receiver,
140 None => return Ok(None),
141 };
142
143 let builder_attr_on_receiver = receiver
144 .attrs
145 .iter()
146 .find(|attr| attr.path().is_ident("builder"));
147
148 if let Some(attr) = builder_attr_on_receiver {
149 bail!(
150 attr,
151 "#[builder] attributes on the receiver are not supported"
152 );
153 }
154
155 let self_ty = match self.impl_ctx.as_deref() {
156 Some(impl_ctx) => &impl_ctx.self_ty,
157 None => return Ok(None),
158 };
159
160 let mut without_self_keyword = receiver.ty.clone();
161
162 NormalizeSelfTy { self_ty }.visit_type_mut(&mut without_self_keyword);
163
164 Ok(Some(AssocMethodReceiverCtx {
165 with_self_keyword: receiver.clone(),
166 without_self_keyword,
167 }))
168 }
169
170 fn generics(&self) -> Generics {
171 let impl_ctx = self.impl_ctx.as_ref();
172 let norm_fn_params = &self.fn_item.norm.sig.generics.params;
173 let 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());
176
177 let where_clauses = [
178 self.fn_item.norm.sig.generics.where_clause.clone(),
179 impl_ctx.and_then(|impl_ctx| impl_ctx.generics.where_clause.clone()),
180 ];
181
182 let where_clause = where_clauses
183 .into_iter()
184 .flatten()
185 .reduce(|mut combined, clause| {
186 combined.predicates.extend(clause.predicates);
187 combined
188 });
189
190 Generics::new(params, where_clause)
191 }
192
193 pub(crate) fn adapted_fn(&self) -> Result<syn::ItemFn> {
194 let mut orig = self.fn_item.orig.clone();
195
196 if let Some(name) = self.config.start_fn.name.as_deref() {
197 if *name == orig.sig.ident {
198 bail!(
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 orig.vis = syn::Visibility::Inherited;
207
208 orig.sig.ident = format_ident!("__orig_{}", orig.sig.ident.raw_name());
216
217 orig.attrs.retain(|attr| !attr.is_doc_expr());
225
226 orig.attrs.extend([syn::parse_quote!(#[doc(hidden)])]);
227 }
228
229 orig.attrs.retain(|attr| !attr.path().is_ident("builder"));
231
232 for arg in &mut orig.sig.inputs {
241 arg.attrs_mut()
242 .retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder"));
243 }
244
245 orig.attrs.push(syn::parse_quote!(#[allow(
246 clippy::too_many_arguments,
252
253 clippy::fn_params_excessive_bools,
256 )]));
257
258 Ok(orig)
259 }
260
261 pub(crate) fn into_builder_gen_ctx(self) -> Result<BuilderGenCtx> {
262 let assoc_method_ctx = self.assoc_method_ctx()?;
263
264 if self.impl_ctx.is_none() {
265 let 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.";
270
271 if let Some(receiver) = &self.fn_item.orig.sig.receiver() {
272 bail!(
273 &receiver.self_token,
274 "function contains a `self` parameter {explanation}"
275 );
276 }
277
278 let mut ctx = FindSelfReference::default();
279 ctx.visit_item_fn(&self.fn_item.orig);
280 if let Some(self_span) = ctx.self_span {
281 bail!(
282 &self_span,
283 "function contains a `Self` type reference {explanation}"
284 );
285 }
286 }
287
288 let 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| {
293 let 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 };
301
302 let ty = SyntaxVariant {
303 norm: arg.norm.ty.clone(),
304 orig: arg.orig.ty.clone(),
305 };
306
307 Ok(RawMember {
308 attrs: &arg.norm.attrs,
309 ident: pat.ident.clone(),
310 ty,
311 })
312 })
313 .collect::<Result<Vec<_>>>()?;
314
315 let members = Member::from_raw(&self.config.on, MemberOrigin::FnArg, members)?;
316
317 let generics = self.generics();
318
319 let finish_fn_body = FnCallBody {
320 sig: self.adapted_fn()?.sig,
321 impl_ctx: self.impl_ctx.clone(),
322 };
323
324 let ItemSigConfig {
325 name: finish_fn_ident,
326 vis: finish_fn_vis,
327 docs: finish_fn_docs,
328 } = self.config.finish_fn;
329
330 let is_special_builder_method = self.impl_ctx.is_some()
331 && (self.fn_item.norm.sig.ident == "new" || self.fn_item.norm.sig.ident == "builder");
332
333 let finish_fn_ident = finish_fn_ident
334 .map(SpannedKey::into_value)
335 .unwrap_or_else(|| {
336 if is_special_builder_method {
338 format_ident!("build")
339 } else {
340 format_ident!("call")
341 }
342 });
343
344 let finish_fn_docs = finish_fn_docs
345 .map(SpannedKey::into_value)
346 .unwrap_or_else(|| {
347 vec![syn::parse_quote! {
348 }]
350 });
351
352 let 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 };
362
363 let fn_allows = self
364 .fn_item
365 .norm
366 .attrs
367 .iter()
368 .filter_map(syn::Attribute::to_allow);
369
370 let 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();
377
378 let builder_ident = || {
379 let user_override = self.config.builder_type.name.map(SpannedKey::into_value);
380
381 if let Some(user_override) = user_override {
382 return user_override;
383 }
384
385 let ty_prefix = self.self_ty_prefix.unwrap_or_default();
386
387 if is_special_builder_method {
395 return format_ident!("{ty_prefix}Builder");
396 }
397
398 let pascal_case_fn = self.fn_item.norm.sig.ident.snake_to_pascal_case();
399
400 format_ident!("{ty_prefix}{pascal_case_fn}Builder")
401 };
402
403 let 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 };
409
410 BuilderGenCtx::new(BuilderGenCtxParams {
411 bon: self.config.bon,
412 namespace: Cow::Borrowed(self.namespace),
413 members,
414
415 allow_attrs,
416
417 on: self.config.on,
418
419 assoc_method_ctx,
420 generics,
421 orig_item_vis: self.fn_item.norm.vis,
422
423 builder_type,
424 state_mod: self.config.state_mod,
425 start_fn: self.start_fn,
426 finish_fn,
427 })
428 }
429}
430
431struct FnCallBody {
432 sig: syn::Signature,
433 impl_ctx: Option<Rc<ImplCtx>>,
434}
435
436impl FinishFnBody for FnCallBody {
437 fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream {
438 let asyncness = &self.sig.asyncness;
439 let maybe_await = asyncness.is_some().then(|| quote!(.await));
440
441 let 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);
453
454 let prefix = self
455 .sig
456 .receiver()
457 .map(|_| quote!(self.__unsafe_private_receiver.))
458 .or_else(|| {
459 let self_ty = &self.impl_ctx.as_deref()?.self_ty;
460 Some(quote!(<#self_ty>::))
461 });
462
463 let fn_ident = &self.sig.ident;
464
465 let member_vars = ctx.members.iter().map(Member::orig_ident);
467
468 quote! {
469 #prefix #fn_ident::<#(#generic_args,)*>(
470 #( #member_vars ),*
471 )
472 #maybe_await
473 }
474 }
475}
476
477fn merge_generic_params(
480 left: &Punctuated<syn::GenericParam, syn::Token![,]>,
481 right: &Punctuated<syn::GenericParam, syn::Token![,]>,
482) -> Vec<syn::GenericParam> {
483 let is_lifetime = |param: &&_| matches!(param, &&syn::GenericParam::Lifetime(_));
484
485 let (left_lifetimes, left_rest): (Vec<_>, Vec<_>) = left.iter().partition(is_lifetime);
486 let (right_lifetimes, right_rest): (Vec<_>, Vec<_>) = right.iter().partition(is_lifetime);
487
488 left_lifetimes
489 .into_iter()
490 .chain(right_lifetimes)
491 .chain(left_rest)
492 .chain(right_rest)
493 .cloned()
494 .collect()
495}
496
497#[derive(Default)]
498struct FindSelfReference {
499 self_span: Option<Span>,
500}
501
502impl Visit<'_> for FindSelfReference {
503 fn visit_item(&mut self, _: &syn::Item) {
504 }
507
508 fn visit_path(&mut self, path: &syn::Path) {
509 if self.self_span.is_some() {
510 return;
511 }
512 syn::visit::visit_path(self, path);
513
514 let first_segment = match path.segments.first() {
515 Some(first_segment) => first_segment,
516 _ => return,
517 };
518
519 if first_segment.ident == "Self" {
520 self.self_span = Some(first_segment.ident.span());
521 }
522 }
523}
524
525fn get_must_use_attribute(attrs: &[syn::Attribute]) -> Result<Option<syn::Attribute>> {
526 let mut iter = attrs
527 .iter()
528 .filter(|attr| attr.meta.path().is_ident("must_use"));
529
530 let result = iter.next();
531
532 if let Some(second) = iter.next() {
533 bail!(
534 second,
535 "found multiple #[must_use], but bon only works with exactly one or zero."
536 );
537 }
538
539 if let Some(attr) = result {
540 if let syn::AttrStyle::Inner(_) = attr.style {
541 bail!(
542 attr,
543 "#[must_use] attribute must be placed on the function itself, \
544 not inside it."
545 );
546 }
547 }
548
549 Ok(result.cloned())
550}