bon_macros/builder/builder_gen/
input_struct.rs

1use super::models::FinishFnParams;
2use super::top_level_config::TopLevelConfig;
3use super::{
4    AssocMethodCtxParams, BuilderGenCtx, FinishFnBody, Generics, Member, MemberOrigin, RawMember,
5};
6use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams};
7use crate::normalization::{GenericsNamespace, SyntaxVariant};
8use crate::parsing::{ItemSigConfig, SpannedKey};
9use crate::util::prelude::*;
10use std::borrow::Cow;
11use syn::spanned::Spanned;
12use syn::visit::Visit;
13use syn::visit_mut::VisitMut;
14
15fn parse_top_level_config(item_struct: &syn::ItemStruct) -> Result<TopLevelConfig> {
16    let configs = item_struct
17        .attrs
18        .iter()
19        .filter(|attr| attr.path().is_ident("builder"))
20        .map(|attr| {
21            let meta = match &attr.meta {
22                syn::Meta::List(meta) => meta,
23                syn::Meta::Path(_) => bail!(
24                    &attr.meta,
25                    "this empty `#[builder]` attribute is redundant; remove it"
26                ),
27                syn::Meta::NameValue(_) => bail!(
28                    &attr.meta,
29                    "`#[builder = ...]` syntax is unsupported; use `#[builder(...)]` instead"
30                ),
31            };
32
33            crate::parsing::require_non_empty_paren_meta_list_or_name_value(&attr.meta)?;
34
35            Ok(meta.tokens.clone())
36        })
37        .collect::<Result<Vec<_>>>()?;
38
39    TopLevelConfig::parse_for_struct(configs)
40}
41
42pub(crate) struct StructInputCtx {
43    struct_item: SyntaxVariant<syn::ItemStruct>,
44    config: TopLevelConfig,
45    struct_ty: syn::Type,
46}
47
48impl StructInputCtx {
49    pub(crate) fn new(orig_struct: syn::ItemStruct) -> Result<Self> {
50        let params = parse_top_level_config(&orig_struct)?;
51
52        let generic_args = orig_struct
53            .generics
54            .params
55            .iter()
56            .map(syn::GenericParam::to_generic_argument);
57        let struct_ident = &orig_struct.ident;
58        let struct_ty = syn::parse_quote!(#struct_ident<#(#generic_args),*>);
59
60        let mut norm_struct = orig_struct.clone();
61
62        // Structs are free to use `Self` inside of their trait bounds and any
63        // internal type contexts. However, when copying these bounds to the
64        // builder struct and its impl blocks we need to get rid of `Self`
65        // references and replace them with the actual struct type.
66        crate::normalization::NormalizeSelfTy {
67            self_ty: &struct_ty,
68        }
69        .visit_item_struct_mut(&mut norm_struct);
70
71        let struct_item = SyntaxVariant {
72            orig: orig_struct,
73            norm: norm_struct,
74        };
75
76        Ok(Self {
77            struct_item,
78            config: params,
79            struct_ty,
80        })
81    }
82
83    pub(crate) fn into_builder_gen_ctx(self) -> Result<BuilderGenCtx> {
84        let fields = self
85            .struct_item
86            .apply_ref(|struct_item| match &struct_item.fields {
87                syn::Fields::Named(fields) => Ok(fields),
88                _ => {
89                    bail!(&struct_item, "Only structs with named fields are supported")
90                }
91            });
92
93        let norm_fields = fields.norm?;
94        let orig_fields = fields.orig?;
95
96        let members = norm_fields
97            .named
98            .iter()
99            .zip(&orig_fields.named)
100            .map(|(norm_field, orig_field)| {
101                let ident = norm_field.ident.clone().ok_or_else(|| {
102                    err!(norm_field, "only structs with named fields are supported")
103                })?;
104
105                let ty = SyntaxVariant {
106                    norm: Box::new(norm_field.ty.clone()),
107                    orig: Box::new(orig_field.ty.clone()),
108                };
109
110                Ok(RawMember {
111                    attrs: &norm_field.attrs,
112                    ident,
113                    ty,
114                    span: orig_field.ident.span(),
115                })
116            })
117            .collect::<Result<Vec<_>>>()?;
118
119        let members = Member::from_raw(&self.config, MemberOrigin::StructField, members)?;
120
121        let generics = Generics::new(
122            self.struct_item
123                .norm
124                .generics
125                .params
126                .iter()
127                .cloned()
128                .collect(),
129            self.struct_item.norm.generics.where_clause.clone(),
130        );
131
132        let finish_fn_body = StructLiteralBody {
133            struct_ident: self.struct_item.norm.ident.clone(),
134        };
135
136        let ItemSigConfig {
137            name: start_fn_ident,
138            vis: start_fn_vis,
139            docs: start_fn_docs,
140        } = self.config.start_fn;
141
142        let start_fn_ident = start_fn_ident
143            .map(SpannedKey::into_value)
144            .unwrap_or_else(|| syn::Ident::new("builder", self.struct_item.norm.ident.span()));
145
146        let ItemSigConfig {
147            name: finish_fn_ident,
148            vis: finish_fn_vis,
149            docs: finish_fn_docs,
150        } = self.config.finish_fn;
151
152        let finish_fn_ident = finish_fn_ident
153            .map(SpannedKey::into_value)
154            .unwrap_or_else(|| syn::Ident::new("build", start_fn_ident.span()));
155
156        let struct_ty = &self.struct_ty;
157        let finish_fn = FinishFnParams {
158            ident: finish_fn_ident,
159            vis: finish_fn_vis.map(SpannedKey::into_value),
160            unsafety: None,
161            asyncness: None,
162            special_attrs: vec![syn::parse_quote! {
163                #[must_use = "building a struct without using it is likely a bug"]
164            }],
165            body: Box::new(finish_fn_body),
166            output: syn::parse_quote!(-> #struct_ty),
167            attrs: finish_fn_docs
168                .map(SpannedKey::into_value)
169                .unwrap_or_else(|| {
170                    vec![syn::parse_quote! {
171                        /// Finish building and return the requested object
172                    }]
173                }),
174        };
175
176        let start_fn_docs = start_fn_docs
177            .map(SpannedKey::into_value)
178            .unwrap_or_else(|| {
179                let docs = format!(
180                    "Create an instance of [`{}`] using the builder syntax",
181                    self.struct_item.norm.ident
182                );
183
184                vec![syn::parse_quote!(#[doc = #docs])]
185            });
186
187        let start_fn = StartFnParams {
188            ident: start_fn_ident,
189            vis: start_fn_vis.map(SpannedKey::into_value),
190            docs: start_fn_docs,
191            generics: None,
192            span: None,
193        };
194
195        let assoc_method_ctx = Some(AssocMethodCtxParams {
196            self_ty: self.struct_ty.into(),
197            receiver: None,
198        });
199
200        let allow_attrs = self
201            .struct_item
202            .norm
203            .attrs
204            .iter()
205            .filter_map(syn::Attribute::to_allow)
206            .collect();
207
208        let builder_type = {
209            let ItemSigConfig { name, vis, docs } = self.config.builder_type;
210
211            let builder_ident = name.map(SpannedKey::into_value).unwrap_or_else(|| {
212                format_ident!("{}Builder", self.struct_item.norm.ident.raw_name())
213            });
214
215            BuilderTypeParams {
216                derives: self.config.derive,
217                ident: builder_ident,
218                docs: docs.map(SpannedKey::into_value),
219                vis: vis.map(SpannedKey::into_value),
220            }
221        };
222
223        let mut namespace = GenericsNamespace::default();
224        namespace.visit_item_struct(&self.struct_item.orig);
225
226        BuilderGenCtx::new(BuilderGenCtxParams {
227            bon: self.config.bon,
228            namespace: Cow::Owned(namespace),
229            members,
230
231            allow_attrs,
232
233            const_: self.config.const_,
234            on: self.config.on,
235
236            assoc_method_ctx,
237            generics,
238            orig_item_vis: self.struct_item.norm.vis,
239
240            builder_type,
241            state_mod: self.config.state_mod,
242            start_fn,
243            finish_fn,
244        })
245    }
246}
247
248struct StructLiteralBody {
249    struct_ident: syn::Ident,
250}
251
252impl FinishFnBody for StructLiteralBody {
253    fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream {
254        let Self { struct_ident } = self;
255
256        // The variables with values of members are in scope for this expression.
257        let member_vars = ctx.members.iter().map(Member::orig_ident);
258
259        quote! {
260            #struct_ident {
261                #(#member_vars,)*
262            }
263        }
264    }
265}