bon_macros/builder/builder_gen/finish_fn.rs
1use super::member::{Member, PosFnMember};
2use crate::util::prelude::*;
3
4impl super::BuilderGenCtx {
5 fn finish_fn_member_expr(&self, member: &Member) -> TokenStream {
6 let member = match member {
7 Member::Named(member) => member,
8 Member::Skip(member) => {
9 return member
10 .value
11 .as_ref()
12 .map(|value| self.sanitize_expr(value))
13 .unwrap_or_else(|| quote! { ::core::default::Default::default() });
14 }
15 Member::StartFn(member) => {
16 let ident = &member.ident;
17 return quote! { self.#ident };
18 }
19 Member::FinishFn(member) => {
20 return member
21 .conversion()
22 .unwrap_or_else(|| member.ident.to_token_stream());
23 }
24 Member::Field(member) => {
25 let ident = &member.ident;
26 return quote! { self.#ident };
27 }
28 };
29
30 let index = &member.index;
31
32 let member_field = quote! {
33 self.__unsafe_private_named.#index
34 };
35
36 let default = member
37 .config
38 .default
39 .as_ref()
40 .map(|default| default.value.as_ref());
41
42 match default {
43 Some(Some(default)) => {
44 let default = if member.config.into.is_present() {
45 quote! { Into::into((|| #default)()) }
46 } else {
47 quote! { #default }
48 };
49
50 // Special case for `const` because `unwrap_or_else` is not `const`
51 // and closure calls aren't supported in `const` contexts at the time
52 // of this writing (Rust 1.86.0).
53 if self.const_.is_some() {
54 return quote! {
55 match #member_field {
56 Some(value) => value,
57 None => #default,
58 }
59 };
60 }
61
62 quote! {
63 ::core::option::Option::unwrap_or_else(#member_field, || #default)
64 }
65 }
66 Some(None) => {
67 quote! {
68 ::core::option::Option::unwrap_or_default(#member_field)
69 }
70 }
71 None => {
72 // For `Option` the default value is always `None`. So we can just return
73 // the value of the member field itself (which is already an `Option<T>`).
74 if member.is_special_option_ty() {
75 return member_field;
76 }
77
78 // SAFETY: we know that the member is set because we are in
79 // the `finish` function where this method uses the trait
80 // bound of `IsSet` for every required member. It's also
81 // not possible to intervene with the builder's state from
82 // the outside because all members of the builder are considered
83 // private (we even generate random names for them to make it
84 // impossible to access them from the outside in the same module).
85 //
86 // We also make sure to use fully qualified paths to methods
87 // involved in setting the value for the required member to make
88 // sure no trait/function in scope can override the behavior.
89
90 // Special case for `const` mode where `unwrap_unchecked` is
91 // unstable in Rust <1.83.0.
92 if self.const_.is_some() {
93 return quote! {
94 match #member_field {
95 Some(value) => value,
96 // SAFETY: see the big safety comment above
97 None => unsafe { ::core::hint::unreachable_unchecked() },
98 }
99 };
100 }
101
102 quote! {
103 // SAFETY: see the big safety comment above
104 unsafe {
105 ::core::option::Option::unwrap_unchecked(#member_field)
106 }
107 }
108 }
109 }
110 }
111
112 pub(super) fn finish_fn(&self) -> TokenStream {
113 let members_vars_decls = self.members.iter().map(|member| {
114 let expr = self.finish_fn_member_expr(member);
115 let var_ident = member.orig_ident();
116
117 // The type hint is necessary in some cases to assist the compiler
118 // in type inference.
119 //
120 // For example, if the expression is passed to a function that accepts
121 // an impl Trait such as `impl Default`, and the expression itself looks
122 // like `Default::default()`. In this case nothing hints to the compiler
123 // the resulting type of the expression, so we add a type hint via an
124 // intermediate variable here.
125 //
126 // This variable can also be accessed by other member's `default`
127 // or `skip` expressions.
128 let ty = member.norm_ty();
129
130 quote! {
131 let #var_ident: #ty = #expr;
132 }
133 });
134
135 let state_mod = &self.state_mod.ident;
136
137 let finish_fn_params = self.finish_fn_args().map(PosFnMember::fn_input_param);
138
139 let body = &self.finish_fn.body.generate(self);
140 let asyncness = &self.finish_fn.asyncness;
141 let unsafety = &self.finish_fn.unsafety;
142 let special_attrs = &self.finish_fn.special_attrs;
143 let attrs = &self.finish_fn.attrs;
144 let finish_fn_vis = &self.finish_fn.vis;
145 let finish_fn_ident = &self.finish_fn.ident;
146 let output = &self.finish_fn.output;
147 let state_var = &self.state_var;
148 let const_ = &self.const_;
149
150 // `#[target_feature]` is not compatible with `#[inline(always)]`,
151 // so we need to downgrade it to `#[inline]
152 let inline_attr = self
153 .finish_fn
154 .special_attrs
155 .iter()
156 .find_map(|attr| {
157 attr.meta
158 .path()
159 .is_ident("target_feature")
160 .then(|| quote! { #[inline] })
161 })
162 .unwrap_or_else(|| quote! { #[inline(always)] });
163
164 quote! {
165 #(#attrs)*
166 #inline_attr
167 #[allow(
168 // This is intentional. We want the builder syntax to compile away
169 clippy::inline_always,
170
171 // This lint flags any function that returns a possibly `!Send` future.
172 // However, it doesn't apply in the generic context where the future is
173 // `Send` if the generic parameters are `Send` as well, so we just suppress
174 // this lint. See the issue: https://github.com/rust-lang/rust-clippy/issues/6947
175 clippy::future_not_send,
176 clippy::missing_const_for_fn,
177 )]
178 #(#special_attrs)*
179 #finish_fn_vis #const_ #asyncness #unsafety fn #finish_fn_ident(
180 self,
181 #(#finish_fn_params,)*
182 ) #output
183 where
184 #state_var: #state_mod::IsComplete
185 {
186 #(#members_vars_decls)*
187 #body
188 }
189 }
190 }
191}