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