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}