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}