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::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>) -> Result<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 let ctx = 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 ctx.validate()?;
125
126 Ok(ctx)
127 }
128
129 fn assoc_method_ctx(&self) -> Result<Option<AssocMethodCtxParams>> {
130 let self_ty = match self.impl_ctx.as_deref() {
131 Some(impl_ctx) => impl_ctx.self_ty.clone(),
132 None => return Ok(None),
133 };
134
135 Ok(Some(AssocMethodCtxParams {
136 self_ty,
137 receiver: self.assoc_method_receiver_ctx_params()?,
138 }))
139 }
140
141 fn assoc_method_receiver_ctx_params(&self) -> Result<Option<AssocMethodReceiverCtxParams>> {
142 let receiver = match self.fn_item.norm.sig.receiver() {
143 Some(receiver) => receiver,
144 None => return Ok(None),
145 };
146
147 let builder_attr_on_receiver = receiver
148 .attrs
149 .iter()
150 .find(|attr| attr.path().is_ident("builder"));
151
152 if let Some(attr) = builder_attr_on_receiver {
153 bail!(
154 attr,
155 "#[builder] attributes on the receiver are not supported"
156 );
157 }
158
159 let self_ty = match self.impl_ctx.as_deref() {
160 Some(impl_ctx) => &impl_ctx.self_ty,
161 None => return Ok(None),
162 };
163
164 let mut without_self_keyword = receiver.ty.clone();
165
166 NormalizeSelfTy { self_ty }.visit_type_mut(&mut without_self_keyword);
167
168 Ok(Some(AssocMethodReceiverCtxParams {
169 with_self_keyword: receiver.clone(),
170 without_self_keyword,
171 }))
172 }
173
174 fn generics(&self) -> Generics {
175 let impl_ctx = self.impl_ctx.as_ref();
176 let norm_fn_params = &self.fn_item.norm.sig.generics.params;
177 let params = impl_ctx
178 .map(|impl_ctx| merge_generic_params(&impl_ctx.generics.params, norm_fn_params))
179 .unwrap_or_else(|| norm_fn_params.iter().cloned().collect());
180
181 let where_clauses = [
182 self.fn_item.norm.sig.generics.where_clause.clone(),
183 impl_ctx.and_then(|impl_ctx| impl_ctx.generics.where_clause.clone()),
184 ];
185
186 let where_clause = where_clauses
187 .into_iter()
188 .flatten()
189 .reduce(|mut combined, clause| {
190 combined.predicates.extend(clause.predicates);
191 combined
192 });
193
194 Generics::new(params, where_clause)
195 }
196
197 pub(crate) fn adapted_fn(&self) -> Result<syn::ItemFn> {
198 let mut orig = self.fn_item.orig.clone();
199
200 if let Some(name) = self.config.start_fn.name.as_deref() {
201 if *name == orig.sig.ident {
202 bail!(
203 &name,
204 "the starting function name must be different from the name \
205 of the positional function under the #[builder] attribute"
206 )
207 }
208 } else {
209 orig.vis = syn::Visibility::Inherited;
211
212 orig.attrs.retain(|attr| !attr.is_doc_expr());
220
221 let bon = &self.config.bon;
222
223 orig.attrs.extend([
224 syn::parse_quote!(#[doc(hidden)]),
225 syn::parse_quote!(#[#bon::__::__privatize]),
232 ]);
233 }
234
235 orig.attrs.retain(|attr| !attr.path().is_ident("builder"));
237
238 for arg in &mut orig.sig.inputs {
247 arg.attrs_mut()
248 .retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder"));
249 }
250
251 orig.attrs.push(syn::parse_quote!(#[allow(
252 clippy::too_many_arguments,
258
259 clippy::fn_params_excessive_bools,
262 )]));
263
264 Ok(orig)
265 }
266
267 pub(crate) fn into_builder_gen_ctx(self) -> Result<BuilderGenCtx> {
268 let assoc_method_ctx = self.assoc_method_ctx()?;
269
270 let members = self
271 .fn_item
272 .apply_ref(|fn_item| fn_item.sig.inputs.iter().filter_map(syn::FnArg::as_typed))
273 .into_iter()
274 .map(|arg| {
275 let pat = match arg.norm.pat.as_ref() {
276 syn::Pat::Ident(pat) => pat,
277 _ => bail!(
278 &arg.orig.pat,
279 "use a simple `identifier: type` syntax for the function argument; \
280 destructuring patterns in arguments aren't supported by the `#[builder]`",
281 ),
282 };
283
284 let ty = SyntaxVariant {
285 norm: arg.norm.ty.clone(),
286 orig: arg.orig.ty.clone(),
287 };
288
289 Ok(RawMember {
290 attrs: &arg.norm.attrs,
291 ident: pat.ident.clone(),
292 ty,
293 })
294 })
295 .collect::<Result<Vec<_>>>()?;
296
297 let members = Member::from_raw(&self.config, MemberOrigin::FnArg, members)?;
298
299 let generics = self.generics();
300 let mut adapted_fn_sig = self.adapted_fn()?.sig;
301
302 if self.config.start_fn.name.is_none() {
303 crate::privatize::privatize_fn_name(&mut adapted_fn_sig);
304 }
305
306 let finish_fn_body = FnCallBody {
307 sig: adapted_fn_sig,
308 impl_ctx: self.impl_ctx.clone(),
309 };
310
311 let ItemSigConfig {
312 name: finish_fn_ident,
313 vis: finish_fn_vis,
314 docs: finish_fn_docs,
315 } = self.config.finish_fn;
316
317 let is_special_builder_method = self.impl_ctx.is_some()
318 && (self.fn_item.norm.sig.ident == "new" || self.fn_item.norm.sig.ident == "builder");
319
320 let finish_fn_ident = finish_fn_ident
321 .map(SpannedKey::into_value)
322 .unwrap_or_else(|| {
323 if is_special_builder_method {
325 format_ident!("build")
326 } else {
327 format_ident!("call")
328 }
329 });
330
331 let finish_fn_docs = finish_fn_docs
332 .map(SpannedKey::into_value)
333 .unwrap_or_else(|| {
334 vec![syn::parse_quote! {
335 }]
337 });
338
339 let finish_fn = FinishFnParams {
340 ident: finish_fn_ident,
341 vis: finish_fn_vis.map(SpannedKey::into_value),
342 unsafety: self.fn_item.norm.sig.unsafety,
343 asyncness: self.fn_item.norm.sig.asyncness,
344 special_attrs: get_propagated_attrs(&self.fn_item.norm.attrs)?,
345 body: Box::new(finish_fn_body),
346 output: self.fn_item.norm.sig.output,
347 attrs: finish_fn_docs,
348 };
349
350 let fn_allows = self
351 .fn_item
352 .norm
353 .attrs
354 .iter()
355 .filter_map(syn::Attribute::to_allow);
356
357 let allow_attrs = self
358 .impl_ctx
359 .as_ref()
360 .into_iter()
361 .flat_map(|impl_ctx| impl_ctx.allow_attrs.iter().cloned())
362 .chain(fn_allows)
363 .collect();
364
365 let builder_ident = || {
366 let user_override = self.config.builder_type.name.map(SpannedKey::into_value);
367
368 if let Some(user_override) = user_override {
369 return user_override;
370 }
371
372 let ty_prefix = self.self_ty_prefix.unwrap_or_default();
373
374 if is_special_builder_method {
382 return format_ident!("{ty_prefix}Builder");
383 }
384
385 let pascal_case_fn = self.fn_item.norm.sig.ident.snake_to_pascal_case();
386
387 format_ident!("{ty_prefix}{pascal_case_fn}Builder")
388 };
389
390 let builder_type = BuilderTypeParams {
391 ident: builder_ident(),
392 derives: self.config.derive,
393 docs: self.config.builder_type.docs.map(SpannedKey::into_value),
394 vis: self.config.builder_type.vis.map(SpannedKey::into_value),
395 };
396
397 BuilderGenCtx::new(BuilderGenCtxParams {
398 bon: self.config.bon,
399 namespace: Cow::Borrowed(self.namespace),
400 members,
401
402 allow_attrs,
403
404 const_: self.config.const_,
405 on: self.config.on,
406
407 assoc_method_ctx,
408 generics,
409 orig_item_vis: self.fn_item.norm.vis,
410
411 builder_type,
412 state_mod: self.config.state_mod,
413 start_fn: self.start_fn,
414 finish_fn,
415 })
416 }
417}
418
419struct FnCallBody {
420 sig: syn::Signature,
421 impl_ctx: Option<Rc<ImplCtx>>,
422}
423
424impl FinishFnBody for FnCallBody {
425 fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream {
426 let asyncness = &self.sig.asyncness;
427 let maybe_await = asyncness.is_some().then(|| quote!(.await));
428
429 let generic_args = self
435 .sig
436 .generics
437 .params
438 .iter()
439 .filter(|arg| !matches!(arg, syn::GenericParam::Lifetime(_)))
440 .map(syn::GenericParam::to_generic_argument);
441
442 let prefix = ctx
443 .assoc_method_ctx
444 .as_ref()
445 .and_then(|ctx| {
446 let ident = &ctx.receiver.as_ref()?.field_ident;
447 Some(quote!(self.#ident.))
448 })
449 .or_else(|| {
450 let self_ty = &self.impl_ctx.as_deref()?.self_ty;
451 Some(quote!(<#self_ty>::))
452 });
453
454 let fn_ident = &self.sig.ident;
455
456 let member_vars = ctx.members.iter().map(Member::orig_ident);
458
459 quote! {
460 #prefix #fn_ident::<#(#generic_args,)*>(
461 #( #member_vars ),*
462 )
463 #maybe_await
464 }
465 }
466}
467
468fn merge_generic_params(
471 left: &Punctuated<syn::GenericParam, syn::Token![,]>,
472 right: &Punctuated<syn::GenericParam, syn::Token![,]>,
473) -> Vec<syn::GenericParam> {
474 let is_lifetime = |param: &&_| matches!(param, &&syn::GenericParam::Lifetime(_));
475
476 let (left_lifetimes, left_rest): (Vec<_>, Vec<_>) = left.iter().partition(is_lifetime);
477 let (right_lifetimes, right_rest): (Vec<_>, Vec<_>) = right.iter().partition(is_lifetime);
478
479 left_lifetimes
480 .into_iter()
481 .chain(right_lifetimes)
482 .chain(left_rest)
483 .chain(right_rest)
484 .cloned()
485 .collect()
486}
487
488const PROPAGATED_ATTRIBUTES: &[&str] = &["must_use", "track_caller", "target_feature"];
489
490fn get_propagated_attrs(attrs: &[syn::Attribute]) -> Result<Vec<syn::Attribute>> {
491 PROPAGATED_ATTRIBUTES
492 .iter()
493 .copied()
494 .filter_map(|needle| find_propagated_attr(attrs, needle).transpose())
495 .collect()
496}
497
498fn find_propagated_attr(attrs: &[syn::Attribute], needle: &str) -> Result<Option<syn::Attribute>> {
499 let mut iter = attrs
500 .iter()
501 .filter(|attr| attr.meta.path().is_ident(needle));
502
503 let result = iter.next();
504
505 if let Some(second) = iter.next() {
506 bail!(
507 second,
508 "found multiple #[{}], but bon only works with exactly one or zero.",
509 needle
510 );
511 }
512
513 if let Some(attr) = result {
514 if let syn::AttrStyle::Inner(_) = attr.style {
515 bail!(
516 attr,
517 "#[{}] attribute must be placed on the function itself, \
518 not inside it.",
519 needle
520 );
521 }
522 }
523
524 Ok(result.cloned())
525}