1use 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::*;
89#[derive(Debug)]
10pub(crate) struct MemberName {
11/// Original name of the member (unchanged). It's used in the finishing
12 /// function of the builder to create a variable for each member.
13pub(crate) orig: syn::Ident,
1415/// `snake_case` version of the member name. By default it's the `orig` name
16 /// itself with the `_` prefix stripped. Otherwise the user can override it
17 /// via `#[builder(name = custom_name)]`
18pub(crate) snake: syn::Ident,
1920/// `snake_case` version of the member name as a string without the `r#` prefix
21 /// (if there is any in the `snake` representation). It's computed and
22 /// stored separately to avoid recomputing it multiple times. It's used
23 /// to derive names for other identifiers that are based on the `snake_case` name.
24pub(crate) snake_raw_str: String,
2526/// `PascalCase` version of the member name. It's always computed as the
27 /// `snake` variant converted to `PascalCase`. The user doesn't have the
28 /// granular control over this name. Users can only specify the snake case
29 /// version of the name, and the pascal case is derived from it.
30pub(crate) pascal: syn::Ident,
3132/// `PascalCase` version of the member name as a string. It's computed and
33 /// stored separately to avoid recomputing it multiple times. It's guaranteed
34 /// to not have the `r#` prefix because:
35 ///
36 /// There are no pascal case keywords in Rust except for `Self`, which
37 /// is anyway not allowed even as a raw identifier:
38 /// <https://internals.rust-lang.org/t/raw-identifiers-dont-work-for-all-identifiers/9094>
39pub(crate) pascal_str: String,
40}
4142impl MemberName {
43pub(crate) fn new(orig: syn::Ident, config: &MemberConfig) -> Self {
44let snake = config.name.clone().unwrap_or_else(|| {
45let orig_str = orig.to_string();
46let norm = orig_str
47// Remove the leading underscore from the member name since it's used
48 // to denote unused symbols in Rust. That doesn't mean the builder
49 // API should expose that knowledge to the caller.
50.strip_prefix('_')
51 .unwrap_or(&orig_str);
5253// Preserve the original identifier span to make IDE's "go to definition" work correctly
54 // and make error messages point to the correct place.
55syn::Ident::new_maybe_raw(norm, orig.span())
56 });
5758let pascal = snake.snake_to_pascal_case();
5960Self {
61 orig,
62 snake_raw_str: snake.raw_name(),
63 snake,
64 pascal_str: pascal.to_string(),
65 pascal,
66 }
67 }
68}
6970/// Regular member for which the builder should have setter methods
71#[derive(Debug)]
72pub(crate) struct NamedMember {
73/// Specifies what syntax the member comes from.
74pub(crate) origin: MemberOrigin,
7576/// Index of the member relative to other named members. The index is 0-based.
77pub(crate) index: syn::Index,
7879/// Name of the member is used to generate names for the setters, names for
80 /// the associated types and type aliases in the builder state, etc.
81pub(crate) name: MemberName,
8283/// Doc comments on top of the original syntax. These are copied to the setters
84 /// unless there are overrides for them.
85pub(crate) docs: Vec<syn::Attribute>,
8687/// Type of the member has to be known to generate the types for fields in
88 /// the builder, signatures of the setter methods, etc.
89pub(crate) ty: SyntaxVariant<Box<syn::Type>>,
9091/// Parameters configured by the user explicitly via attributes
92pub(crate) config: MemberConfig,
93}
9495impl NamedMember {
96pub(super) fn validate(&self) -> Result {
97if let Some(default) = &self.config.default {
98if self.is_special_option_ty() {
99bail!(
100&default.key,
101"`Option<_>` already implies a default of `None`, \
102 so explicit #[builder(default)] is redundant",
103 );
104 }
105 }
106107let member_docs_not_copied = self
108.config
109 .setters
110 .as_ref()
111 .map(|setters| {
112if setters.docs.is_some() {
113return true;
114 }
115116let SettersFnsConfig { some_fn, option_fn } = &setters.fns;
117matches!(
118 (some_fn.as_deref(), option_fn.as_deref()),
119 (
120Some(ItemSigConfig { docs: Some(_), .. }),
121Some(ItemSigConfig { docs: Some(_), .. })
122 )
123 )
124 })
125 .unwrap_or(false);
126127if !member_docs_not_copied {
128crate::parsing::reject_self_mentions_in_docs(
129"builder struct's impl block",
130&self.docs,
131 )?;
132 }
133134self.validate_setters_config()?;
135136if self.config.required.is_present() && !self.ty.norm.is_option() {
137bail!(
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 }
143144Ok(())
145 }
146147fn validate_setters_config(&self) -> Result {
148let setters = match &self.config.setters {
149Some(setters) => setters,
150None => return Ok(()),
151 };
152153if self.is_required() {
154let SettersFnsConfig { some_fn, option_fn } = &setters.fns;
155156let unexpected_setter = option_fn.as_ref().or(some_fn.as_ref());
157158if let Some(setter) = unexpected_setter {
159bail!(
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 }
167168if let SettersFnsConfig {
169 some_fn: Some(some_fn),
170 option_fn: Some(option_fn),
171 } = &setters.fns
172 {
173let setter_fns = &[some_fn, option_fn];
174175Self::validate_unused_setters_cfg(setter_fns, &setters.name, |config| &config.name)?;
176Self::validate_unused_setters_cfg(setter_fns, &setters.vis, |config| &config.vis)?;
177Self::validate_unused_setters_cfg(setter_fns, &setters.docs, |config| &config.docs)?;
178 }
179180Ok(())
181 }
182183// Lint from nightly. `&Option<T>` is used to reduce syntax at the call site
184#[allow(unknown_lints, clippy::ref_option)]
185fn validate_unused_setters_cfg<T>(
186 overrides: &[&SpannedKey<ItemSigConfig>],
187 config: &Option<SpannedKey<T>>,
188 get_val: impl Fn(&ItemSigConfig) -> &Option<SpannedKey<T>>,
189 ) -> Result {
190let config = match config {
191Some(config) => config,
192None => return Ok(()),
193 };
194195let overrides_values = overrides
196 .iter()
197 .copied()
198 .map(|over| get_val(&over.value).as_ref());
199200if !overrides_values.clone().all(|over| over.is_some()) {
201return Ok(());
202 }
203204let setters = overrides
205 .iter()
206 .map(|over| format!("`{}`", over.key))
207 .join(", ");
208209bail!(
210&config.key,
211"this `{name}` configuration is unused because all of the \
212 {setters} setters contain a `{name}` override",
213 name = config.key,
214 );
215 }
216217/// Returns `true` if this member is of `Option<_>` type, but returns `false`
218 /// if `#[builder(required)]` is set.
219pub(crate) fn is_special_option_ty(&self) -> bool {
220 !self.config.required.is_present() && self.ty.norm.is_option()
221 }
222223/// Returns `false` if the member has a default value. It means this member
224 /// is required to be set before building can be finished.
225pub(crate) fn is_required(&self) -> bool {
226self.config.default.is_none() && !self.is_special_option_ty()
227 }
228229/// A stateful member is the one that has a corresponding associated type in
230 /// the builder's type state trait. This is used to track the fact that the
231 /// member was set or not. This is necessary to make sure all members without
232 /// default values are set before building can be finished.
233pub(crate) fn is_stateful(&self) -> bool {
234self.is_required() || !self.config.overwritable.is_present()
235 }
236237/// Returns the normalized type of the member stripping the `Option<_>`
238 /// wrapper if it's present unless `#[builder(required)]` is set.
239pub(crate) fn underlying_norm_ty(&self) -> &syn::Type {
240self.underlying_ty(&self.ty.norm)
241 }
242243/// Returns the original type of the member stripping the `Option<_>`
244 /// wrapper if it's present unless `#[builder(required)]` is set.
245pub(crate) fn underlying_orig_ty(&self) -> &syn::Type {
246self.underlying_ty(&self.ty.orig)
247 }
248249fn underlying_ty<'m>(&'m self, ty: &'m syn::Type) -> &'m syn::Type {
250if self.config.required.is_present() || self.config.default.is_some() {
251 ty
252 } else {
253 ty.option_type_param().unwrap_or(ty)
254 }
255 }
256257pub(crate) fn is(&self, other: &Self) -> bool {
258self.index == other.index
259 }
260261pub(crate) fn merge_on_config(&mut self, on: &[OnConfig]) -> Result {
262// This is a temporary hack. We only allow `on(_, required)` as the
263 // first `on(...)` clause. Instead we should implement the extended design:
264 // https://github.com/elastio/bon/issues/152
265if let Some(on) = on.first().filter(|on| on.required.is_present()) {
266if self.is_special_option_ty() {
267self.config.required = on.required;
268 }
269 }
270271self.merge_config_into(on)?;
272273// FIXME: refactor this to make it more consistent with `into`
274 // and allow for non-boolean flags in `OnConfig`. E.g. add support
275 // for `with = closure` to `on` as well.
276self.config.overwritable = config::EvalBlanketFlagParam {
277 on,
278 param_name: config::BlanketParamName::Overwritable,
279 member_config: &self.config,
280 scrutinee: self.underlying_norm_ty(),
281 origin: self.origin,
282 }
283 .eval()?;
284285Ok(())
286 }
287}