1use super::builder_gen::input_fn::{FnInputCtx, FnInputCtxParams, ImplCtx};
2use super::builder_gen::TopLevelConfig;
3use crate::normalization::{GenericsNamespace, SyntaxVariant};
4use crate::util::prelude::*;
5use darling::ast::NestedMeta;
6use darling::FromMeta;
7use std::rc::Rc;
8use syn::visit::Visit;
9use syn::visit_mut::VisitMut;
1011#[derive(FromMeta)]
12pub(crate) struct ImplInputParams {
13/// Overrides the path to the `bon` crate. This is usedfule when the macro is
14 /// wrapped in another macro that also reexports `bon`.
15#[darling(rename = "crate", default, map = Some, with = crate::parsing::parse_bon_crate_path)]
16bon: Option<syn::Path>,
17}
1819// ImplInputParams will evolve in the future where we'll probably want to move from it
20#[allow(clippy::needless_pass_by_value)]
21pub(crate) fn generate(
22 impl_params: ImplInputParams,
23mut orig_impl_block: syn::ItemImpl,
24) -> Result<TokenStream> {
25let mut namespace = GenericsNamespace::default();
26 namespace.visit_item_impl(&orig_impl_block);
2728if let Some((_, trait_path, _)) = &orig_impl_block.trait_ {
29bail!(trait_path, "Impls of traits are not supported yet");
30 }
3132let (builder_fns, other_items): (Vec<_>, Vec<_>) =
33 orig_impl_block.items.into_iter().partition(|item| {
34let fn_item = match item {
35 syn::ImplItem::Fn(fn_item) => fn_item,
36_ => return false,
37 };
3839 fn_item
40 .attrs
41 .iter()
42 .any(|attr| attr.path().is_ident("builder"))
43 });
4445if builder_fns.is_empty() {
46bail!(
47&Span::call_site(),
48"there are no #[builder] functions in the impl block, so there is no \
49 need for a #[bon] attribute here"
50);
51 }
5253 orig_impl_block.items = builder_fns;
5455// We do this back-and-forth with normalizing various syntax and saving original
56 // to provide cleaner code generation that is easier to consume for IDEs and for
57 // rust-analyzer specifically.
58 //
59 // For codegen logic we would like to have everything normalized. For example, we
60 // want to assume `Self` is replaced with the original type and all lifetimes are
61 // named, and `impl Traits` are desugared into type parameters.
62 //
63 // However, in output code we want to preserve existing `Self` references to make
64 // sure rust-analyzer highlights them properly. If we just strip `Self` from output
65 // code, then rust-analyzer won't be able to associate what `Self` token maps to in
66 // the input. It would highlight `Self` as an "unresolved symbol"
67let mut norm_impl_block = orig_impl_block.clone();
6869crate::normalization::NormalizeLifetimes::new(&namespace)
70 .visit_item_impl_mut(&mut norm_impl_block);
7172crate::normalization::NormalizeImplTraits::new(&namespace)
73 .visit_item_impl_mut(&mut norm_impl_block);
7475// Retain a variant of the impl block without the normalized `Self` mentions.
76 // This way we preserve the original code that the user wrote with `Self` mentions
77 // as much as possible, therefore IDE's are able to provide correct syntax highlighting
78 // for `Self` mentions, because they aren't removed from the generated code output
79let mut norm_selfful_impl_block = norm_impl_block.clone();
8081crate::normalization::NormalizeSelfTy {
82 self_ty: &norm_impl_block.self_ty.clone(),
83 }
84 .visit_item_impl_mut(&mut norm_impl_block);
8586let impl_ctx = Rc::new(ImplCtx {
87 self_ty: norm_impl_block.self_ty,
88 generics: norm_impl_block.generics,
89 allow_attrs: norm_impl_block
90 .attrs
91 .iter()
92 .filter_map(syn::Attribute::to_allow)
93 .collect(),
94 });
9596let outputs = orig_impl_block
97 .items
98 .into_iter()
99 .zip(norm_impl_block.items)
100 .map(|(orig_item, norm_item)| {
101let norm_fn = match norm_item {
102 syn::ImplItem::Fn(norm_fn) => norm_fn,
103_ => unreachable!(),
104 };
105let orig_fn = match orig_item {
106 syn::ImplItem::Fn(orig_fn) => orig_fn,
107_ => unreachable!(),
108 };
109110let norm_fn = conv_impl_item_fn_into_fn_item(norm_fn)?;
111let orig_fn = conv_impl_item_fn_into_fn_item(orig_fn)?;
112113let meta = orig_fn
114 .attrs
115 .iter()
116 .filter(|attr| attr.path().is_ident("builder"))
117 .map(|attr| {
118if let syn::Meta::List(_) = attr.meta {
119crate::parsing::require_non_empty_paren_meta_list_or_name_value(
120&attr.meta,
121 )?;
122 }
123let meta_list = darling::util::parse_attribute_to_meta_list(attr)?;
124 NestedMeta::parse_meta_list(meta_list.tokens).map_err(Into::into)
125 })
126 .collect::<Result<Vec<_>>>()?
127.into_iter()
128 .flatten()
129 .collect::<Vec<_>>();
130131let mut config = TopLevelConfig::parse_for_fn(&meta)?;
132133if let Some(bon) = config.bon {
134bail!(
135&bon,
136"`crate` parameter should be specified via `#[bon(crate = path::to::bon)]` \
137 when impl block syntax is used; no need to specify it in the method's \
138 `#[builder]` attribute"
139);
140 }
141142 config.bon.clone_from(&impl_params.bon);
143144let fn_item = SyntaxVariant {
145 orig: orig_fn,
146 norm: norm_fn,
147 };
148149let ctx = FnInputCtx::new(FnInputCtxParams {
150 namespace: &namespace,
151 fn_item,
152 impl_ctx: Some(impl_ctx.clone()),
153 config,
154 });
155156Result::<_>::Ok((ctx.adapted_fn()?, ctx.into_builder_gen_ctx()?.output()?))
157 })
158 .collect::<Result<Vec<_>>>()?;
159160let new_impl_items = outputs.iter().flat_map(|(adapted_fn, output)| {
161let start_fn = &output.start_fn;
162 [syn::parse_quote!(#adapted_fn), syn::parse_quote!(#start_fn)]
163 });
164165 norm_selfful_impl_block.items = other_items;
166 norm_selfful_impl_block.items.extend(new_impl_items);
167168let other_items = outputs.iter().map(|(_, output)| &output.other_items);
169170Ok(quote! {
171// Keep the original impl block at the top. It seems like rust-analyzer
172 // does better job of highlighting syntax when it is here. Assuming
173 // this is because rust-analyzer prefers the first occurrence of the
174 // span when highlighting.
175 //
176 // See this issue for details: https://github.com/rust-lang/rust-analyzer/issues/18438
177#norm_selfful_impl_block
178179 #(#other_items)*
180 })
181}
182183fn conv_impl_item_fn_into_fn_item(func: syn::ImplItemFn) -> Result<syn::ItemFn> {
184let syn::ImplItemFn {
185 attrs,
186 vis,
187 defaultness,
188 sig,
189 block,
190 } = func;
191192if let Some(defaultness) = &defaultness {
193bail!(defaultness, "Default functions are not supported yet");
194 }
195196Ok(syn::ItemFn {
197 attrs,
198 vis,
199 sig,
200 block: Box::new(block),
201 })
202}