bon_macros/normalization/cfg/
mod.rs1mod parse;
2mod visit;
3
4use crate::util::prelude::*;
5use darling::ast::NestedMeta;
6use parse::CfgSyntax;
7use std::collections::BTreeSet;
8use syn::parse::Parser;
9
10pub(crate) enum Expansion {
11 Expanded(Expanded),
12 Recurse(TokenStream),
13}
14
15pub(crate) struct Expanded {
16 pub(crate) config: TokenStream,
17 pub(crate) item: syn::Item,
18}
19
20pub(crate) struct ExpandCfg {
21 pub(crate) current_macro: syn::Ident,
22 pub(crate) config: TokenStream,
23 pub(crate) item: syn::Item,
24}
25
26impl ExpandCfg {
27 pub(crate) fn expand_cfg(mut self) -> Result<Expansion> {
28 let predicates = self.collect_predicates()?;
29
30 if predicates.is_empty() {
31 return Ok(Expansion::Expanded(Expanded {
32 config: self.config,
33 item: self.item,
34 }));
35 }
36
37 let predicate_results = match parse::parse_predicate_results(self.config.clone())? {
38 Some(predicate_results) => predicate_results,
39 None => return self.into_recursion(0, &predicates),
40 };
41
42 self.config = predicate_results.rest;
44
45 let true_predicates: BTreeSet<_> = predicates
46 .iter()
47 .map(ToString::to_string)
48 .zip(predicate_results.results)
49 .filter(|(_, result)| *result)
50 .map(|(predicate, _)| predicate)
51 .collect();
52
53 visit::visit_attrs(&mut self.item, |attrs| eval_cfgs(&true_predicates, attrs))?;
54
55 let predicates = self.collect_predicates()?;
58
59 if predicates.is_empty() {
60 return Ok(Expansion::Expanded(Expanded {
61 config: self.config,
62 item: self.item,
63 }));
64 }
65
66 self.into_recursion(predicate_results.recursion_counter + 1, &predicates)
67 }
68
69 fn collect_predicates(&mut self) -> Result<Vec<TokenStream>> {
72 let mut predicates = vec![];
73 let mut visited = BTreeSet::new();
74
75 visit::visit_attrs(&mut self.item, |attrs| {
76 for attr in attrs {
77 let cfg_syntax = match CfgSyntax::from_meta(&attr.meta)? {
78 Some(cfg_syntax) => cfg_syntax,
79 None => continue,
80 };
81
82 let predicate = match cfg_syntax {
83 CfgSyntax::Cfg(predicate) => predicate,
84 CfgSyntax::CfgAttr(cfg_attr) => cfg_attr.predicate.to_token_stream(),
85 };
86
87 if visited.insert(predicate.to_string()) {
88 predicates.push(predicate);
89 }
90 }
91
92 Ok(true)
93 })?;
94
95 Ok(predicates)
96 }
97
98 fn into_recursion(
99 self,
100 recursion_counter: usize,
101 predicates: &[TokenStream],
102 ) -> Result<Expansion> {
103 let Self {
104 config,
105 item,
106 current_macro,
107 } = self;
108
109 let bon = NestedMeta::parse_meta_list(config.clone())?
110 .iter()
111 .find_map(|meta| match meta {
112 NestedMeta::Meta(syn::Meta::NameValue(meta)) if meta.path.is_ident("crate") => {
113 let path = &meta.value;
114 Some(syn::Path::parse_mod_style.parse2(quote!(#path)))
115 }
116 _ => None,
117 })
118 .transpose()?
119 .unwrap_or_else(|| syn::parse_quote!(::bon));
120
121 let current_macro = syn::parse_quote!(#bon::#current_macro);
122
123 let invocation_name = Self::unique_invocation_name(&item, ¤t_macro)?;
124
125 let predicates = predicates.iter().enumerate().map(|(i, predicate)| {
126 let pred_id = format_ident!("{invocation_name}_{recursion_counter}_{i}");
129 quote!(#pred_id: #predicate)
130 });
131
132 let expansion = quote! {
133 #bon::__eval_cfg_callback! {
134 {}
135 #((#predicates))*
136 #current_macro,
137 #recursion_counter,
138 ( #config )
139 #item
140 }
141 };
142
143 Ok(Expansion::Recurse(expansion))
144 }
145
146 fn unique_invocation_name(item: &syn::Item, current_macro: &syn::Path) -> Result<String> {
173 let path_to_ident =
174 |path: &syn::Path| path.segments.iter().map(|segment| &segment.ident).join("_");
175
176 let macro_path_str = path_to_ident(current_macro);
180
181 let item_name = match item {
182 syn::Item::Fn(item) => item.sig.ident.to_string(),
183 syn::Item::Impl(item) => {
184 let self_ty = item
185 .self_ty
186 .as_path()
187 .map(|path| path_to_ident(&path.path))
188 .unwrap_or_default();
189
190 let first_fn = item
191 .items
192 .iter()
193 .find_map(|item| match item {
194 syn::ImplItem::Fn(method) => Some(method.sig.ident.to_string()),
195 _ => None,
196 })
197 .unwrap_or_default();
198
199 format!("impl_{self_ty}_fn_{first_fn}")
200 }
201 _ => bail!(&Span::call_site(), "Unsupported item type"),
202 };
203
204 Ok(format!("__eval_cfg_{macro_path_str}_{item_name}"))
205 }
206}
207
208fn eval_cfgs(true_predicates: &BTreeSet<String>, attrs: &mut Vec<syn::Attribute>) -> Result<bool> {
209 let mut cfg_attr_expansions = vec![];
210
211 for (i, attr) in attrs.iter().enumerate() {
212 let syntax = match CfgSyntax::from_meta(&attr.meta)? {
213 Some(syntax) => syntax,
214 _ => continue,
215 };
216
217 let expansion = match syntax {
218 CfgSyntax::Cfg(predicate) => {
219 if !true_predicates.contains(&predicate.to_string()) {
220 return Ok(false);
224 }
225
226 None
228 }
229 CfgSyntax::CfgAttr(cfg_attr) => {
230 let predicate = cfg_attr.predicate.to_token_stream().to_string();
231
232 true_predicates
237 .contains(&predicate)
238 .then(|| cfg_attr.then_branch)
239 }
240 };
241
242 cfg_attr_expansions.push((i, expansion));
243 }
244
245 for (i, metas) in cfg_attr_expansions.iter().rev() {
247 let metas = if let Some(metas) = metas {
248 metas
249 } else {
250 attrs.remove(*i);
251 continue;
252 };
253
254 let replacement = metas.iter().map(|meta| syn::parse_quote!(#[#meta]));
255
256 attrs.splice(i..=i, replacement);
257 }
258
259 Ok(true)
260}