Skip to main content

bon_macros/builder/builder_gen/setters/
mod.rs

1use super::member::{SetterClosure, WithConfig};
2use super::{BuilderGenCtx, NamedMember};
3use crate::parsing::ItemSigConfig;
4use crate::util::prelude::*;
5use std::iter;
6
7pub(crate) struct SettersCtx<'a> {
8    base: &'a BuilderGenCtx,
9    member: &'a NamedMember,
10}
11
12impl<'a> SettersCtx<'a> {
13    pub(crate) fn new(base: &'a BuilderGenCtx, member: &'a NamedMember) -> Self {
14        Self { base, member }
15    }
16
17    pub(crate) fn setter_methods(&self) -> Result<TokenStream> {
18        match SettersItems::new(self) {
19            SettersItems::Required(item) => self.setter_for_required_member(item),
20            SettersItems::Optional(setters) => self.setters_for_optional_member(setters),
21        }
22    }
23
24    fn setter_for_required_member(&self, item: SetterItem) -> Result<TokenStream> {
25        let inputs;
26        let expr;
27
28        let member_type = self.member.ty.norm.as_ref();
29
30        if let Some(with) = &self.member.config.with {
31            inputs = self.underlying_inputs_from_with(with)?;
32            expr = self.member_expr_from_with(with);
33        } else if self.member.config.into.is_present() {
34            inputs = vec![(
35                pat_ident("value"),
36                syn::parse_quote!(impl Into<#member_type>),
37            )];
38            expr = quote!(Into::into(value));
39        } else {
40            inputs = vec![(pat_ident("value"), member_type.clone())];
41            expr = quote!(value);
42        }
43
44        let body = SetterBody::SetMember {
45            expr: quote!(::core::option::Option::Some(#expr)),
46        };
47
48        Ok(self.setter_method(Setter {
49            item,
50            imp: SetterImpl { inputs, body },
51        }))
52    }
53
54    fn setters_for_optional_member(&self, items: OptionalSettersItems) -> Result<TokenStream> {
55        if let Some(with) = &self.member.config.with {
56            return self.setters_for_optional_member_having_with(with, items);
57        }
58
59        let underlying_ty = self.member.underlying_norm_ty();
60        let underlying_ty: syn::Type = if self.member.config.into.is_present() {
61            syn::parse_quote!(impl Into<#underlying_ty>)
62        } else {
63            underlying_ty.clone()
64        };
65
66        let some_fn = Setter {
67            item: items.some_fn,
68            imp: SetterImpl {
69                inputs: vec![(pat_ident("value"), underlying_ty.clone())],
70                body: SetterBody::Forward {
71                    body: {
72                        let option_fn_name = &items.option_fn.name;
73                        quote! {
74                            self.#option_fn_name(Some(value))
75                        }
76                    },
77                },
78            },
79        };
80
81        let option_fn = Setter {
82            item: items.option_fn,
83            imp: SetterImpl {
84                inputs: vec![(
85                    pat_ident("value"),
86                    syn::parse_quote!(Option<#underlying_ty>),
87                )],
88                body: SetterBody::SetMember {
89                    expr: if self.member.config.into.is_present() {
90                        quote! {
91                            Option::map(value, Into::into)
92                        }
93                    } else {
94                        quote!(value)
95                    },
96                },
97            },
98        };
99
100        Ok([self.setter_method(some_fn), self.setter_method(option_fn)].concat())
101    }
102
103    fn setters_for_optional_member_having_with(
104        &self,
105        with: &WithConfig,
106        items: OptionalSettersItems,
107    ) -> Result<TokenStream> {
108        let inputs = self.underlying_inputs_from_with(with)?;
109
110        let idents = inputs.iter().map(|(pat, _)| &pat.ident);
111
112        // If the closure accepts just a single input avoid wrapping it
113        // in a tuple in the `option_fn` setter.
114        let tuple_if_many = |val: TokenStream| -> TokenStream {
115            if inputs.len() == 1 {
116                val
117            } else {
118                quote!((#val))
119            }
120        };
121
122        let ident_maybe_tuple = tuple_if_many(quote!( #( #idents ),* ));
123
124        let some_fn = Setter {
125            item: items.some_fn,
126            imp: SetterImpl {
127                inputs: inputs.clone(),
128                body: SetterBody::Forward {
129                    body: {
130                        let option_fn_name = &items.option_fn.name;
131                        quote! {
132                            self.#option_fn_name(Some(#ident_maybe_tuple))
133                        }
134                    },
135                },
136            },
137        };
138
139        let option_fn_impl = SetterImpl {
140            inputs: {
141                let input_types = inputs.iter().map(|(_, ty)| ty);
142                let input_types = tuple_if_many(quote!(#( #input_types, )*));
143
144                vec![(pat_ident("value"), syn::parse_quote!(Option<#input_types>))]
145            },
146            body: SetterBody::SetMember {
147                expr: {
148                    let expr = self.member_expr_from_with(with);
149                    quote! {
150                        // Not using `Option::map` here because the `#expr`
151                        // can contain a `?` operator for a fallible operation.
152                        match value {
153                            Some(#ident_maybe_tuple) => Some(#expr),
154                            None => None,
155                        }
156                    }
157                },
158            },
159        };
160
161        let option_fn = Setter {
162            item: items.option_fn,
163            imp: option_fn_impl,
164        };
165
166        Ok([self.setter_method(some_fn), self.setter_method(option_fn)].concat())
167    }
168
169    /// This method is reused between the setter for the required member and
170    /// the `some_fn` setter for the optional member.
171    ///
172    /// We intentionally keep the name and signature of the setter method
173    /// for an optional member that accepts the value under the option the
174    /// same as the setter method for the required member to keep the API
175    /// of the builder compatible when a required member becomes optional.
176    /// To be able to explicitly pass an `Option` value to the setter method
177    /// users need to use the `maybe_{member_ident}` method.
178    fn underlying_inputs_from_with(
179        &self,
180        with: &WithConfig,
181    ) -> Result<Vec<(syn::PatIdent, syn::Type)>> {
182        let inputs = match with {
183            WithConfig::Closure(closure) => closure
184                .inputs
185                .iter()
186                .map(|input| (input.pat.clone(), (*input.ty).clone()))
187                .collect(),
188            WithConfig::Some(some) => {
189                let input_ty = self
190                    .member
191                    .underlying_norm_ty()
192                    .option_type_param()
193                    .ok_or_else(|| {
194                        if self.member.ty.norm.is_option() {
195                            err!(
196                                some,
197                                "the underlying type of this member is not `Option`; \
198                                by default, members of type `Option` are optional and their \
199                                'underlying type' is the type under the `Option`; \
200                                you might be missing #[builder(required)]` annotation \
201                                for this member"
202                            )
203                        } else {
204                            err!(
205                                &self.member.underlying_norm_ty(),
206                                "`with = Some` only works for members with the underlying \
207                                    type of `Option`;"
208                            )
209                        }
210                    })?;
211
212                vec![(pat_ident("value"), input_ty.clone())]
213            }
214            WithConfig::FromIter(from_iter) => {
215                let collection_ty = self.member.underlying_norm_ty();
216
217                let well_known_single_arg_suffixes = ["Vec", "Set", "Deque", "Heap", "List"];
218
219                let err = || {
220                    let mut from_iter_path = quote!(#from_iter).to_string();
221                    from_iter_path.retain(|c| !c.is_whitespace());
222
223                    err!(
224                        collection_ty,
225                        "the underlying type of this member is not a known collection type; \
226                        only a collection type that matches the following patterns will be \
227                        accepted by `#[builder(with = {from_iter_path})], where * at \
228                        the beginning means the collection type may start with any prefix:\n\
229                        - *Map<K, V>\n\
230                        {}",
231                        well_known_single_arg_suffixes
232                            .iter()
233                            .map(|suffix| { format!("- *{suffix}<T>") })
234                            .join("\n")
235                    )
236                };
237
238                let path = collection_ty.as_path_no_qself().ok_or_else(err)?;
239
240                let last_segment = path.segments.last().ok_or_else(err)?;
241                let args = match &last_segment.arguments {
242                    syn::PathArguments::AngleBracketed(args) => &args.args,
243                    _ => return Err(err()),
244                };
245
246                let last_segment_ident_str = last_segment.ident.to_string();
247
248                let item_ty = if well_known_single_arg_suffixes
249                    .iter()
250                    .any(|suffix| last_segment_ident_str.ends_with(suffix))
251                {
252                    // We don't compare for `len == 1` because there may be an optional last
253                    // type argument for the allocator
254                    if args.is_empty() {
255                        return Err(err());
256                    }
257
258                    let arg = args.first().ok_or_else(err)?;
259
260                    quote!(#arg)
261                } else if last_segment_ident_str.ends_with("Map") {
262                    // We don't compare for `len == 2` because there may be an optional last
263                    // type argument for the allocator
264                    if args.len() < 2 {
265                        return Err(err());
266                    }
267
268                    let mut args = args.iter();
269                    let key = args.next().ok_or_else(err)?;
270                    let value = args.next().ok_or_else(err)?;
271
272                    quote!((#key, #value))
273                } else {
274                    return Err(err());
275                };
276
277                vec![(
278                    pat_ident("iter"),
279                    syn::parse_quote!(impl IntoIterator<Item = #item_ty>),
280                )]
281            }
282        };
283
284        Ok(inputs)
285    }
286
287    fn member_expr_from_with(&self, with: &WithConfig) -> TokenStream {
288        match with {
289            WithConfig::Closure(closure) => self.member_expr_from_with_closure(with, closure),
290            WithConfig::Some(some) => quote!(#some(value)),
291            WithConfig::FromIter(from_iter) => quote!(#from_iter(iter)),
292        }
293    }
294
295    fn member_expr_from_with_closure(
296        &self,
297        with: &WithConfig,
298        closure: &SetterClosure,
299    ) -> TokenStream {
300        let body = &closure.body;
301
302        let ty = self.member.underlying_norm_ty().to_token_stream();
303
304        let output = Self::maybe_wrap_in_result(with, ty);
305
306        // Closures aren't supported in `const` contexts at the time of this writing
307        // (Rust 1.86.0), so we don't wrap it in a closure but we require the expression
308        // to be simple so that it doesn't break out of the surrounding scope.
309        // Search for `require_embeddable_const_expr` for more.
310        if self.base.const_.is_some() {
311            let body = quote! {{
312                let value: #output = #body;
313                value
314            }};
315
316            if closure.output.is_none() {
317                return body;
318            }
319
320            return quote! {
321                match #body {
322                    Ok(value) => value,
323                    Err(err) => return Err(err),
324                }
325            };
326        }
327
328        // Avoid wrapping the body in a block if it's already a block.
329        let body = if matches!(body.as_ref(), syn::Expr::Block(_)) {
330            body.to_token_stream()
331        } else {
332            quote!({ #body })
333        };
334
335        let question_mark = closure
336            .output
337            .is_some()
338            .then(|| syn::Token![?](Span::call_site()));
339
340        quote! {
341            (move || -> #output #body)() #question_mark
342        }
343    }
344
345    fn maybe_wrap_in_result(with: &WithConfig, ty: TokenStream) -> TokenStream {
346        let closure = match with {
347            WithConfig::Closure(closure) => closure,
348            _ => return ty,
349        };
350
351        let output = match closure.output.as_ref() {
352            Some(output) => output,
353            None => return ty,
354        };
355        let result_path = &output.result_path;
356        let err_ty = output.err_ty.iter();
357        quote! {
358            #result_path< #ty #(, #err_ty )* >
359        }
360    }
361
362    fn setter_method(&self, setter: Setter) -> TokenStream {
363        let Setter { item, imp } = setter;
364
365        let maybe_mut = match imp.body {
366            SetterBody::Forward { .. } => None,
367            SetterBody::SetMember { .. } => Some(syn::Token![mut](Span::call_site())),
368        };
369
370        let body = match imp.body {
371            SetterBody::Forward { body } => body,
372            SetterBody::SetMember { expr } => {
373                let mut output = if !self.member.is_stateful() {
374                    quote! {
375                        self
376                    }
377                } else {
378                    let builder_ident = &self.base.builder_type.ident;
379
380                    let maybe_receiver_field = self.base.receiver().map(|receiver| {
381                        let ident = &receiver.field_ident;
382                        quote!(#ident: self.#ident,)
383                    });
384
385                    let start_fn_args_fields_idents =
386                        self.base.start_fn_args().map(|member| &member.ident);
387
388                    let custom_fields_idents = self.base.custom_fields().map(|field| &field.ident);
389
390                    quote! {
391                        #builder_ident {
392                            __unsafe_private_phantom: ::core::marker::PhantomData,
393                            #( #custom_fields_idents: self.#custom_fields_idents, )*
394                            #maybe_receiver_field
395                            #( #start_fn_args_fields_idents: self.#start_fn_args_fields_idents, )*
396                            __unsafe_private_named: self.__unsafe_private_named,
397                        }
398                    }
399                };
400
401                let result_output = self
402                    .member
403                    .config
404                    .with
405                    .as_ref()
406                    .and_then(|with| with.as_closure()?.output.as_ref());
407
408                if let Some(result_output) = result_output {
409                    let result_path = &result_output.result_path;
410                    output = quote!(#result_path::Ok(#output));
411                }
412
413                let index = &self.member.index;
414                quote! {
415                    self.__unsafe_private_named.#index = #expr;
416                    #output
417                }
418            }
419        };
420
421        let state_mod = &self.base.state_mod.ident;
422
423        let mut return_type = if !self.member.is_stateful() {
424            quote! { Self }
425        } else {
426            let state_transition = format_ident!("Set{}", self.member.name.pascal_str);
427            let builder_ident = &self.base.builder_type.ident;
428            let generic_args = &self.base.generics.args;
429            let state_var = &self.base.state_var;
430
431            quote! {
432                #builder_ident<#(#generic_args,)* #state_mod::#state_transition<#state_var>>
433            }
434        };
435
436        if let Some(with) = &self.member.config.with {
437            return_type = Self::maybe_wrap_in_result(with, return_type);
438        }
439
440        let where_clause = (!self.member.config.overwritable.is_present()).then(|| {
441            let state_var = &self.base.state_var;
442            let member_pascal = &self.member.name.pascal;
443            quote! {
444                where #state_var::#member_pascal: #state_mod::IsUnset,
445            }
446        });
447
448        let SetterItem { name, vis, docs } = item;
449        let pats = imp.inputs.iter().map(|(pat, _)| pat);
450        let types = imp.inputs.iter().map(|(_, ty)| ty);
451        let const_ = &self.base.const_;
452        let fn_modifiers = self.member.respan(quote!(#vis #const_));
453
454        // It's important to keep the span of `self` the same across all
455        // references to it. Otherwise `self`s that have different spans will
456        // be treated as totally different symbols due to the hygiene rules.
457        let self_ = quote!(self);
458
459        quote_spanned! {self.member.span=>
460            #( #docs )*
461            #[allow(
462                // This is intentional. We want the builder syntax to compile away
463                clippy::inline_always,
464                // We don't want to avoid using `impl Trait` in the setter. This way
465                // the setter signature is easier to read, and anyway if you want to
466                // specify a type hint for the method that accepts an `impl Into`, then
467                // your design of this setter already went wrong.
468                clippy::impl_trait_in_params,
469                clippy::missing_const_for_fn,
470                // When having a field which has one of the prefixes listed by
471                // `clippy::wrong_self_convention` you will end up getting said lint
472                // warning in your `bon::Builder` because we take self by value.
473                clippy::wrong_self_convention,
474            )]
475            #[inline(always)]
476            #(#fn_modifiers)* fn #name(#maybe_mut #self_, #( #pats: #types ),*) -> #return_type
477            #where_clause
478            {
479                #body
480            }
481        }
482    }
483}
484
485struct Setter {
486    item: SetterItem,
487    imp: SetterImpl,
488}
489
490struct SetterImpl {
491    inputs: Vec<(syn::PatIdent, syn::Type)>,
492    body: SetterBody,
493}
494
495enum SetterBody {
496    /// The setter forwards the call to another method.
497    Forward { body: TokenStream },
498
499    /// The setter sets the member as usual and transitions the builder state.
500    SetMember { expr: TokenStream },
501}
502
503enum SettersItems {
504    Required(SetterItem),
505    Optional(OptionalSettersItems),
506}
507
508struct OptionalSettersItems {
509    some_fn: SetterItem,
510    option_fn: SetterItem,
511}
512
513struct SetterItem {
514    name: syn::Ident,
515    vis: syn::Visibility,
516    docs: Vec<syn::Attribute>,
517}
518
519impl SettersItems {
520    fn new(ctx: &SettersCtx<'_>) -> Self {
521        let SettersCtx { member, base } = ctx;
522        let builder_type = &base.builder_type;
523
524        let config = member.config.setters.as_ref();
525
526        let common_name = config.and_then(|config| config.name.as_deref());
527        let common_vis = config.and_then(|config| config.vis.as_deref());
528        let common_docs =
529            config.and_then(|config| config.doc.content.as_deref().map(Vec::as_slice));
530
531        let doc = |docs: &str| iter::once(syn::parse_quote!(#[doc = #docs]));
532
533        if member.is_required() {
534            let docs = common_docs.unwrap_or(&member.docs);
535
536            let header = "_**Required.**_\n\n";
537
538            let docs = doc(header).chain(docs.iter().cloned()).collect();
539
540            return Self::Required(SetterItem {
541                name: common_name.unwrap_or(&member.name.snake).clone(),
542                vis: common_vis.unwrap_or(&builder_type.vis).clone(),
543                docs,
544            });
545        }
546
547        let some_fn = config.and_then(|config| config.fns.some_fn.as_deref());
548        let some_fn_name = some_fn
549            .and_then(ItemSigConfig::name)
550            .or(common_name)
551            .unwrap_or(&member.name.snake)
552            .clone();
553
554        let option_fn = config.and_then(|config| config.fns.option_fn.as_deref());
555        let option_fn_name = option_fn
556            .and_then(ItemSigConfig::name)
557            .cloned()
558            .unwrap_or_else(|| {
559                let base_name = common_name.unwrap_or(&member.name.snake);
560                // It's important to preserve the original identifier span
561                // to make IDE's "go to definition" work correctly. It's so
562                // important that this doesn't use `format_ident!`, but rather
563                // `syn::Ident::new` to set the span of the `Ident` explicitly.
564                syn::Ident::new(&format!("maybe_{}", base_name.raw_name()), base_name.span())
565            });
566
567        let default = member.config.default.as_deref().and_then(|default| {
568            if let Some(setters) = &member.config.setters {
569                if let Some(default) = &setters.doc.default {
570                    if default.skip.is_present() {
571                        return None;
572                    }
573                }
574            }
575
576            let default = default
577                .clone()
578                .or_else(|| well_known_default(&member.ty.norm))
579                .unwrap_or_else(|| {
580                    let ty = &member.ty.norm;
581                    syn::parse_quote!(<#ty as Default>::default())
582                });
583
584            let file = syn::parse_quote!(const _: () = #default;);
585            let file = prettyplease::unparse(&file);
586
587            let begin = file.find('=')?;
588            let default = file.get(begin + 1..)?.trim();
589            let default = default.strip_suffix(';')?;
590
591            Some(default.to_owned())
592        });
593
594        let default = default.as_deref();
595
596        // FIXME: the docs shouldn't reference the companion setter if that
597        // setter has a lower visibility.
598        let some_fn_docs = some_fn
599            .and_then(ItemSigConfig::docs)
600            .or(common_docs)
601            .unwrap_or(&member.docs);
602
603        let some_fn_docs =
604            optional_setter_docs(default, &some_fn_name, &option_fn_name, some_fn_docs);
605
606        let option_fn_docs = option_fn
607            .and_then(ItemSigConfig::docs)
608            .or(common_docs)
609            .unwrap_or(&member.docs);
610
611        let option_fn_docs =
612            optional_setter_docs(default, &some_fn_name, &option_fn_name, option_fn_docs);
613
614        let some_fn = SetterItem {
615            name: some_fn_name,
616            vis: some_fn
617                .and_then(ItemSigConfig::vis)
618                .or(common_vis)
619                .unwrap_or(&builder_type.vis)
620                .clone(),
621
622            docs: some_fn_docs,
623        };
624
625        let option_fn = config.and_then(|config| config.fns.option_fn.as_deref());
626        let option_fn = SetterItem {
627            name: option_fn_name,
628
629            vis: option_fn
630                .and_then(ItemSigConfig::vis)
631                .or(common_vis)
632                .unwrap_or(&builder_type.vis)
633                .clone(),
634
635            docs: option_fn_docs,
636        };
637
638        Self::Optional(OptionalSettersItems { some_fn, option_fn })
639    }
640}
641
642fn optional_setter_docs(
643    default: Option<&str>,
644    some_fn: &syn::Ident,
645    option_fn: &syn::Ident,
646    doc_comments: &[syn::Attribute],
647) -> Vec<syn::Attribute> {
648    let header = format!(
649        "_**Optional** ([Some](Self::{some_fn}()) / [Option](Self::{option_fn}()) setters)._"
650    );
651
652    let mut attrs = vec![syn::parse_quote!(#[doc = #header])];
653
654    if let Some(default) = default {
655        let sep = if doc_comments.is_empty() { "" } else { "\n\n" };
656
657        if default.contains('\n') || default.len() > 80 {
658            // `no_doctest` helps to avoid interpreting the code block as
659            // an "ignored but still runnable" doc test. See details:
660            // - bon issue: https://github.com/elastio/bon/issues/359
661            // - rust issue: https://github.com/rust-lang/rust/issues/63193
662            let tail = format!("\n{default}\n````{sep}");
663            attrs.extend([
664                syn::parse_quote!(#[doc = " _**Default:**_\n"]),
665                syn::parse_quote!(#[cfg_attr(doctest, doc = "````no_doctest")]),
666                syn::parse_quote!(#[cfg_attr(not(doctest), doc = "````")]),
667                syn::parse_quote!(#[doc = #tail]),
668            ]);
669        } else {
670            let doc = format!(" _**Default:**_ ```{default}```.{sep}");
671            attrs.push(syn::parse_quote!(#[doc = #doc]));
672        }
673    }
674
675    attrs.extend(doc_comments.iter().cloned());
676    attrs
677}
678
679fn well_known_default(ty: &syn::Type) -> Option<syn::Expr> {
680    let path = match ty {
681        syn::Type::Path(syn::TypePath { path, qself: None }) => path,
682        _ => return None,
683    };
684
685    use syn::parse_quote as pq;
686
687    let ident = path.get_ident()?.to_string();
688
689    let value = match ident.as_str() {
690        "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64" | "i128"
691        | "isize" => pq!(0),
692        "f32" | "f64" => pq!(0.0),
693        "bool" => pq!(false),
694        "char" => pq!('\0'),
695        "String" => pq!(""),
696        _ => return None,
697    };
698
699    Some(value)
700}
701
702/// Unfortunately there is no `syn::Parse` impl for `PatIdent` directly,
703/// so we use this workaround instead.
704fn pat_ident(ident_name: &'static str) -> syn::PatIdent {
705    let ident = syn::Ident::new(ident_name, Span::call_site());
706    let pat: syn::Pat = syn::parse_quote!(#ident);
707    match pat {
708        syn::Pat::Ident(pat_ident) => pat_ident,
709        _ => unreachable!("can't parse something else than PatIdent here: {pat:?}"),
710    }
711}