bon_macros/builder/builder_gen/member/
named.rs1use super::config::MemberConfig;
2use super::{config, MemberOrigin};
3use crate::builder::builder_gen::member::config::SettersFnsConfig;
4use crate::builder::builder_gen::top_level_config::OnConfig;
5use crate::normalization::SyntaxVariant;
6use crate::parsing::{ItemSigConfig, SpannedKey};
7use crate::util::prelude::*;
8
9#[derive(Debug)]
10pub(crate) struct MemberName {
11 pub(crate) orig: syn::Ident,
14
15 pub(crate) snake: syn::Ident,
19
20 pub(crate) snake_raw_str: String,
25
26 pub(crate) pascal: syn::Ident,
31
32 pub(crate) pascal_str: String,
40}
41
42impl MemberName {
43 pub(crate) fn new(orig: syn::Ident, config: &MemberConfig) -> Self {
44 let snake = config.name.clone().unwrap_or_else(|| {
45 let orig_str = orig.to_string();
46 let norm = orig_str
47 .strip_prefix('_')
51 .unwrap_or(&orig_str);
52
53 syn::Ident::new_maybe_raw(norm, orig.span())
56 });
57
58 let pascal = snake.snake_to_pascal_case();
59
60 Self {
61 orig,
62 snake_raw_str: snake.raw_name(),
63 snake,
64 pascal_str: pascal.to_string(),
65 pascal,
66 }
67 }
68}
69
70#[derive(Debug)]
72pub(crate) struct NamedMember {
73 pub(crate) origin: MemberOrigin,
75
76 pub(crate) index: syn::Index,
78
79 pub(crate) name: MemberName,
82
83 pub(crate) docs: Vec<syn::Attribute>,
86
87 pub(crate) ty: SyntaxVariant<Box<syn::Type>>,
90
91 pub(crate) config: MemberConfig,
93}
94
95impl NamedMember {
96 pub(super) fn validate(&self) -> Result {
97 if let Some(default) = &self.config.default {
98 if self.is_special_option_ty() {
99 bail!(
100 &default.key,
101 "`Option<_>` already implies a default of `None`, \
102 so explicit #[builder(default)] is redundant",
103 );
104 }
105 }
106
107 let member_docs_not_copied = self
108 .config
109 .setters
110 .as_ref()
111 .map(|setters| {
112 if setters.doc.content.is_some() {
113 return true;
114 }
115
116 let SettersFnsConfig { some_fn, option_fn } = &setters.fns;
117 matches!(
118 (some_fn.as_deref(), option_fn.as_deref()),
119 (
120 Some(ItemSigConfig { docs: Some(_), .. }),
121 Some(ItemSigConfig { docs: Some(_), .. })
122 )
123 )
124 })
125 .unwrap_or(false);
126
127 if !member_docs_not_copied {
128 crate::parsing::reject_self_mentions_in_docs(
129 "builder struct's impl block",
130 &self.docs,
131 )?;
132 }
133
134 self.validate_setters_config()?;
135
136 if self.config.required.is_present() && !self.ty.norm.is_option() {
137 bail!(
138 &self.config.required.span(),
139 "`#[builder(required)]` can only be applied to members of \
140 type `Option<T>` to disable their special handling",
141 );
142 }
143
144 Ok(())
145 }
146
147 fn validate_setters_config(&self) -> Result {
148 let setters = match &self.config.setters {
149 Some(setters) => setters,
150 None => return Ok(()),
151 };
152
153 if self.is_required() {
154 let SettersFnsConfig { some_fn, option_fn } = &setters.fns;
155
156 let unexpected_setter = option_fn.as_ref().or(some_fn.as_ref());
157
158 if let Some(setter) = unexpected_setter {
159 bail!(
160 &setter.key,
161 "`{}` setter function applies only to members with `#[builder(default)]` \
162 or members of `Option<T>` type (if #[builder(required)] is not set)",
163 setter.key
164 );
165 }
166 }
167
168 if let SettersFnsConfig {
169 some_fn: Some(some_fn),
170 option_fn: Some(option_fn),
171 } = &setters.fns
172 {
173 let setter_fns = &[some_fn, option_fn];
174
175 Self::validate_unused_setters_cfg(setter_fns, &setters.name, |config| &config.name)?;
176 Self::validate_unused_setters_cfg(setter_fns, &setters.vis, |config| &config.vis)?;
177 Self::validate_unused_setters_cfg(setter_fns, &setters.doc.content, |config| {
178 &config.docs
179 })?;
180 }
181
182 Ok(())
183 }
184
185 #[allow(unknown_lints, clippy::ref_option)]
187 fn validate_unused_setters_cfg<T>(
188 overrides: &[&SpannedKey<ItemSigConfig>],
189 config: &Option<SpannedKey<T>>,
190 get_val: impl Fn(&ItemSigConfig) -> &Option<SpannedKey<T>>,
191 ) -> Result {
192 let config = match config {
193 Some(config) => config,
194 None => return Ok(()),
195 };
196
197 let overrides_values = overrides
198 .iter()
199 .copied()
200 .map(|over| get_val(&over.value).as_ref());
201
202 if !overrides_values.clone().all(|over| over.is_some()) {
203 return Ok(());
204 }
205
206 let setters = overrides
207 .iter()
208 .map(|over| format!("`{}`", over.key))
209 .join(", ");
210
211 bail!(
212 &config.key,
213 "this `{name}` configuration is unused because all of the \
214 {setters} setters contain a `{name}` override",
215 name = config.key,
216 );
217 }
218
219 pub(crate) fn is_special_option_ty(&self) -> bool {
222 !self.config.required.is_present() && self.ty.norm.is_option()
223 }
224
225 pub(crate) fn is_required(&self) -> bool {
228 self.config.default.is_none() && !self.is_special_option_ty()
229 }
230
231 pub(crate) fn is_stateful(&self) -> bool {
236 self.is_required() || !self.config.overwritable.is_present()
237 }
238
239 pub(crate) fn underlying_norm_ty(&self) -> &syn::Type {
242 self.underlying_ty(&self.ty.norm)
243 }
244
245 pub(crate) fn underlying_orig_ty(&self) -> &syn::Type {
248 self.underlying_ty(&self.ty.orig)
249 }
250
251 fn underlying_ty<'m>(&'m self, ty: &'m syn::Type) -> &'m syn::Type {
252 if self.config.required.is_present() || self.config.default.is_some() {
253 ty
254 } else {
255 ty.option_type_param().unwrap_or(ty)
256 }
257 }
258
259 pub(crate) fn is(&self, other: &Self) -> bool {
260 self.index == other.index
261 }
262
263 pub(crate) fn merge_on_config(&mut self, on: &[OnConfig]) -> Result {
264 if let Some(on) = on.first().filter(|on| on.required.is_present()) {
268 if self.is_special_option_ty() {
269 self.config.required = on.required;
270 }
271 }
272
273 self.merge_config_into(on)?;
274 self.merge_setters_doc_skip(on)?;
275
276 self.config.overwritable = config::EvalBlanketFlagParam {
280 on,
281 param_name: config::BlanketParamName::Overwritable,
282 member_config: &self.config,
283 scrutinee: self.underlying_norm_ty(),
284 origin: self.origin,
285 }
286 .eval()?;
287
288 Ok(())
289 }
290
291 fn merge_setters_doc_skip(&mut self, on: &[OnConfig]) -> Result {
292 let skip = config::EvalBlanketFlagParam {
293 on,
294 param_name: config::BlanketParamName::SettersDocDefaultSkip,
295 member_config: &self.config,
296 scrutinee: self.underlying_norm_ty(),
297 origin: self.origin,
298 }
299 .eval()?;
300
301 let setters = self.config.setters.get_or_insert_with(Default::default);
302 let default = setters.doc.default.get_or_insert_with(Default::default);
303
304 default.skip = skip;
305
306 Ok(())
307 }
308}