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