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.on, MemberOrigin::FnArg, members)?;
298
299 let generics = self.generics();
300
301 let mut adapted_fn_sig = self.adapted_fn()?.sig;
302
303 if self.config.start_fn.name.is_none() {
304 crate::privatize::privatize_fn_name(&mut adapted_fn_sig);
305 }
306
307 let finish_fn_body = FnCallBody {
308 sig: adapted_fn_sig,
309 impl_ctx: self.impl_ctx.clone(),
310 };
311
312 let ItemSigConfig {
313 name: finish_fn_ident,
314 vis: finish_fn_vis,
315 docs: finish_fn_docs,
316 } = self.config.finish_fn;
317
318 let is_special_builder_method = self.impl_ctx.is_some()
319 && (self.fn_item.norm.sig.ident == "new" || self.fn_item.norm.sig.ident == "builder");
320
321 let finish_fn_ident = finish_fn_ident
322 .map(SpannedKey::into_value)
323 .unwrap_or_else(|| {
324 if is_special_builder_method {
326 format_ident!("build")
327 } else {
328 format_ident!("call")
329 }
330 });
331
332 let finish_fn_docs = finish_fn_docs
333 .map(SpannedKey::into_value)
334 .unwrap_or_else(|| {
335 vec![syn::parse_quote! {
336 }]
338 });
339
340 let finish_fn = FinishFnParams {
341 ident: finish_fn_ident,
342 vis: finish_fn_vis.map(SpannedKey::into_value),
343 unsafety: self.fn_item.norm.sig.unsafety,
344 asyncness: self.fn_item.norm.sig.asyncness,
345 must_use: get_must_use_attribute(&self.fn_item.norm.attrs)?,
346 body: Box::new(finish_fn_body),
347 output: self.fn_item.norm.sig.output,
348 attrs: finish_fn_docs,
349 };
350
351 let fn_allows = self
352 .fn_item
353 .norm
354 .attrs
355 .iter()
356 .filter_map(syn::Attribute::to_allow);
357
358 let allow_attrs = self
359 .impl_ctx
360 .as_ref()
361 .into_iter()
362 .flat_map(|impl_ctx| impl_ctx.allow_attrs.iter().cloned())
363 .chain(fn_allows)
364 .collect();
365
366 let builder_ident = || {
367 let user_override = self.config.builder_type.name.map(SpannedKey::into_value);
368
369 if let Some(user_override) = user_override {
370 return user_override;
371 }
372
373 let ty_prefix = self.self_ty_prefix.unwrap_or_default();
374
375 if is_special_builder_method {
383 return format_ident!("{ty_prefix}Builder");
384 }
385
386 let pascal_case_fn = self.fn_item.norm.sig.ident.snake_to_pascal_case();
387
388 format_ident!("{ty_prefix}{pascal_case_fn}Builder")
389 };
390
391 let builder_type = BuilderTypeParams {
392 ident: builder_ident(),
393 derives: self.config.derive,
394 docs: self.config.builder_type.docs.map(SpannedKey::into_value),
395 vis: self.config.builder_type.vis.map(SpannedKey::into_value),
396 };
397
398 BuilderGenCtx::new(BuilderGenCtxParams {
399 bon: self.config.bon,
400 namespace: Cow::Borrowed(self.namespace),
401 members,
402
403 allow_attrs,
404
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
488fn get_must_use_attribute(attrs: &[syn::Attribute]) -> Result<Option<syn::Attribute>> {
489 let mut iter = attrs
490 .iter()
491 .filter(|attr| attr.meta.path().is_ident("must_use"));
492
493 let result = iter.next();
494
495 if let Some(second) = iter.next() {
496 bail!(
497 second,
498 "found multiple #[must_use], but bon only works with exactly one or zero."
499 );
500 }
501
502 if let Some(attr) = result {
503 if let syn::AttrStyle::Inner(_) = attr.style {
504 bail!(
505 attr,
506 "#[must_use] attribute must be placed on the function itself, \
507 not inside it."
508 );
509 }
510 }
511
512 Ok(result.cloned())
513}