bon_macros/normalization/cfg/
mod.rs

1mod 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        // Update the config to remove the `@cfgs(...)` prefix from them
43        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        // Collect predicates again after the cfgs were evaluated. This is needed
56        // because cfgs may create new cfgs e.g.: `#[cfg_attr(foo, cfg_attr(bar, ...))]`.
57        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    /// There is no mutation happening here, but we just reuse the same
70    /// visitor implementation that works with mutable references.
71    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, &current_macro)?;
124
125        let predicates = predicates.iter().enumerate().map(|(i, predicate)| {
126            // We need to insert the recursion counter into the name so that
127            // the name is unique on every recursive iteration of the cfg eval.
128            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    /// The macro `__eval_cfg_callback` needs to generate a use statement for
147    /// every `cfg` predicate. To do that it needs to assign a unique name for
148    /// every `use` statement so they doesn't collide with other items in
149    /// the same scope and with each other.
150    ///
151    /// But.. How in the world can we generate a unique name for every `use`
152    /// if proc macros are supposed to be stateless and deterministic? 😳
153    ///
154    /// We could use a random number here, but that would make the output
155    /// non-deterministic, which is not good for reproducible builds and
156    /// generally may lead to some unexpected headaches 🤧.
157    ///
158    /// That's a silly problem, and here is a silly solution that doesn't
159    /// work in 100% of the cases but it's probably good enough 😸.
160    ///
161    /// We just need to use some existing name as a source of uniqueness.
162    /// The name of the item under the macro is a good candidate for that.
163    /// If the item is a function, then we can use the function name as that
164    /// reliable source of uniqueness.
165    ///
166    /// If the item is an `impl` block, then we have a bit of a problem because
167    /// the `impl` block doesn't have a unique identifier attached to it, especially
168    /// if the `self_ty` of the `impl` block isn't some simple syntax like a path.
169    ///
170    /// However, in most of the cases it will be a simple path, so its combination
171    /// with the name of the first function in the `impl` block should be unique enough.
172    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        // Include the name of the proc macro in the unique name to avoid
177        // collisions when different proc macros are placed on the same item
178        // and they use this code to generate unique names.
179        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                    // The cfg predicate is false. No need to keep iterating
221                    // because the entire syntax this attribute is attached to
222                    // should be removed. Signal the caller to remove it via `false`.
223                    return Ok(false);
224                }
225
226                // Just remove the attribute. It evaluated to `true`
227                None
228            }
229            CfgSyntax::CfgAttr(cfg_attr) => {
230                let predicate = cfg_attr.predicate.to_token_stream().to_string();
231
232                // We can't both iterate over the attributes and mutate them,
233                // so collect the planned actions in a separate vector, and
234                // do the mutations after the iteration.
235
236                true_predicates
237                    .contains(&predicate)
238                    .then(|| cfg_attr.then_branch)
239            }
240        };
241
242        cfg_attr_expansions.push((i, expansion));
243    }
244
245    // It's important to iterate in reverse order to avoid index invalidation
246    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}