bon_macros/builder/builder_gen/
input_struct.rs1use 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 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 }]
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 let member_vars = ctx.members.iter().map(Member::orig_ident);
258
259 quote! {
260 #struct_ident {
261 #(#member_vars,)*
262 }
263 }
264 }
265}