bon_macros/builder/builder_gen/
input_struct.rs

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