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