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(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| quote! { (|| #value)() })
13                    .unwrap_or_else(|| quote! { ::core::default::Default::default() });
14            }
15            Member::StartFn(member) => {
16                let index = &member.index;
17
18                return quote! { self.__unsafe_private_start_fn_args.#index };
19            }
20            Member::FinishFn(member) => {
21                return member
22                    .conversion()
23                    .unwrap_or_else(|| member.ident.to_token_stream());
24            }
25            Member::Field(member) => {
26                let ident = &member.ident;
27                return quote! { self.#ident };
28            }
29        };
30
31        let index = &member.index;
32
33        let member_field = quote! {
34            self.__unsafe_private_named.#index
35        };
36
37        let default = member
38            .config
39            .default
40            .as_ref()
41            .map(|default| default.value.as_ref());
42
43        match default {
44            Some(Some(default)) => {
45                let default = if member.config.into.is_present() {
46                    quote! { Into::into((|| #default)()) }
47                } else {
48                    quote! { #default }
49                };
50
51                quote! {
52                    ::core::option::Option::unwrap_or_else(#member_field, || #default)
53                }
54            }
55            Some(None) => {
56                quote! {
57                    ::core::option::Option::unwrap_or_default(#member_field)
58                }
59            }
60            None => {
61                // For `Option` the default value is always `None`. So we can just return
62                // the value of the member field itself (which is already an `Option<T>`).
63                if member.is_special_option_ty() {
64                    return member_field;
65                }
66
67                quote! {
68                    unsafe {
69                        // SAFETY: we know that the member is set because we are in
70                        // the `finish` function where this method uses the trait
71                        // bound of `IsSet` for every required member. It's also
72                        // not possible to intervene with the builder's state from
73                        // the outside because all members of the builder are considered
74                        // private (we even generate random names for them to make it
75                        // impossible to access them from the outside in the same module).
76                        //
77                        // We also make sure to use fully qualified paths to methods
78                        // involved in setting the value for the required member to make
79                        // sure no trait/function in scope can override the behavior.
80                        ::core::option::Option::unwrap_unchecked(#member_field)
81                    }
82                }
83            }
84        }
85    }
86
87    pub(super) fn finish_fn(&self) -> TokenStream {
88        let members_vars_decls = self.members.iter().map(|member| {
89            let expr = Self::finish_fn_member_expr(member);
90            let var_ident = member.orig_ident();
91
92            // The type hint is necessary in some cases to assist the compiler
93            // in type inference.
94            //
95            // For example, if the expression is passed to a function that accepts
96            // an impl Trait such as `impl Default`, and the expression itself looks
97            // like `Default::default()`. In this case nothing hints to the compiler
98            // the resulting type of the expression, so we add a type hint via an
99            // intermediate variable here.
100            //
101            // This variable can also be accessed by other member's `default`
102            // or `skip` expressions.
103            let ty = member.norm_ty();
104
105            quote! {
106                let #var_ident: #ty = #expr;
107            }
108        });
109
110        let state_mod = &self.state_mod.ident;
111
112        let finish_fn_params = self
113            .members
114            .iter()
115            .filter_map(Member::as_finish_fn)
116            .map(PosFnMember::fn_input_param);
117
118        let body = &self.finish_fn.body.generate(self);
119        let asyncness = &self.finish_fn.asyncness;
120        let unsafety = &self.finish_fn.unsafety;
121        let must_use = &self.finish_fn.must_use;
122        let attrs = &self.finish_fn.attrs;
123        let finish_fn_vis = &self.finish_fn.vis;
124        let finish_fn_ident = &self.finish_fn.ident;
125        let output = &self.finish_fn.output;
126        let state_var = &self.state_var;
127
128        quote! {
129            #(#attrs)*
130            #[inline(always)]
131            #[allow(
132                // This is intentional. We want the builder syntax to compile away
133                clippy::inline_always,
134
135                // This lint flags any function that returns a possibly `!Send` future.
136                // However, it doesn't apply in the generic context where the future is
137                // `Send` if the generic parameters are `Send` as well, so we just suppress
138                // this lint. See the issue: https://github.com/rust-lang/rust-clippy/issues/6947
139                clippy::future_not_send,
140                clippy::missing_const_for_fn,
141            )]
142            #must_use
143            #finish_fn_vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output
144            where
145                #state_var: #state_mod::IsComplete
146            {
147                #(#members_vars_decls)*
148                #body
149            }
150        }
151    }
152}