cfg/
cfg_expr.rs

1//! The condition expression used in `#[cfg(..)]` attributes.
2//!
3//! See: <https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation>
4
5use std::fmt;
6
7use intern::Symbol;
8
9/// A simple configuration value passed in from the outside.
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub enum CfgAtom {
12    /// eg. `#[cfg(test)]`
13    Flag(Symbol),
14    /// eg. `#[cfg(target_os = "linux")]`
15    ///
16    /// Note that a key can have multiple values that are all considered "active" at the same time.
17    /// For example, `#[cfg(target_feature = "sse")]` and `#[cfg(target_feature = "sse2")]`.
18    KeyValue { key: Symbol, value: Symbol },
19}
20
21impl PartialOrd for CfgAtom {
22    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
23        Some(self.cmp(other))
24    }
25}
26
27impl Ord for CfgAtom {
28    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
29        match (self, other) {
30            (CfgAtom::Flag(a), CfgAtom::Flag(b)) => a.as_str().cmp(b.as_str()),
31            (CfgAtom::Flag(_), CfgAtom::KeyValue { .. }) => std::cmp::Ordering::Less,
32            (CfgAtom::KeyValue { .. }, CfgAtom::Flag(_)) => std::cmp::Ordering::Greater,
33            (CfgAtom::KeyValue { key, value }, CfgAtom::KeyValue { key: key2, value: value2 }) => {
34                key.as_str().cmp(key2.as_str()).then(value.as_str().cmp(value2.as_str()))
35            }
36        }
37    }
38}
39
40impl fmt::Display for CfgAtom {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            CfgAtom::Flag(name) => name.fmt(f),
44            CfgAtom::KeyValue { key, value } => write!(f, "{key} = {value:?}"),
45        }
46    }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Hash)]
50#[cfg_attr(test, derive(arbitrary::Arbitrary))]
51pub enum CfgExpr {
52    Invalid,
53    Atom(CfgAtom),
54    All(Box<[CfgExpr]>),
55    Any(Box<[CfgExpr]>),
56    Not(Box<CfgExpr>),
57}
58
59impl From<CfgAtom> for CfgExpr {
60    fn from(atom: CfgAtom) -> Self {
61        CfgExpr::Atom(atom)
62    }
63}
64
65impl CfgExpr {
66    // FIXME: Parsing from `tt` is only used in a handful of places, reconsider
67    // if we should switch them to AST.
68    #[cfg(feature = "tt")]
69    pub fn parse<S: Copy>(tt: &tt::TopSubtree<S>) -> CfgExpr {
70        next_cfg_expr(&mut tt.iter()).unwrap_or(CfgExpr::Invalid)
71    }
72
73    #[cfg(feature = "tt")]
74    pub fn parse_from_iter<S: Copy>(tt: &mut tt::iter::TtIter<'_, S>) -> CfgExpr {
75        next_cfg_expr(tt).unwrap_or(CfgExpr::Invalid)
76    }
77
78    #[cfg(feature = "syntax")]
79    pub fn parse_from_ast(
80        ast: &mut std::iter::Peekable<syntax::ast::TokenTreeChildren>,
81    ) -> CfgExpr {
82        next_cfg_expr_from_ast(ast).unwrap_or(CfgExpr::Invalid)
83    }
84
85    /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
86    pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option<bool> {
87        match self {
88            CfgExpr::Invalid => None,
89            CfgExpr::Atom(atom) => Some(query(atom)),
90            CfgExpr::All(preds) => {
91                preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
92            }
93            CfgExpr::Any(preds) => {
94                preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?))
95            }
96            CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
97        }
98    }
99}
100
101#[cfg(feature = "syntax")]
102fn next_cfg_expr_from_ast(
103    it: &mut std::iter::Peekable<syntax::ast::TokenTreeChildren>,
104) -> Option<CfgExpr> {
105    use intern::sym;
106    use syntax::{NodeOrToken, SyntaxKind, T, ast};
107
108    let name = match it.next() {
109        None => return None,
110        Some(NodeOrToken::Token(ident)) if ident.kind().is_any_identifier() => {
111            Symbol::intern(ident.text())
112        }
113        Some(_) => return Some(CfgExpr::Invalid),
114    };
115
116    let ret = match it.peek() {
117        Some(NodeOrToken::Token(eq)) if eq.kind() == T![=] => {
118            it.next();
119            if let Some(NodeOrToken::Token(literal)) = it.peek()
120                && matches!(literal.kind(), SyntaxKind::STRING)
121            {
122                let literal = tt::token_to_literal(literal.text(), ()).symbol;
123                it.next();
124                CfgAtom::KeyValue { key: name, value: literal.clone() }.into()
125            } else {
126                return Some(CfgExpr::Invalid);
127            }
128        }
129        Some(NodeOrToken::Node(subtree)) => {
130            let mut subtree_iter = ast::TokenTreeChildren::new(subtree).peekable();
131            it.next();
132            let mut subs = std::iter::from_fn(|| next_cfg_expr_from_ast(&mut subtree_iter));
133            match name {
134                s if s == sym::all => CfgExpr::All(subs.collect()),
135                s if s == sym::any => CfgExpr::Any(subs.collect()),
136                s if s == sym::not => {
137                    CfgExpr::Not(Box::new(subs.next().unwrap_or(CfgExpr::Invalid)))
138                }
139                _ => CfgExpr::Invalid,
140            }
141        }
142        _ => CfgAtom::Flag(name).into(),
143    };
144
145    // Eat comma separator
146    while it.next().is_some_and(|it| it.as_token().is_none_or(|it| it.kind() != T![,])) {}
147
148    Some(ret)
149}
150
151#[cfg(feature = "tt")]
152fn next_cfg_expr<S: Copy>(it: &mut tt::iter::TtIter<'_, S>) -> Option<CfgExpr> {
153    use intern::sym;
154    use tt::iter::TtElement;
155
156    let name = match it.next() {
157        None => return None,
158        Some(TtElement::Leaf(tt::Leaf::Ident(ident))) => ident.sym.clone(),
159        Some(_) => return Some(CfgExpr::Invalid),
160    };
161
162    let ret = match it.peek() {
163        Some(TtElement::Leaf(tt::Leaf::Punct(punct)))
164            // Don't consume on e.g. `=>`.
165            if punct.char == '='
166                && (punct.spacing == tt::Spacing::Alone
167                    || it.remaining().flat_tokens().get(1).is_none_or(|peek2| {
168                        !matches!(peek2, tt::TokenTree::Leaf(tt::Leaf::Punct(_)))
169                    })) =>
170        {
171            match it.remaining().flat_tokens().get(1) {
172                Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
173                    it.next();
174                    it.next();
175                    CfgAtom::KeyValue { key: name, value: literal.symbol.clone() }.into()
176                }
177                _ => return Some(CfgExpr::Invalid),
178            }
179        }
180        Some(TtElement::Subtree(_, mut sub_it)) => {
181            it.next();
182            let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it));
183            match name {
184                s if s == sym::all => CfgExpr::All(subs.collect()),
185                s if s == sym::any => CfgExpr::Any(subs.collect()),
186                s if s == sym::not => {
187                    CfgExpr::Not(Box::new(subs.next().unwrap_or(CfgExpr::Invalid)))
188                }
189                _ => CfgExpr::Invalid,
190            }
191        }
192        _ => CfgAtom::Flag(name).into(),
193    };
194
195    // Eat comma separator
196    if let Some(TtElement::Leaf(tt::Leaf::Punct(punct))) = it.peek()
197        && punct.char == ','
198    {
199        it.next();
200    }
201    Some(ret)
202}
203
204#[cfg(test)]
205impl arbitrary::Arbitrary<'_> for CfgAtom {
206    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
207        if u.arbitrary()? {
208            Ok(CfgAtom::Flag(Symbol::intern(<_>::arbitrary(u)?)))
209        } else {
210            Ok(CfgAtom::KeyValue {
211                key: Symbol::intern(<_>::arbitrary(u)?),
212                value: Symbol::intern(<_>::arbitrary(u)?),
213            })
214        }
215    }
216}