bon_macros/builder/builder_gen/input_fn/
mod.rs1mod validation;
2
3use super::models::{AssocMethodReceiverCtxParams, FinishFnParams};
4use super::top_level_config::TopLevelConfig;
5use super::{
6 AssocMethodCtxParams, BuilderGenCtx, FinishFnBody, Generics, Member, MemberOrigin, RawMember,
7};
8use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams};
9use crate::normalization::{GenericsNamespace, NormalizeSelfTy, SyntaxVariant};
10use crate::parsing::{ItemSigConfig, SpannedKey};
11use crate::util::prelude::*;
12use std::borrow::Cow;
13use std::rc::Rc;
14use syn::punctuated::Punctuated;
15use syn::spanned::Spanned;
16use syn::visit_mut::VisitMut;
17
18pub(crate) struct FnInputCtx<'a> {
19 namespace: &'a GenericsNamespace,
20 fn_item: SyntaxVariant<syn::ItemFn>,
21 impl_ctx: Option<Rc<ImplCtx>>,
22 config: TopLevelConfig,
23
24 start_fn: StartFnParams,
25 self_ty_prefix: Option<String>,
26}
27
28pub(crate) struct FnInputCtxParams<'a> {
29 pub(crate) namespace: &'a GenericsNamespace,
30 pub(crate) fn_item: SyntaxVariant<syn::ItemFn>,
31 pub(crate) impl_ctx: Option<Rc<ImplCtx>>,
32 pub(crate) config: TopLevelConfig,
33}
34
35pub(crate) struct ImplCtx {
36 pub(crate) self_ty: Box<syn::Type>,
37 pub(crate) generics: syn::Generics,
38
39 pub(crate) allow_attrs: Vec<syn::Attribute>,
43}
44
45impl<'a> FnInputCtx<'a> {
46 pub(crate) fn new(params: FnInputCtxParams<'a>) -> Result<Self> {
47 let start_fn = params.config.start_fn.clone();
48
49 let start_fn_ident = start_fn
50 .name
51 .map(SpannedKey::into_value)
52 .unwrap_or_else(|| {
53 let fn_ident = ¶ms.fn_item.norm.sig.ident;
54
55 if params.impl_ctx.is_some() && fn_ident == "new" {
61 syn::Ident::new("builder", fn_ident.span())
62 } else {
63 fn_ident.clone()
64 }
65 });
66
67 let start_fn = StartFnParams {
68 ident: start_fn_ident,
69
70 vis: start_fn.vis.map(SpannedKey::into_value),
71
72 docs: start_fn
73 .docs
74 .map(SpannedKey::into_value)
75 .unwrap_or_else(|| {
76 params
77 .fn_item
78 .norm
79 .attrs
80 .iter()
81 .filter(|attr| attr.is_doc_expr())
82 .cloned()
83 .collect()
84 }),
85
86 generics: Some(Generics::new(
90 params
91 .fn_item
92 .norm
93 .sig
94 .generics
95 .params
96 .iter()
97 .cloned()
98 .collect(),
99 params.fn_item.norm.sig.generics.where_clause.clone(),
100 )),
101 span: Some(params.fn_item.orig.block.span()),
106 };
107
108 let self_ty_prefix = params.impl_ctx.as_deref().and_then(|impl_ctx| {
109 let prefix = impl_ctx
110 .self_ty
111 .as_path()?
112 .path
113 .segments
114 .last()?
115 .ident
116 .to_string();
117
118 Some(prefix)
119 });
120
121 let ctx = Self {
122 namespace: params.namespace,
123 fn_item: params.fn_item,
124 impl_ctx: params.impl_ctx,
125 config: params.config,
126 self_ty_prefix,
127 start_fn,
128 };
129
130 ctx.validate()?;
131
132 Ok(ctx)
133 }
134
135 fn assoc_method_ctx(&self) -> Result<Option<AssocMethodCtxParams>> {
136 let self_ty = match self.impl_ctx.as_deref() {
137 Some(impl_ctx) => impl_ctx.self_ty.clone(),
138 None => return Ok(None),
139 };
140
141 Ok(Some(AssocMethodCtxParams {
142 self_ty,
143 receiver: self.assoc_method_receiver_ctx_params()?,
144 }))
145 }
146
147 fn assoc_method_receiver_ctx_params(&self) -> Result<Option<AssocMethodReceiverCtxParams>> {
148 let receiver = match self.fn_item.norm.sig.receiver() {
149 Some(receiver) => receiver,
150 None => return Ok(None),
151 };
152
153 let builder_attr_on_receiver = receiver
154 .attrs
155 .iter()
156 .find(|attr| attr.path().is_ident("builder"));
157
158 if let Some(attr) = builder_attr_on_receiver {
159 bail!(
160 attr,
161 "#[builder] attributes on the receiver are not supported"
162 );
163 }
164
165 let self_ty = match self.impl_ctx.as_deref() {
166 Some(impl_ctx) => &impl_ctx.self_ty,
167 None => return Ok(None),
168 };
169
170 let mut without_self_keyword = receiver.ty.clone();
171
172 NormalizeSelfTy { self_ty }.visit_type_mut(&mut without_self_keyword);
173
174 Ok(Some(AssocMethodReceiverCtxParams {
175 with_self_keyword: receiver.clone(),
176 without_self_keyword,
177 }))
178 }
179
180 fn generics(&self) -> Generics {
181 let impl_ctx = self.impl_ctx.as_ref();
182 let norm_fn_params = &self.fn_item.norm.sig.generics.params;
183 let params = impl_ctx
184 .map(|impl_ctx| merge_generic_params(&impl_ctx.generics.params, norm_fn_params))
185 .unwrap_or_else(|| norm_fn_params.iter().cloned().collect());
186
187 let where_clauses = [
188 self.fn_item.norm.sig.generics.where_clause.clone(),
189 impl_ctx.and_then(|impl_ctx| impl_ctx.generics.where_clause.clone()),
190 ];
191
192 let where_clause = where_clauses
193 .into_iter()
194 .flatten()
195 .reduce(|mut combined, clause| {
196 combined.predicates.extend(clause.predicates);
197 combined
198 });
199
200 Generics::new(params, where_clause)
201 }
202
203 pub(crate) fn adapted_fn(&self) -> Result<syn::ItemFn> {
204 let mut orig = self.fn_item.orig.clone();
205
206 if let Some(name) = self.config.start_fn.name.as_deref() {
207 if *name == orig.sig.ident {
208 bail!(
209 &name,
210 "the starting function name must be different from the name \
211 of the positional function under the #[builder] attribute"
212 )
213 }
214 } else {
215 orig.vis = syn::Visibility::Inherited;
217
218 orig.attrs.retain(|attr| !attr.is_doc_expr());
226
227 let bon = &self.config.bon;
228
229 orig.attrs.extend([
230 syn::parse_quote!(#[doc(hidden)]),
231 syn::parse_quote!(#[#bon::__::__privatize]),
238 ]);
239 }
240
241 orig.attrs.retain(|attr| !attr.path().is_ident("builder"));
243
244 for arg in &mut orig.sig.inputs {
253 arg.attrs_mut()
254 .retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder"));
255 }
256
257 orig.attrs.push(syn::parse_quote!(#[allow(
258 clippy::too_many_arguments,
264
265 clippy::fn_params_excessive_bools,
268 )]));
269
270 Ok(orig)
271 }
272
273 pub(crate) fn into_builder_gen_ctx(self) -> Result<BuilderGenCtx> {
274 let assoc_method_ctx = self.assoc_method_ctx()?;
275
276 let members = self
277 .fn_item
278 .apply_ref(|fn_item| fn_item.sig.inputs.iter().filter_map(syn::FnArg::as_typed))
279 .into_iter()
280 .map(|arg| {
281 let pat = match arg.norm.pat.as_ref() {
282 syn::Pat::Ident(pat) => pat,
283 _ => bail!(
284 &arg.orig.pat,
285 "use a simple `identifier: type` syntax for the function argument; \
286 destructuring patterns in arguments aren't supported by the `#[builder]`",
287 ),
288 };
289
290 let ty = SyntaxVariant {
291 norm: arg.norm.ty.clone(),
292 orig: arg.orig.ty.clone(),
293 };
294
295 Ok(RawMember {
296 attrs: &arg.norm.attrs,
297 ident: pat.ident.clone(),
298 ty,
299 span: pat.ident.span(),
300 })
301 })
302 .collect::<Result<Vec<_>>>()?;
303
304 let members = Member::from_raw(&self.config, MemberOrigin::FnArg, members)?;
305
306 let generics = self.generics();
307 let mut adapted_fn_sig = self.adapted_fn()?.sig;
308
309 if self.config.start_fn.name.is_none() {
310 crate::privatize::privatize_fn_name(&mut adapted_fn_sig);
311 }
312
313 let finish_fn_body = FnCallBody {
314 sig: adapted_fn_sig,
315 impl_ctx: self.impl_ctx.clone(),
316 };
317
318 let ItemSigConfig {
319 name: finish_fn_ident,
320 vis: finish_fn_vis,
321 docs: finish_fn_docs,
322 } = self.config.finish_fn;
323
324 let is_special_builder_method = self.impl_ctx.is_some()
325 && (self.fn_item.norm.sig.ident == "new" || self.fn_item.norm.sig.ident == "builder");
326
327 let finish_fn_ident = finish_fn_ident
328 .map(SpannedKey::into_value)
329 .unwrap_or_else(|| {
330 if is_special_builder_method {
332 format_ident!("build")
333 } else {
334 format_ident!("call")
335 }
336 });
337
338 let finish_fn_docs = finish_fn_docs
339 .map(SpannedKey::into_value)
340 .unwrap_or_else(|| {
341 vec![syn::parse_quote! {
342 }]
344 });
345
346 let finish_fn = FinishFnParams {
347 ident: finish_fn_ident,
348 vis: finish_fn_vis.map(SpannedKey::into_value),
349 unsafety: self.fn_item.norm.sig.unsafety,
350 asyncness: self.fn_item.norm.sig.asyncness,
351 special_attrs: get_propagated_attrs(&self.fn_item.norm.attrs)?,
352 body: Box::new(finish_fn_body),
353 output: self.fn_item.norm.sig.output,
354 attrs: finish_fn_docs,
355 };
356
357 let fn_allows = self
358 .fn_item
359 .norm
360 .attrs
361 .iter()
362 .filter_map(syn::Attribute::to_allow);
363
364 let allow_attrs = self
365 .impl_ctx
366 .as_ref()
367 .into_iter()
368 .flat_map(|impl_ctx| impl_ctx.allow_attrs.iter().cloned())
369 .chain(fn_allows)
370 .collect();
371
372 let builder_ident = || {
373 let user_override = self.config.builder_type.name.map(SpannedKey::into_value);
374
375 if let Some(user_override) = user_override {
376 return user_override;
377 }
378
379 let ty_prefix = self.self_ty_prefix.unwrap_or_default();
380
381 if is_special_builder_method {
389 return format_ident!("{ty_prefix}Builder");
390 }
391
392 let pascal_case_fn = self.fn_item.norm.sig.ident.snake_to_pascal_case();
393
394 format_ident!("{ty_prefix}{pascal_case_fn}Builder")
395 };
396
397 let builder_type = BuilderTypeParams {
398 ident: builder_ident(),
399 derives: self.config.derive,
400 docs: self.config.builder_type.docs.map(SpannedKey::into_value),
401 vis: self.config.builder_type.vis.map(SpannedKey::into_value),
402 };
403
404 BuilderGenCtx::new(BuilderGenCtxParams {
405 bon: self.config.bon,
406 namespace: Cow::Borrowed(self.namespace),
407 members,
408
409 allow_attrs,
410
411 const_: self.config.const_,
412 on: self.config.on,
413
414 assoc_method_ctx,
415 generics,
416 orig_item_vis: self.fn_item.norm.vis,
417
418 builder_type,
419 state_mod: self.config.state_mod,
420 start_fn: self.start_fn,
421 finish_fn,
422 })
423 }
424}
425
426struct FnCallBody {
427 sig: syn::Signature,
428 impl_ctx: Option<Rc<ImplCtx>>,
429}
430
431impl FinishFnBody for FnCallBody {
432 fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream {
433 let asyncness = &self.sig.asyncness;
434 let maybe_await = asyncness.is_some().then(|| quote!(.await));
435
436 let generic_args = self
442 .sig
443 .generics
444 .params
445 .iter()
446 .filter(|arg| !matches!(arg, syn::GenericParam::Lifetime(_)))
447 .map(syn::GenericParam::to_generic_argument);
448
449 let prefix = ctx
450 .assoc_method_ctx
451 .as_ref()
452 .and_then(|ctx| {
453 let ident = &ctx.receiver.as_ref()?.field_ident;
454 Some(quote!(self.#ident.))
455 })
456 .or_else(|| {
457 let self_ty = &self.impl_ctx.as_deref()?.self_ty;
458 Some(quote!(<#self_ty>::))
459 });
460
461 let fn_ident = &self.sig.ident;
462
463 let member_vars = ctx.members.iter().map(Member::orig_ident);
465
466 quote! {
467 #prefix #fn_ident::<#(#generic_args,)*>(
468 #( #member_vars ),*
469 )
470 #maybe_await
471 }
472 }
473}
474
475fn merge_generic_params(
478 left: &Punctuated<syn::GenericParam, syn::Token![,]>,
479 right: &Punctuated<syn::GenericParam, syn::Token![,]>,
480) -> Vec<syn::GenericParam> {
481 let is_lifetime = |param: &&_| matches!(param, &&syn::GenericParam::Lifetime(_));
482
483 let (left_lifetimes, left_rest): (Vec<_>, Vec<_>) = left.iter().partition(is_lifetime);
484 let (right_lifetimes, right_rest): (Vec<_>, Vec<_>) = right.iter().partition(is_lifetime);
485
486 left_lifetimes
487 .into_iter()
488 .chain(right_lifetimes)
489 .chain(left_rest)
490 .chain(right_rest)
491 .cloned()
492 .collect()
493}
494
495const PROPAGATED_ATTRIBUTES: &[&str] = &["must_use", "track_caller", "target_feature"];
496
497fn get_propagated_attrs(attrs: &[syn::Attribute]) -> Result<Vec<syn::Attribute>> {
498 PROPAGATED_ATTRIBUTES
499 .iter()
500 .copied()
501 .filter_map(|needle| find_propagated_attr(attrs, needle).transpose())
502 .collect()
503}
504
505fn find_propagated_attr(attrs: &[syn::Attribute], needle: &str) -> Result<Option<syn::Attribute>> {
506 let mut iter = attrs
507 .iter()
508 .filter(|attr| attr.meta.path().is_ident(needle));
509
510 let result = iter.next();
511
512 if let Some(second) = iter.next() {
513 bail!(
514 second,
515 "found multiple #[{}], but bon only works with exactly one or zero.",
516 needle
517 );
518 }
519
520 if let Some(attr) = result {
521 if let syn::AttrStyle::Inner(_) = attr.style {
522 bail!(
523 attr,
524 "#[{}] attribute must be placed on the function itself, \
525 not inside it.",
526 needle
527 );
528 }
529 }
530
531 Ok(result.cloned())
532}