bon_macros/builder/builder_gen/
getters.rs
1use 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
56 Ok(quote! {
57 #( #docs )*
58 #[allow(
59 clippy::inline_always,
61 clippy::missing_const_for_fn,
62 )]
63 #[inline(always)]
64 #[must_use = "this method has no side effects; it only returns a value"]
65 #vis fn #name(&self) -> #return_ty
66 where
67 #state_var::#member_pascal: #state_mod::IsSet,
68 {
69 #body
70 }
71 })
72 }
73
74 fn body(&self) -> TokenStream {
75 let index = &self.member.index;
76 let member = quote! {
77 self.__unsafe_private_named.#index
78 };
79
80 let bon = &self.base.bon;
81
82 match self.config.kind.as_deref() {
83 Some(GetterKind::Copy) => {
84 let span = self.member.underlying_orig_ty().span();
88 let ty = quote_spanned!(span=> _);
89
90 let copy = quote! {
91 #bon::__::better_errors::copy_member::<#ty>(&#member)
92 };
93
94 if !self.member.is_required() {
95 return copy;
96 }
97 quote! {
98 unsafe {
100 ::core::option::Option::unwrap_unchecked(#copy)
101 }
102 }
103 }
104 Some(GetterKind::Clone) => {
105 let span = self.member.underlying_orig_ty().span();
109 let ty = quote_spanned!(span=> _);
110
111 let clone = quote! {
112 <#ty as ::core::clone::Clone>::clone
113 };
114
115 if !self.member.is_required() {
116 return quote! {
117 #clone(&#member)
118 };
119 }
120 quote! {
121 match &#member {
122 Some(value) => #clone(value),
123
124 None => unsafe {
126 ::core::hint::unreachable_unchecked()
127 },
128 }
129 }
130 }
131 Some(GetterKind::Deref(ty)) => {
132 let span = ty.span();
135 let value = quote_spanned!(span=> value);
136
137 if !self.member.is_required() {
138 return quote! {
139 match &#member {
142 Some(#value) => Some(#value),
143 None => None,
144 }
145 };
146 }
147 quote! {
148 match &#member {
151 Some(#value) => #value,
152
153 None => unsafe {
155 ::core::hint::unreachable_unchecked()
156 },
157 }
158 }
159 }
160 None => {
161 if !self.member.is_required() {
162 return quote! {
163 ::core::option::Option::as_ref(&#member)
164 };
165 }
166 quote! {
167 match &#member {
168 Some(value) => value,
169
170 None => unsafe {
172 ::core::hint::unreachable_unchecked()
173 },
174 }
175 }
176 }
177 }
178 }
179
180 fn return_ty(&self) -> Result<TokenStream> {
181 let underlying_return_ty = self.underlying_return_ty()?;
182
183 Ok(if self.member.is_required() {
184 quote! { #underlying_return_ty }
185 } else {
186 quote! { Option<#underlying_return_ty> }
190 })
191 }
192
193 fn underlying_return_ty(&self) -> Result<TokenStream> {
194 let ty = self.member.underlying_norm_ty();
195
196 let kind = match &self.config.kind {
197 Some(kind) => kind,
198 None => return Ok(quote! { &#ty }),
199 };
200
201 match &kind.value {
202 GetterKind::Copy | GetterKind::Clone => Ok(quote! { #ty }),
203 GetterKind::Deref(Some(deref_target)) => Ok(quote! { &#deref_target }),
204 GetterKind::Deref(None) => Self::infer_deref_target(ty, kind),
205 }
206 }
207
208 fn infer_deref_target(
209 underlying_member_ty: &syn::Type,
210 kind: &SpannedKey<GetterKind>,
211 ) -> Result<TokenStream> {
212 use quote_spanned as qs;
213
214 let span = underlying_member_ty.span();
215
216 #[allow(clippy::type_complexity)]
217 let deref_target_inference_table: &[(_, &dyn Fn(&Punctuated<_, _>) -> _)] = &[
218 ("Vec", &|args| args.first().map(|arg| qs!(span=> [#arg]))),
219 ("Box", &|args| args.first().map(ToTokens::to_token_stream)),
220 ("Rc", &|args| args.first().map(ToTokens::to_token_stream)),
221 ("Arc", &|args| args.first().map(ToTokens::to_token_stream)),
222 ("String", &|args| args.is_empty().then(|| qs!(span=> str))),
223 ("CString", &|args| {
224 let module = if rustversion::cfg!(since(1.64.0)) {
227 format_ident!("core")
228 } else {
229 format_ident!("std")
230 };
231 args.is_empty().then(|| qs!(span=> ::#module::ffi::CStr))
232 }),
233 ("OsString", &|args| {
234 args.is_empty().then(|| qs!(span=> ::std::ffi::OsStr))
235 }),
236 ("PathBuf", &|args| {
237 args.is_empty().then(|| qs!(span=> ::std::path::Path))
238 }),
239 ("Cow", &|args| {
240 args.iter()
241 .find(|arg| matches!(arg, syn::GenericArgument::Type(_)))
242 .map(ToTokens::to_token_stream)
243 }),
244 ];
245
246 let err = || {
247 let inferable_types = deref_target_inference_table
248 .iter()
249 .map(|(name, _)| format!("- {name}"))
250 .join("\n");
251
252 err!(
253 &kind.key,
254 "can't infer the `Deref::Target` for the getter from the member's type; \
255 please specify the return type (target of the deref coercion) explicitly \
256 in parentheses without the leading `&`;\n\
257 example: `#[builder(getter(deref(TargetTypeHere))]`\n\
258 \n\
259 automatic deref target detection is supported only for the following types:\n\
260 {inferable_types}",
261 )
262 };
263
264 let path = underlying_member_ty.as_path_no_qself().ok_or_else(err)?;
265
266 let last_segment = path.segments.last().ok_or_else(err)?;
267
268 let empty_punctuated = Punctuated::new();
269
270 let args = match &last_segment.arguments {
271 syn::PathArguments::AngleBracketed(args) => &args.args,
272 _ => &empty_punctuated,
273 };
274
275 let last_segment_ident_str = last_segment.ident.to_string();
276
277 let inferred = deref_target_inference_table
278 .iter()
279 .find(|(name, _)| last_segment_ident_str == *name)
280 .and_then(|(_, infer)| infer(args))
281 .ok_or_else(err)?;
282
283 Ok(quote!(&#inferred))
284 }
285}