bon_macros/builder/builder_gen/
getters.rs1use super::member::{GetterConfig, GetterKind};
2use super::{BuilderGenCtx, NamedMember};
3use crate::parsing::SpannedKey;
4use crate::util::prelude::*;
5use syn::punctuated::Punctuated;
6use syn::spanned::Spanned;
7
8pub(crate) struct GettersCtx<'a> {
9 base: &'a BuilderGenCtx,
10 member: &'a NamedMember,
11 config: &'a GetterConfig,
12}
13
14impl<'a> GettersCtx<'a> {
15 pub(crate) fn new(base: &'a BuilderGenCtx, member: &'a NamedMember) -> Option<Self> {
16 Some(Self {
17 base,
18 member,
19 config: member.config.getter.as_ref()?,
20 })
21 }
22
23 pub(crate) fn getter_methods(self) -> Result<TokenStream> {
24 let name = self.config.name.as_deref().cloned().unwrap_or_else(|| {
25 syn::Ident::new(
26 &format!("get_{}", self.member.name.snake.raw_name()),
27 self.member.name.snake.span(),
28 )
29 });
30
31 let vis = self
32 .config
33 .vis
34 .as_deref()
35 .unwrap_or(&self.base.builder_type.vis)
36 .clone();
37
38 let docs = self.config.docs.as_deref().cloned().unwrap_or_else(|| {
39 let header = format!(
40 "_**Getter.**_ Returns `{}`, which must be set before calling this method.\n\n",
41 self.member.name.snake,
42 );
43
44 std::iter::once(syn::parse_quote!(#[doc = #header]))
45 .chain(self.member.docs.iter().cloned())
46 .collect()
47 });
48
49 let return_ty = self.return_ty()?;
50 let body = self.body();
51
52 let state_var = &self.base.state_var;
53 let member_pascal = &self.member.name.pascal;
54 let state_mod = &self.base.state_mod.ident;
55 let const_ = &self.base.const_;
56 let fn_modifiers = self.member.respan(quote!(#vis #const_));
57
58 let self_ = quote!(self);
62
63 let where_clause = self.member.is_stateful().then(|| {
64 quote! {
65 where
66 #state_var::#member_pascal: #state_mod::IsSet,
67 }
68 });
69
70 Ok(quote_spanned! {self.member.span=>
71 #( #docs )*
72 #[allow(
73 clippy::inline_always,
75 clippy::missing_const_for_fn,
76 )]
77 #[inline(always)]
78 #[must_use = "this method has no side effects; it only returns a value"]
79 #(#fn_modifiers)* fn #name(&#self_) -> #return_ty
80 #where_clause
81 {
82 #body
83 }
84 })
85 }
86
87 fn body(&self) -> TokenStream {
88 let index = &self.member.index;
89 let member = quote! {
90 self.__unsafe_private_named.#index
91 };
92
93 let bon = &self.base.bon;
94
95 match self.config.kind.as_deref() {
96 Some(GetterKind::Copy) => {
97 let span = self.member.underlying_orig_ty().span();
101 let ty = quote_spanned!(span=> _);
102
103 let copy = quote! {
104 #bon::__::better_errors::copy_member::<#ty>(&#member)
105 };
106
107 if !self.member.is_required() {
108 return copy;
109 }
110 quote! {
111 unsafe {
113 ::core::option::Option::unwrap_unchecked(#copy)
114 }
115 }
116 }
117 Some(GetterKind::Clone) => {
118 let span = self.member.underlying_orig_ty().span();
122 let ty = quote_spanned!(span=> _);
123
124 let clone = quote! {
125 <#ty as ::core::clone::Clone>::clone
126 };
127
128 if !self.member.is_required() {
129 return quote! {
130 #clone(&#member)
131 };
132 }
133 quote! {
134 match &#member {
135 Some(value) => #clone(value),
136
137 None => unsafe {
139 ::core::hint::unreachable_unchecked()
140 },
141 }
142 }
143 }
144 Some(GetterKind::Deref(ty)) => {
145 let span = ty.span();
148 let value = quote_spanned!(span=> value);
149
150 if !self.member.is_required() {
151 return quote! {
152 match &#member {
155 Some(#value) => Some(#value),
156 None => None,
157 }
158 };
159 }
160 quote! {
161 match &#member {
164 Some(#value) => #value,
165
166 None => unsafe {
168 ::core::hint::unreachable_unchecked()
169 },
170 }
171 }
172 }
173 None => {
174 if !self.member.is_required() {
175 return quote! {
176 ::core::option::Option::as_ref(&#member)
177 };
178 }
179 quote! {
180 match &#member {
181 Some(value) => value,
182
183 None => unsafe {
185 ::core::hint::unreachable_unchecked()
186 },
187 }
188 }
189 }
190 }
191 }
192
193 fn return_ty(&self) -> Result<TokenStream> {
194 let underlying_return_ty = self.underlying_return_ty()?;
195
196 Ok(if self.member.is_required() {
197 quote! { #underlying_return_ty }
198 } else {
199 quote! { Option<#underlying_return_ty> }
203 })
204 }
205
206 fn underlying_return_ty(&self) -> Result<TokenStream> {
207 let ty = self.member.underlying_norm_ty();
208
209 let kind = match &self.config.kind {
210 Some(kind) => kind,
211 None => return Ok(quote! { &#ty }),
212 };
213
214 match &kind.value {
215 GetterKind::Copy | GetterKind::Clone => Ok(quote! { #ty }),
216 GetterKind::Deref(Some(deref_target)) => Ok(quote! { &#deref_target }),
217 GetterKind::Deref(None) => Self::infer_deref_target(ty, kind),
218 }
219 }
220
221 fn infer_deref_target(
222 underlying_member_ty: &syn::Type,
223 kind: &SpannedKey<GetterKind>,
224 ) -> Result<TokenStream> {
225 use quote_spanned as qs;
226
227 let span = underlying_member_ty.span();
228
229 #[allow(clippy::type_complexity)]
230 let deref_target_inference_table: &[(_, &dyn Fn(&Punctuated<_, _>) -> _)] = &[
231 ("Vec", &|args| args.first().map(|arg| qs!(span=> [#arg]))),
232 ("Box", &|args| args.first().map(ToTokens::to_token_stream)),
233 ("Rc", &|args| args.first().map(ToTokens::to_token_stream)),
234 ("Arc", &|args| args.first().map(ToTokens::to_token_stream)),
235 ("String", &|args| args.is_empty().then(|| qs!(span=> str))),
236 ("CString", &|args| {
237 let module = if rustversion::cfg!(since(1.64.0)) {
240 format_ident!("core")
241 } else {
242 format_ident!("std")
243 };
244 args.is_empty().then(|| qs!(span=> ::#module::ffi::CStr))
245 }),
246 ("OsString", &|args| {
247 args.is_empty().then(|| qs!(span=> ::std::ffi::OsStr))
248 }),
249 ("PathBuf", &|args| {
250 args.is_empty().then(|| qs!(span=> ::std::path::Path))
251 }),
252 ("Cow", &|args| {
253 args.iter()
254 .find(|arg| matches!(arg, syn::GenericArgument::Type(_)))
255 .map(ToTokens::to_token_stream)
256 }),
257 ];
258
259 let err = || {
260 let inferable_types = deref_target_inference_table
261 .iter()
262 .map(|(name, _)| format!("- {name}"))
263 .join("\n");
264
265 err!(
266 &kind.key,
267 "can't infer the `Deref::Target` for the getter from the member's type; \
268 please specify the return type (target of the deref coercion) explicitly \
269 in parentheses without the leading `&`;\n\
270 example: `#[builder(getter(deref(TargetTypeHere))]`\n\
271 \n\
272 automatic deref target detection is supported only for the following types:\n\
273 {inferable_types}",
274 )
275 };
276
277 let path = underlying_member_ty.as_path_no_qself().ok_or_else(err)?;
278
279 let last_segment = path.segments.last().ok_or_else(err)?;
280
281 let empty_punctuated = Punctuated::new();
282
283 let args = match &last_segment.arguments {
284 syn::PathArguments::AngleBracketed(args) => &args.args,
285 _ => &empty_punctuated,
286 };
287
288 let last_segment_ident_str = last_segment.ident.to_string();
289
290 let inferred = deref_target_inference_table
291 .iter()
292 .find(|(name, _)| last_segment_ident_str == *name)
293 .and_then(|(_, infer)| infer(args))
294 .ok_or_else(err)?;
295
296 Ok(quote!(&#inferred))
297 }
298}