]> git.lizzy.rs Git - rust.git/blob - crates/cfg/src/cfg_expr.rs
42327f1e147bbbadf2aaae0b9411aec2cea3434c
[rust.git] / crates / cfg / src / 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
5 use std::{fmt, slice::Iter as SliceIter};
6
7 use tt::SmolStr;
8
9 /// A simple configuration value passed in from the outside.
10 #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
11 pub enum CfgAtom {
12     /// eg. `#[cfg(test)]`
13     Flag(SmolStr),
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: SmolStr, value: SmolStr },
19 }
20
21 impl CfgAtom {
22     /// Returns `true` when the atom comes from the target specification.
23     ///
24     /// If this returns `true`, then changing this atom requires changing the compilation target. If
25     /// it returns `false`, the atom might come from a build script or the build system.
26     pub fn is_target_defined(&self) -> bool {
27         match self {
28             CfgAtom::Flag(flag) => matches!(&**flag, "unix" | "windows"),
29             CfgAtom::KeyValue { key, value: _ } => matches!(
30                 &**key,
31                 "target_arch"
32                     | "target_os"
33                     | "target_env"
34                     | "target_family"
35                     | "target_endian"
36                     | "target_pointer_width"
37                     | "target_vendor" // NOTE: `target_feature` is left out since it can be configured via `-Ctarget-feature`
38             ),
39         }
40     }
41 }
42
43 impl fmt::Display for CfgAtom {
44     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45         match self {
46             CfgAtom::Flag(name) => write!(f, "{}", name),
47             CfgAtom::KeyValue { key, value } => write!(f, "{} = {:?}", key, value),
48         }
49     }
50 }
51
52 #[derive(Debug, Clone, PartialEq, Eq)]
53 pub enum CfgExpr {
54     Invalid,
55     Atom(CfgAtom),
56     All(Vec<CfgExpr>),
57     Any(Vec<CfgExpr>),
58     Not(Box<CfgExpr>),
59 }
60
61 impl From<CfgAtom> for CfgExpr {
62     fn from(atom: CfgAtom) -> Self {
63         CfgExpr::Atom(atom)
64     }
65 }
66
67 impl CfgExpr {
68     pub fn parse(tt: &tt::Subtree) -> CfgExpr {
69         next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
70     }
71     /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
72     pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option<bool> {
73         match self {
74             CfgExpr::Invalid => None,
75             CfgExpr::Atom(atom) => Some(query(atom)),
76             CfgExpr::All(preds) => {
77                 preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
78             }
79             CfgExpr::Any(preds) => {
80                 preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?))
81             }
82             CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
83         }
84     }
85 }
86
87 fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
88     let name = match it.next() {
89         None => return None,
90         Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(),
91         Some(_) => return Some(CfgExpr::Invalid),
92     };
93
94     // Peek
95     let ret = match it.as_slice().first() {
96         Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
97             match it.as_slice().get(1) {
98                 Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
99                     it.next();
100                     it.next();
101                     // FIXME: escape? raw string?
102                     let value =
103                         SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
104                     CfgAtom::KeyValue { key: name, value }.into()
105                 }
106                 _ => return Some(CfgExpr::Invalid),
107             }
108         }
109         Some(tt::TokenTree::Subtree(subtree)) => {
110             it.next();
111             let mut sub_it = subtree.token_trees.iter();
112             let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it)).collect();
113             match name.as_str() {
114                 "all" => CfgExpr::All(subs),
115                 "any" => CfgExpr::Any(subs),
116                 "not" => CfgExpr::Not(Box::new(subs.pop().unwrap_or(CfgExpr::Invalid))),
117                 _ => CfgExpr::Invalid,
118             }
119         }
120         _ => CfgAtom::Flag(name).into(),
121     };
122
123     // Eat comma separator
124     if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() {
125         if punct.char == ',' {
126             it.next();
127         }
128     }
129     Some(ret)
130 }