bon_macros/builder/builder_gen/member/
mod.rs

1mod config;
2mod into_conversion;
3mod named;
4
5pub(crate) use config::*;
6pub(crate) use named::*;
7
8use super::top_level_config::OnConfig;
9use crate::normalization::SyntaxVariant;
10use crate::util::prelude::*;
11use config::MemberConfig;
12use darling::FromAttributes;
13use std::fmt;
14
15#[derive(Debug, Clone, Copy)]
16pub(crate) enum MemberOrigin {
17    FnArg,
18    StructField,
19}
20
21impl fmt::Display for MemberOrigin {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            Self::FnArg => write!(f, "function argument"),
25            Self::StructField => write!(f, "struct field"),
26        }
27    }
28}
29
30impl MemberOrigin {
31    fn parent_construct(self) -> &'static str {
32        match self {
33            Self::FnArg => "function",
34            Self::StructField => "struct",
35        }
36    }
37}
38
39#[derive(Debug)]
40pub(crate) enum Member {
41    /// Member that was marked with `#[builder(start_fn)]`
42    StartFn(PosFnMember),
43
44    /// Member that was marked with `#[builder(field)]`
45    Field(CustomField),
46
47    /// Member that was marked with `#[builder(finish_fn)]`
48    FinishFn(PosFnMember),
49
50    /// Regular named member included in the typestate.
51    Named(NamedMember),
52
53    /// Member that was marked with `#[builder(skip)]`
54    Skip(SkipMember),
55}
56
57#[derive(Debug)]
58pub(crate) struct CustomField {
59    pub(crate) ident: syn::Ident,
60    pub(crate) norm_ty: Box<syn::Type>,
61
62    /// Initial value of the field
63    pub(crate) init: Option<syn::Expr>,
64}
65
66#[derive(Debug)]
67pub(crate) struct PosFnMember {
68    /// Specifies what syntax the member comes from.
69    pub(crate) origin: MemberOrigin,
70
71    /// Original identifier of the member
72    pub(crate) ident: syn::Ident,
73
74    /// Type of the member
75    pub(crate) ty: SyntaxVariant<Box<syn::Type>>,
76
77    /// Parameters configured by the user explicitly via attributes
78    pub(crate) config: MemberConfig,
79}
80
81/// Member that was skipped by the user with `#[builder(skip)]`
82#[derive(Debug)]
83pub(crate) struct SkipMember {
84    pub(crate) ident: syn::Ident,
85
86    /// Normalized type of the member
87    pub(crate) norm_ty: Box<syn::Type>,
88
89    /// Value to assign to the member
90    pub(crate) value: Option<syn::Expr>,
91}
92
93pub(crate) struct RawMember<'a> {
94    pub(crate) attrs: &'a [syn::Attribute],
95    pub(crate) ident: syn::Ident,
96    pub(crate) ty: SyntaxVariant<Box<syn::Type>>,
97}
98
99impl Member {
100    // False-positive lint. We can't elide the lifetime in `RawMember` because
101    // anonymous lifetimes in impl traits are unstable, and we shouldn't omit
102    // the lifetime parameter because we want to be explicit about its existence
103    // (there is an other lint that checks for this).
104    #[allow(single_use_lifetimes)]
105    pub(crate) fn from_raw<'a>(
106        on: &[OnConfig],
107        origin: MemberOrigin,
108        members: impl IntoIterator<Item = RawMember<'a>>,
109    ) -> Result<Vec<Self>> {
110        let mut members = members
111            .into_iter()
112            .map(|member| {
113                for attr in member.attrs {
114                    if attr.meta.path().is_ident("builder") {
115                        crate::parsing::require_non_empty_paren_meta_list_or_name_value(
116                            &attr.meta,
117                        )?;
118                    }
119                }
120
121                let config = MemberConfig::from_attributes(member.attrs)?;
122                config.validate(origin)?;
123                Ok((member, config))
124            })
125            .collect::<Result<Vec<_>>>()?
126            .into_iter()
127            .peekable();
128
129        let mut output = vec![];
130
131        // Collect `start_fn` members
132        while let Some((member, config)) = members.next_if(|(_, cfg)| cfg.start_fn.is_present()) {
133            let member = PosFnMember::new(origin, member, on, config)?;
134            output.push(Self::StartFn(member));
135        }
136
137        // Collect `field` members
138        while let Some((member, config)) = members.next_if(|(_, cfg)| cfg.field.is_some()) {
139            let init = config
140                .field
141                .expect("validated `field.is_some()` in `next_if`")
142                .value;
143
144            let member = CustomField::new(member, init)?;
145            output.push(Self::Field(member));
146        }
147
148        // Collect `finish_fn` members
149        while let Some((member, cfg)) = members.next_if(|(_, cfg)| cfg.finish_fn.is_present()) {
150            let member = PosFnMember::new(origin, member, on, cfg)?;
151            output.push(Self::FinishFn(member));
152        }
153
154        let mut named_count = 0;
155
156        for (member, config) in members {
157            let RawMember { attrs, ident, ty } = member;
158
159            if let Some(value) = config.skip {
160                output.push(Self::Skip(SkipMember {
161                    ident,
162                    norm_ty: ty.norm,
163                    value: value.value,
164                }));
165                continue;
166            }
167
168            let active_flag = |flag: darling::util::Flag| flag.is_present().then(|| flag.span());
169
170            let incorrect_order = None
171                .or_else(|| active_flag(config.start_fn))
172                .or_else(|| Some(config.field.as_ref()?.key.span()))
173                .or_else(|| active_flag(config.finish_fn));
174
175            if let Some(span) = incorrect_order {
176                bail!(
177                    &span,
178                    "incorrect members ordering; expected ordering:\n\
179                    (1) members annotated with #[builder(start_fn)]\n\
180                    (2) members annotated with #[builder(field)]\n\
181                    (3) members annotated with #[builder(finish_fn)]\n\
182                    (4) all other members in any order",
183                );
184            }
185
186            // XXX: docs are collected only for named members. There is no obvious
187            // place where to put the docs for positional and skipped members.
188            //
189            // Even if there are some docs on them and the function syntax is used
190            // then these docs will just be removed from the output function.
191            // It's probably fine since the doc comments are there in the code
192            // itself which is also useful for people reading the source code.
193            let docs = attrs
194                .iter()
195                .filter(|attr| attr.is_doc_expr())
196                .cloned()
197                .collect();
198
199            let mut member = NamedMember {
200                index: named_count.into(),
201                origin,
202                name: MemberName::new(ident, &config),
203                ty,
204                config,
205                docs,
206            };
207
208            member.merge_on_config(on)?;
209            member.validate()?;
210
211            output.push(Self::Named(member));
212            named_count += 1;
213        }
214
215        Ok(output)
216    }
217}
218
219impl Member {
220    pub(crate) fn norm_ty(&self) -> &syn::Type {
221        match self {
222            Self::Field(me) => &me.norm_ty,
223            Self::FinishFn(me) | Self::StartFn(me) => &me.ty.norm,
224            Self::Named(me) => &me.ty.norm,
225            Self::Skip(me) => &me.norm_ty,
226        }
227    }
228
229    pub(crate) fn orig_ident(&self) -> &syn::Ident {
230        match self {
231            Self::Field(me) => &me.ident,
232            Self::FinishFn(me) | Self::StartFn(me) => &me.ident,
233            Self::Named(me) => &me.name.orig,
234            Self::Skip(me) => &me.ident,
235        }
236    }
237
238    pub(crate) fn as_named(&self) -> Option<&NamedMember> {
239        match self {
240            Self::Named(me) => Some(me),
241            _ => None,
242        }
243    }
244
245    pub(crate) fn as_field(&self) -> Option<&CustomField> {
246        match self {
247            Self::Field(me) => Some(me),
248            _ => None,
249        }
250    }
251
252    pub(crate) fn as_start_fn(&self) -> Option<&PosFnMember> {
253        match self {
254            Self::StartFn(me) => Some(me),
255            _ => None,
256        }
257    }
258
259    pub(crate) fn as_finish_fn(&self) -> Option<&PosFnMember> {
260        match self {
261            Self::FinishFn(me) => Some(me),
262            _ => None,
263        }
264    }
265}
266
267impl PosFnMember {
268    fn new(
269        origin: MemberOrigin,
270        member: RawMember<'_>,
271        on: &[OnConfig],
272        config: MemberConfig,
273    ) -> Result<Self> {
274        let RawMember {
275            attrs: _,
276            ident,
277            ty,
278        } = member;
279
280        let mut me = Self {
281            origin,
282            ident,
283            ty,
284            config,
285        };
286
287        me.merge_config_into(on)?;
288
289        Ok(me)
290    }
291}
292
293impl CustomField {
294    fn new(member: RawMember<'_>, init: Option<syn::Expr>) -> Result<Self> {
295        if member.ident.to_string().starts_with("__") {
296            bail!(
297                &member.ident,
298                "field names starting with `__` are reserved for `bon`'s internal use; \
299                please, select a different name",
300            );
301        }
302
303        Ok(Self {
304            ident: member.ident,
305            norm_ty: member.ty.norm,
306            init,
307        })
308    }
309}