Skip to main content

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