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