bon_macros/builder/
item_impl.rs1use super::builder_gen::input_fn::{FnInputCtx, FnInputCtxParams, ImplCtx};
2use super::builder_gen::TopLevelConfig;
3use crate::normalization::{GenericsNamespace, SyntaxVariant};
4use crate::parsing::BonCratePath;
5use crate::util::prelude::*;
6use darling::FromMeta;
7use std::rc::Rc;
8use syn::visit::Visit;
9use syn::visit_mut::VisitMut;
10
11#[derive(FromMeta)]
12pub(crate) struct ImplInputParams {
13 #[darling(rename = "crate", default)]
16 bon: BonCratePath,
17}
18
19#[allow(clippy::needless_pass_by_value)]
21pub(crate) fn generate(
22 impl_params: ImplInputParams,
23 mut orig_impl_block: syn::ItemImpl,
24) -> Result<TokenStream> {
25 let mut namespace = GenericsNamespace::default();
26 namespace.visit_item_impl(&orig_impl_block);
27
28 if let Some((_, trait_path, _)) = &orig_impl_block.trait_ {
29 bail!(trait_path, "Impls of traits are not supported yet");
30 }
31
32 let (builder_fns, other_items): (Vec<_>, Vec<_>) =
33 orig_impl_block.items.into_iter().partition(|item| {
34 let fn_item = match item {
35 syn::ImplItem::Fn(fn_item) => fn_item,
36 _ => return false,
37 };
38
39 fn_item
40 .attrs
41 .iter()
42 .any(|attr| attr.path().is_ident("builder"))
43 });
44
45 if builder_fns.is_empty() {
46 return Err(no_builder_attrs_error(&other_items));
47 }
48
49 orig_impl_block.items = builder_fns;
50
51 let mut norm_impl_block = orig_impl_block.clone();
64
65 crate::normalization::NormalizeLifetimes::new(&namespace)
66 .visit_item_impl_mut(&mut norm_impl_block);
67
68 crate::normalization::NormalizeImplTraits::new(&namespace)
69 .visit_item_impl_mut(&mut norm_impl_block);
70
71 let mut norm_selfful_impl_block = norm_impl_block.clone();
76
77 crate::normalization::NormalizeSelfTy {
78 self_ty: &norm_impl_block.self_ty.clone(),
79 }
80 .visit_item_impl_mut(&mut norm_impl_block);
81
82 let impl_ctx = Rc::new(ImplCtx {
83 self_ty: norm_impl_block.self_ty,
84 generics: norm_impl_block.generics,
85 allow_attrs: norm_impl_block
86 .attrs
87 .iter()
88 .filter_map(syn::Attribute::to_allow)
89 .collect(),
90 });
91
92 let outputs = orig_impl_block
93 .items
94 .into_iter()
95 .zip(norm_impl_block.items)
96 .map(|(orig_item, norm_item)| {
97 let norm_fn = match norm_item {
98 syn::ImplItem::Fn(norm_fn) => norm_fn,
99 _ => unreachable!(),
100 };
101 let orig_fn = match orig_item {
102 syn::ImplItem::Fn(orig_fn) => orig_fn,
103 _ => unreachable!(),
104 };
105
106 let norm_fn = conv_impl_item_fn_into_fn_item(norm_fn)?;
107 let orig_fn = conv_impl_item_fn_into_fn_item(orig_fn)?;
108
109 let mut config = TopLevelConfig::parse_for_fn(&orig_fn, None)?;
110
111 if let BonCratePath::Explicit(path) = config.bon {
112 bail!(
113 &path,
114 "`crate` parameter should be specified via `#[bon(crate = path::to::bon)]` \
115 when impl block syntax is used; no need to specify it in the method's \
116 `#[builder]` attribute"
117 );
118 }
119
120 config.bon.clone_from(&impl_params.bon);
121
122 let fn_item = SyntaxVariant {
123 orig: orig_fn,
124 norm: norm_fn,
125 };
126
127 let ctx = FnInputCtx::new(FnInputCtxParams {
128 namespace: &namespace,
129 fn_item,
130 impl_ctx: Some(impl_ctx.clone()),
131 config,
132 })?;
133
134 let adapted_fn = ctx.adapted_fn()?;
135 let warnings = ctx.warnings();
136
137 let mut output = ctx.into_builder_gen_ctx()?.output()?;
138 output.other_items.extend(warnings);
139
140 Result::<_>::Ok((adapted_fn, output))
141 })
142 .collect::<Result<Vec<_>>>()?;
143
144 let new_impl_items = outputs.iter().flat_map(|(adapted_fn, output)| {
145 let start_fn = &output.start_fn;
146 [syn::parse_quote!(#adapted_fn), syn::parse_quote!(#start_fn)]
147 });
148
149 norm_selfful_impl_block.items = other_items;
150 norm_selfful_impl_block.items.extend(new_impl_items);
151
152 let other_items = outputs.iter().map(|(_, output)| &output.other_items);
153
154 Ok(quote! {
155 #norm_selfful_impl_block
162
163 #(#other_items)*
164 })
165}
166
167fn conv_impl_item_fn_into_fn_item(func: syn::ImplItemFn) -> Result<syn::ItemFn> {
168 let syn::ImplItemFn {
169 attrs,
170 vis,
171 defaultness,
172 sig,
173 block,
174 } = func;
175
176 if let Some(defaultness) = &defaultness {
177 bail!(defaultness, "Default functions are not supported yet");
178 }
179
180 Ok(syn::ItemFn {
181 attrs,
182 vis,
183 sig,
184 block: Box::new(block),
185 })
186}
187
188fn no_builder_attrs_error(other_items: &[syn::ImplItem]) -> Error {
189 let builder_like_err = other_items.iter().find_map(|item| {
190 let item = match item {
191 syn::ImplItem::Fn(fn_item) => fn_item,
192 _ => return None,
193 };
194
195 let builder_like = item
196 .attrs
197 .iter()
198 .find(|attr| attr.path().ends_with_segment("builder"))?;
199
200 let builder_like_str = darling::util::path_to_string(builder_like.path());
201 let builder_like_prefix = builder_like_str
202 .strip_suffix("builder")
203 .unwrap_or(&builder_like_str);
204
205 Some(err!(
206 &builder_like.path(),
207 "#[bon] macro found no #[builder] attributes in the impl block, but \
208 it looks like this attribute was meant for #[bon]; note that #[bon] \
209 expects a bare #[builder] attribute without the `{builder_like_prefix}` \
210 prefix; #[builder] acts as a simple config attribute for the active \
211 #[bon] attribute in impl blocks; more info on inert vs active attributes: \
212 https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes"
213 ))
214 });
215
216 if let Some(err) = builder_like_err {
217 return err;
218 }
219
220 err!(
221 &Span::call_site(),
222 "there are no #[builder] functions in the impl block, so there is no \
223 need for a #[bon] attribute here"
224 )
225}