bon_macros/builder/builder_gen/start_fn.rs
1use super::member::PosFnMember;
2use crate::util::prelude::*;
3
4impl super::BuilderGenCtx {
5 pub(super) fn start_fn(&self) -> syn::ItemFn {
6 let builder_ident = &self.builder_type.ident;
7 let docs = &self.start_fn.docs;
8 let vis = &self.start_fn.vis;
9
10 let start_fn_ident = &self.start_fn.ident;
11
12 // TODO: we can use a shorter syntax with anonymous lifetimes to make
13 // the generated code and function signature displayed by rust-analyzer
14 // a bit shorter and easier to read. However, the caveat is that we can
15 // do this only for lifetimes that have no bounds and if they don't appear
16 // in the where clause. Research `darling`'s lifetime tracking API and
17 // maybe implement this in the future
18
19 let generics = self.start_fn.generics.as_ref().unwrap_or(&self.generics);
20
21 let generics_decl = &generics.decl_without_defaults;
22 let where_clause = &generics.where_clause;
23 let generic_args = &self.generics.args;
24
25 let receiver = self.receiver();
26
27 let receiver_field_init = receiver.map(|receiver| {
28 let ident = &receiver.field_ident;
29 let self_token = &receiver.with_self_keyword.self_token;
30 quote! {
31 #ident: #self_token,
32 }
33 });
34
35 let receiver = receiver.map(|receiver| {
36 let mut receiver = receiver.with_self_keyword.clone();
37
38 if receiver.reference.is_none() {
39 receiver.mutability = None;
40 }
41
42 quote! { #receiver, }
43 });
44
45 let start_fn_params = self.start_fn_args().map(PosFnMember::fn_input_param);
46
47 // Assign `start_fn_args` to intermediate variables, which may be used
48 // by custom fields init expressions. This is needed only if there is
49 // a conversion configured for the `start_fn` members, otherwise these
50 // are already available in scope as function arguments directly.
51 let start_fn_vars = self.start_fn_args().filter_map(|member| {
52 let ident = &member.ident;
53 let ty = &member.ty.orig;
54 let conversion = member.conversion()?;
55
56 Some(quote! {
57 let #ident: #ty = #conversion;
58 })
59 });
60
61 let start_fn_args_fields_idents = self.start_fn_args().map(|member| &member.ident);
62
63 // Create custom fields in separate variables. This way custom fields
64 // declared lower in the struct definition can reference custom fields
65 // declared higher in their init expressions.
66 let custom_fields_vars = self.custom_fields().map(|field| {
67 let ident = &field.ident;
68 let ty = &field.norm_ty;
69 let init = field
70 .init
71 .as_ref()
72 .map(|init| self.sanitize_expr(init))
73 .unwrap_or_else(|| quote! { ::core::default::Default::default() });
74
75 quote! {
76 let #ident: #ty = #init;
77 }
78 });
79
80 let custom_fields_idents = self.custom_fields().map(|field| &field.ident);
81
82 let ide_hints = self.ide_hints();
83
84 // `Default` trait implementation is provided only for tuples up to 12
85 // elements in the standard library 😳:
86 // https://github.com/rust-lang/rust/blob/67bb749c2e1cf503fee64842963dd3e72a417a3f/library/core/src/tuple.rs#L213
87 let named_members_field_init =
88 if self.named_members().take(13).count() <= 12 && self.const_.is_none() {
89 quote!(::core::default::Default::default())
90 } else {
91 let none = format_ident!("None");
92 let nones = self.named_members().map(|_| &none);
93 quote! {
94 (#(#nones,)*)
95 }
96 };
97
98 let const_ = &self.const_;
99 // add the `clippy::needless_lifetimes` lint if before rust version 1.87
100 // Rust version 1.87 includes a clippy change where `needless_lifetimes`
101 // was split with the more complex part of the lint going to
102 // `elidable_lifetime_names`. For versions since 1.87 we want to block
103 // `elidable_lifetime_names` (See
104 // https://github.com/elastio/bon/pull/341#discussion_r2398893516 for
105 // an explanation).
106 let needless_lifetime_lint = if rustversion::cfg!(before(1.87)) {
107 format_ident!("needless_lifetimes")
108 } else {
109 format_ident!("elidable_lifetime_names")
110 };
111
112 // Construct using a span which links to our original implementation.
113 // This ensures rustdoc doesn't just link every method to the macro
114 // callsite.
115 syn::parse_quote_spanned! {self.start_fn.span=>
116 #(#docs)*
117 #[inline(always)]
118 #[allow(
119 // This is intentional. We want the builder syntax to compile away
120 clippy::inline_always,
121 // We normalize `Self` references intentionally to simplify code generation
122 clippy::use_self,
123 // Let's keep it as non-const for now to avoid restricting ourselfves to only
124 // const operations.
125 clippy::missing_const_for_fn,
126 clippy::#needless_lifetime_lint
127 )]
128 #vis #const_ fn #start_fn_ident< #(#generics_decl),* >(
129 #receiver
130 #(#start_fn_params,)*
131 ) -> #builder_ident< #(#generic_args,)* >
132 #where_clause
133 {
134 #ide_hints
135 #( #start_fn_vars )*
136 #( #custom_fields_vars )*
137
138 #builder_ident {
139 __unsafe_private_phantom: ::core::marker::PhantomData,
140 #( #custom_fields_idents, )*
141 #receiver_field_init
142 #( #start_fn_args_fields_idents, )*
143 __unsafe_private_named: #named_members_field_init,
144 }
145 }
146 }
147 }
148}