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(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    #[cfg(feature = "tt")]
67    pub fn parse<S: Copy>(tt: &tt::TopSubtree<S>) -> CfgExpr {
68        next_cfg_expr(&mut tt.iter()).unwrap_or(CfgExpr::Invalid)
69    }
70
71    #[cfg(feature = "tt")]
72    pub fn parse_from_iter<S: Copy>(tt: &mut tt::iter::TtIter<'_, S>) -> CfgExpr {
73        next_cfg_expr(tt).unwrap_or(CfgExpr::Invalid)
74    }
75
76    /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
77    pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option<bool> {
78        match self {
79            CfgExpr::Invalid => None,
80            CfgExpr::Atom(atom) => Some(query(atom)),
81            CfgExpr::All(preds) => {
82                preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
83            }
84            CfgExpr::Any(preds) => {
85                preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?))
86            }
87            CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
88        }
89    }
90}
91
92#[cfg(feature = "tt")]
93fn next_cfg_expr<S: Copy>(it: &mut tt::iter::TtIter<'_, S>) -> Option<CfgExpr> {
94    use intern::sym;
95    use tt::iter::TtElement;
96
97    let name = match it.next() {
98        None => return None,
99        Some(TtElement::Leaf(tt::Leaf::Ident(ident))) => ident.sym.clone(),
100        Some(_) => return Some(CfgExpr::Invalid),
101    };
102
103    let ret = match it.peek() {
104        Some(TtElement::Leaf(tt::Leaf::Punct(punct)))
105            // Don't consume on e.g. `=>`.
106            if punct.char == '='
107                && (punct.spacing == tt::Spacing::Alone
108                    || it.remaining().flat_tokens().get(1).is_none_or(|peek2| {
109                        !matches!(peek2, tt::TokenTree::Leaf(tt::Leaf::Punct(_)))
110                    })) =>
111        {
112            match it.remaining().flat_tokens().get(1) {
113                Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
114                    it.next();
115                    it.next();
116                    CfgAtom::KeyValue { key: name, value: literal.symbol.clone() }.into()
117                }
118                _ => return Some(CfgExpr::Invalid),
119            }
120        }
121        Some(TtElement::Subtree(_, mut sub_it)) => {
122            it.next();
123            let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it));
124            match name {
125                s if s == sym::all => CfgExpr::All(subs.collect()),
126                s if s == sym::any => CfgExpr::Any(subs.collect()),
127                s if s == sym::not => {
128                    CfgExpr::Not(Box::new(subs.next().unwrap_or(CfgExpr::Invalid)))
129                }
130                _ => CfgExpr::Invalid,
131            }
132        }
133        _ => CfgAtom::Flag(name).into(),
134    };
135
136    // Eat comma separator
137    if let Some(TtElement::Leaf(tt::Leaf::Punct(punct))) = it.peek()
138        && punct.char == ','
139    {
140        it.next();
141    }
142    Some(ret)
143}
144
145#[cfg(test)]
146impl arbitrary::Arbitrary<'_> for CfgAtom {
147    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
148        if u.arbitrary()? {
149            Ok(CfgAtom::Flag(Symbol::intern(<_>::arbitrary(u)?)))
150        } else {
151            Ok(CfgAtom::KeyValue {
152                key: Symbol::intern(<_>::arbitrary(u)?),
153                value: Symbol::intern(<_>::arbitrary(u)?),
154            })
155        }
156    }
157}