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}