cfg_tt/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod cfg;
4use cfg::*;
5mod find;
6use find::*;
7
8use std::{collections::HashSet, iter};
9
10use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
11use quote::ToTokens;
12use syn::{
13    Item, Stmt,
14    parse::{Parse, ParseStream},
15};
16
17struct Many<T>(Vec<T>);
18
19impl<T: Parse> Parse for Many<T> {
20    fn parse(input: ParseStream) -> syn::Result<Self> {
21        let mut items = Vec::new();
22        while !input.is_empty() {
23            items.push(input.parse::<T>()?);
24        }
25        Ok(Many(items))
26    }
27}
28
29fn expand_for_cfg(ts: TokenStream, active_cfg: &Cfg) -> TokenStream {
30    let mut it = ts.into_iter().peekable();
31    let mut out = TokenStream::new();
32    while let Some(tt) = it.next() {
33        match &tt {
34            TokenTree::Group(g) => {
35                let expanded = expand_for_cfg(g.stream(), active_cfg);
36                let expanded = TokenTree::Group(Group::new(g.delimiter(), expanded));
37                out.extend([expanded]);
38            }
39            TokenTree::Punct(p) if p.as_char() == '#' => {
40                // # ...
41                let Some(TokenTree::Group(g)) = it.peek() else {
42                    out.extend([tt]);
43                    continue;
44                };
45                if g.delimiter() != Delimiter::Bracket {
46                    out.extend([tt]);
47                    continue;
48                }
49
50                // #[ ... ]
51                let mut attr_ts = TokenStream::new();
52                attr_ts.extend(iter::once(tt.clone()));
53                attr_ts.extend(iter::once(TokenTree::Group(g.clone())));
54
55                let Ok(attr) = parse_any_attr(attr_ts) else {
56                    out.extend([tt]);
57                    continue;
58                };
59                let Some(cfg) = Cfg::from_attr(&attr) else {
60                    out.extend([tt]);
61                    continue;
62                };
63                if !attr.path().is_ident("cfg") {
64                    out.extend([tt]);
65                    continue;
66                }
67
68                // consume #[cfg(...)]
69                let _ = it.next();
70
71                // target of cfg
72                let Some(target) = it.next() else { continue };
73                if active_cfg.implies(&cfg) {
74                    // active
75                    let target = if let TokenTree::Group(g) = target {
76                        g.stream()
77                    } else {
78                        target.into_token_stream()
79                    };
80                    let expanded = expand_for_cfg(target, active_cfg);
81                    out.extend([expanded]);
82                } else {
83                    // dont emit anything
84                }
85            }
86            _ => {
87                out.extend([tt]);
88            }
89        }
90    }
91
92    out
93}
94
95fn generate_all_combinations(cfgs: Vec<Cfg>) -> Vec<Cfg> {
96    fn core<T: Clone>(
97        items: &[T],
98        i: usize,
99        acc: &mut Vec<(T, bool)>,
100        out: &mut impl FnMut(&Vec<(T, bool)>),
101    ) {
102        if i == items.len() {
103            out(acc);
104            return;
105        }
106
107        // excluded
108        acc.push((items[i].clone(), false));
109        core(items, i + 1, acc, out);
110        acc.pop();
111
112        // included
113        acc.push((items[i].clone(), true));
114        core(items, i + 1, acc, out);
115        acc.pop();
116    }
117
118    if cfgs.is_empty() {
119        return Vec::new();
120    }
121
122    let mut acc = Vec::with_capacity(cfgs.len());
123    let mut out = Vec::with_capacity(cfgs.len() * cfgs.len());
124    core(&cfgs, 0, &mut acc, &mut |cfgs| {
125        let mut list = cfgs
126            .iter()
127            .cloned()
128            .map(
129                |(cfg, active)| {
130                    if active { cfg } else { Cfg::Not(Box::new(cfg)) }
131                },
132            )
133            .collect::<Vec<_>>();
134        out.push(if list.len() == 1 {
135            list.pop().unwrap()
136        } else {
137            Cfg::All(list)
138        });
139    });
140    out
141}
142
143fn find_base_cfgs(input: impl IntoIterator<Item = Cfg>) -> Vec<Cfg> {
144    let mut cfgs = HashSet::new();
145
146    // Remove duplicates and negations
147    for cfg in input.into_iter() {
148        match cfg {
149            Cfg::Not(inner) => cfgs.insert(*inner),
150            Cfg::All(list) | Cfg::Any(list) if list.is_empty() => false,
151            Cfg::All(list) | Cfg::Any(list) if list.len() == 1 => cfgs.insert(list[0].clone()),
152            _ => cfgs.insert(cfg),
153        };
154    }
155
156    // Remove all() if all inner cfgs exist
157    let cfgs: Vec<Cfg> = cfgs
158        .iter()
159        .filter(|cfg| match cfg {
160            Cfg::All(xs) => !xs.iter().all(|child| match child {
161                Cfg::Not(inner) => cfgs.contains(inner),
162                _ => cfgs.contains(child),
163            }),
164            _ => true,
165        })
166        .cloned()
167        .collect();
168
169    cfgs
170}
171
172/// Apply `#[cfg(...)]` at **token-tree granularity**, anywhere.
173///
174/// This macro processes raw token trees *before parsing* and allows
175/// `#[cfg]` to appear in places Rust normally forbids.
176///
177/// Each `#[cfg(...)]` attribute applies to **exactly the next `TokenTree`**:
178/// - an identifier (e.g. `foo`)
179/// - a literal (e.g. `42`)
180/// - a punctuation token (e.g. `+`)
181/// - a group (`{}`, `()`, `[]`)
182///
183/// To conditionally include more than one token tree, wrap them in a group.
184///
185/// After cfg filtering, the remaining tokens are emitted unchanged and must
186/// form valid Rust code.
187#[proc_macro]
188pub fn cfg_tt(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
189    let content: TokenStream = input.into();
190
191    // Collect all occurances or #[cfg()] in the input
192    let cfgs = find_cfg_attrs(content.clone());
193    if cfgs.is_empty() {
194        // Nothing to do
195        return content.into();
196    }
197
198    let cfgs = find_base_cfgs(cfgs);
199
200    // Now construct every possible combination of applicable configurations
201    let configurations = generate_all_combinations(cfgs);
202
203    let mut out = TokenStream::new();
204    for cfg in &configurations {
205        let expanded = expand_for_cfg(content.clone(), cfg);
206        let items = match syn::parse2::<Many<Item>>(expanded.clone()) {
207            Ok(items) => items.0.iter().map(|item| item.to_token_stream()).collect(),
208            Err(_) => match syn::parse2::<Many<Stmt>>(expanded.clone()) {
209                Ok(stmts) => stmts.0.iter().map(|item| item.to_token_stream()).collect(),
210                Err(_) => vec![expanded],
211            },
212        };
213
214        for item in items {
215            out.extend([cfg.to_token_stream(), item]);
216        }
217    }
218
219    out.into()
220}