1mod parse;
2mod visit;
34use crate::util::prelude::*;
5use darling::ast::NestedMeta;
6use parse::CfgSyntax;
7use std::collections::BTreeSet;
8use syn::parse::Parser;
910pub(crate) enum Expansion {
11 Expanded(Expanded),
12 Recurse(TokenStream),
13}
1415pub(crate) struct Expanded {
16pub(crate) config: TokenStream,
17pub(crate) item: syn::Item,
18}
1920pub(crate) struct ExpandCfg {
21pub(crate) current_macro: syn::Ident,
22pub(crate) config: TokenStream,
23pub(crate) item: syn::Item,
24}
2526impl ExpandCfg {
27pub(crate) fn expand_cfg(mut self) -> Result<Expansion> {
28let predicates = self.collect_predicates()?;
2930if predicates.is_empty() {
31return Ok(Expansion::Expanded(Expanded {
32 config: self.config,
33 item: self.item,
34 }));
35 }
3637let predicate_results = match parse::parse_predicate_results(self.config.clone())? {
38Some(predicate_results) => predicate_results,
39None => return self.into_recursion(0, &predicates),
40 };
4142// Update the config to remove the `@cfgs(...)` prefix from them
43self.config = predicate_results.rest;
4445let 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();
5253 visit::visit_attrs(&mut self.item, |attrs| eval_cfgs(&true_predicates, attrs))?;
5455// 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, ...))]`.
57let predicates = self.collect_predicates()?;
5859if predicates.is_empty() {
60return Ok(Expansion::Expanded(Expanded {
61 config: self.config,
62 item: self.item,
63 }));
64 }
6566self.into_recursion(predicate_results.recursion_counter + 1, &predicates)
67 }
6869/// There is no mutation happening here, but we just reuse the same
70 /// visitor implementation that works with mutable references.
71fn collect_predicates(&mut self) -> Result<Vec<TokenStream>> {
72let mut predicates = vec![];
73let mut visited = BTreeSet::new();
7475 visit::visit_attrs(&mut self.item, |attrs| {
76for attr in attrs {
77let cfg_syntax = match CfgSyntax::from_meta(&attr.meta)? {
78Some(cfg_syntax) => cfg_syntax,
79None => continue,
80 };
8182let predicate = match cfg_syntax {
83 CfgSyntax::Cfg(predicate) => predicate,
84 CfgSyntax::CfgAttr(cfg_attr) => cfg_attr.predicate.to_token_stream(),
85 };
8687if visited.insert(predicate.to_string()) {
88 predicates.push(predicate);
89 }
90 }
9192Ok(true)
93 })?;
9495Ok(predicates)
96 }
9798fn into_recursion(
99self,
100 recursion_counter: usize,
101 predicates: &[TokenStream],
102 ) -> Result<Expansion> {
103let Self {
104 config,
105 item,
106 current_macro,
107 } = self;
108109let 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") => {
113let path = &meta.value;
114Some(syn::Path::parse_mod_style.parse2(quote!(#path)))
115 }
116_ => None,
117 })
118 .transpose()?
119.unwrap_or_else(|| syn::parse_quote!(::bon));
120121let current_macro = syn::parse_quote!(#bon::#current_macro);
122123let invocation_name = Self::unique_invocation_name(&item, ¤t_macro)?;
124125let 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.
128let pred_id = format_ident!("{invocation_name}_{recursion_counter}_{i}");
129quote!(#pred_id: #predicate)
130 });
131132let expansion = quote! {
133 #bon::__eval_cfg_callback! {
134 {}
135 #((#predicates))*
136 #current_macro,
137 #recursion_counter,
138 ( #config )
139 #item
140 }
141 };
142143Ok(Expansion::Recurse(expansion))
144 }
145146/// 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.
172fn unique_invocation_name(item: &syn::Item, current_macro: &syn::Path) -> Result<String> {
173let path_to_ident =
174 |path: &syn::Path| path.segments.iter().map(|segment| &segment.ident).join("_");
175176// 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.
179let macro_path_str = path_to_ident(current_macro);
180181let item_name = match item {
182 syn::Item::Fn(item) => item.sig.ident.to_string(),
183 syn::Item::Impl(item) => {
184let self_ty = item
185 .self_ty
186 .as_path()
187 .map(|path| path_to_ident(&path.path))
188 .unwrap_or_default();
189190let 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();
198199format!("impl_{self_ty}_fn_{first_fn}")
200 }
201_ => bail!(&Span::call_site(), "Unsupported item type"),
202 };
203204Ok(format!("__eval_cfg_{macro_path_str}_{item_name}"))
205 }
206}
207208fn eval_cfgs(true_predicates: &BTreeSet<String>, attrs: &mut Vec<syn::Attribute>) -> Result<bool> {
209let mut cfg_attr_expansions = vec![];
210211for (i, attr) in attrs.iter().enumerate() {
212let syntax = match CfgSyntax::from_meta(&attr.meta)? {
213Some(syntax) => syntax,
214_ => continue,
215 };
216217let expansion = match syntax {
218 CfgSyntax::Cfg(predicate) => {
219if !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`.
223return Ok(false);
224 }
225226// Just remove the attribute. It evaluated to `true`
227None
228}
229 CfgSyntax::CfgAttr(cfg_attr) => {
230let predicate = cfg_attr.predicate.to_token_stream().to_string();
231232// 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.
235236true_predicates
237 .contains(&predicate)
238 .then(|| cfg_attr.then_branch)
239 }
240 };
241242 cfg_attr_expansions.push((i, expansion));
243 }
244245// It's important to iterate in reverse order to avoid index invalidation
246for (i, metas) in cfg_attr_expansions.iter().rev() {
247let metas = if let Some(metas) = metas {
248 metas
249 } else {
250 attrs.remove(*i);
251continue;
252 };
253254let replacement = metas.iter().map(|meta| syn::parse_quote!(#[#meta]));
255256 attrs.splice(i..=i, replacement);
257 }
258259Ok(true)
260}