cfg/
lib.rs

1//! cfg defines conditional compiling options, `cfg` attribute parser and evaluator
2
3#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
4
5#[cfg(feature = "in-rust-tree")]
6extern crate rustc_driver as _;
7
8mod cfg_expr;
9mod dnf;
10#[cfg(test)]
11mod tests;
12
13use std::fmt;
14
15use rustc_hash::FxHashSet;
16
17use intern::{Symbol, sym};
18
19pub use cfg_expr::{CfgAtom, CfgExpr};
20pub use dnf::DnfExpr;
21
22/// Configuration options used for conditional compilation on items with `cfg` attributes.
23/// We have two kind of options in different namespaces: atomic options like `unix`, and
24/// key-value options like `target_arch="x86"`.
25///
26/// Note that for key-value options, one key can have multiple values (but not none).
27/// `feature` is an example. We have both `feature="foo"` and `feature="bar"` if features
28/// `foo` and `bar` are both enabled. And here, we store key-value options as a set of tuple
29/// of key and value in `key_values`.
30///
31/// See: <https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options>
32#[derive(Clone, PartialEq, Eq)]
33pub struct CfgOptions {
34    enabled: FxHashSet<CfgAtom>,
35}
36
37impl Default for CfgOptions {
38    fn default() -> Self {
39        Self { enabled: FxHashSet::from_iter([CfgAtom::Flag(sym::true_)]) }
40    }
41}
42
43impl fmt::Debug for CfgOptions {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        let mut items = self
46            .enabled
47            .iter()
48            .map(|atom| match atom {
49                CfgAtom::Flag(it) => it.to_string(),
50                CfgAtom::KeyValue { key, value } => format!("{key}={value}"),
51            })
52            .collect::<Vec<_>>();
53        items.sort();
54        f.debug_tuple("CfgOptions").field(&items).finish()
55    }
56}
57
58impl CfgOptions {
59    pub fn check(&self, cfg: &CfgExpr) -> Option<bool> {
60        cfg.fold(&|atom| self.enabled.contains(atom))
61    }
62
63    pub fn check_atom(&self, cfg: &CfgAtom) -> bool {
64        self.enabled.contains(cfg)
65    }
66
67    pub fn insert_atom(&mut self, key: Symbol) {
68        self.insert_any_atom(CfgAtom::Flag(key));
69    }
70
71    pub fn insert_key_value(&mut self, key: Symbol, value: Symbol) {
72        self.insert_any_atom(CfgAtom::KeyValue { key, value });
73    }
74
75    pub fn apply_diff(&mut self, diff: CfgDiff) {
76        for atom in diff.enable {
77            self.insert_any_atom(atom);
78        }
79
80        for atom in diff.disable {
81            let (CfgAtom::Flag(sym) | CfgAtom::KeyValue { key: sym, .. }) = &atom;
82            if *sym == sym::true_ || *sym == sym::false_ {
83                tracing::error!("cannot remove `true` or `false` from cfg");
84                continue;
85            }
86            self.enabled.remove(&atom);
87        }
88    }
89
90    fn insert_any_atom(&mut self, atom: CfgAtom) {
91        let (CfgAtom::Flag(sym) | CfgAtom::KeyValue { key: sym, .. }) = &atom;
92        if *sym == sym::true_ || *sym == sym::false_ {
93            tracing::error!("cannot insert `true` or `false` to cfg");
94            return;
95        }
96        self.enabled.insert(atom);
97    }
98
99    pub fn get_cfg_keys(&self) -> impl Iterator<Item = &Symbol> {
100        self.enabled.iter().map(|it| match it {
101            CfgAtom::Flag(key) => key,
102            CfgAtom::KeyValue { key, .. } => key,
103        })
104    }
105
106    pub fn get_cfg_values<'a>(&'a self, cfg_key: &'a str) -> impl Iterator<Item = &'a Symbol> + 'a {
107        self.enabled.iter().filter_map(move |it| match it {
108            CfgAtom::KeyValue { key, value } if cfg_key == key.as_str() => Some(value),
109            _ => None,
110        })
111    }
112
113    pub fn to_hashable(&self) -> HashableCfgOptions {
114        let mut enabled = self.enabled.iter().cloned().collect::<Box<[_]>>();
115        enabled.sort_unstable();
116        HashableCfgOptions { _enabled: enabled }
117    }
118
119    #[inline]
120    pub fn shrink_to_fit(&mut self) {
121        self.enabled.shrink_to_fit();
122    }
123
124    pub fn append(&mut self, other: CfgOptions) {
125        // Do not call `insert_any_atom()`, as it'll check for `true` and `false`, but this is not
126        // needed since we already checked for that when constructing `other`. Furthermore, this
127        // will always err, as `other` inevitably contains `true` (just as we do).
128        self.enabled.extend(other.enabled);
129    }
130}
131
132impl Extend<CfgAtom> for CfgOptions {
133    fn extend<T: IntoIterator<Item = CfgAtom>>(&mut self, iter: T) {
134        iter.into_iter().for_each(|cfg_flag| self.insert_any_atom(cfg_flag));
135    }
136}
137
138impl IntoIterator for CfgOptions {
139    type Item = <FxHashSet<CfgAtom> as IntoIterator>::Item;
140
141    type IntoIter = <FxHashSet<CfgAtom> as IntoIterator>::IntoIter;
142
143    fn into_iter(self) -> Self::IntoIter {
144        <FxHashSet<CfgAtom> as IntoIterator>::into_iter(self.enabled)
145    }
146}
147
148impl<'a> IntoIterator for &'a CfgOptions {
149    type Item = <&'a FxHashSet<CfgAtom> as IntoIterator>::Item;
150
151    type IntoIter = <&'a FxHashSet<CfgAtom> as IntoIterator>::IntoIter;
152
153    fn into_iter(self) -> Self::IntoIter {
154        <&FxHashSet<CfgAtom> as IntoIterator>::into_iter(&self.enabled)
155    }
156}
157
158impl FromIterator<CfgAtom> for CfgOptions {
159    fn from_iter<T: IntoIterator<Item = CfgAtom>>(iter: T) -> Self {
160        let mut options = CfgOptions::default();
161        options.extend(iter);
162        options
163    }
164}
165
166#[derive(Default, Clone, Debug, PartialEq, Eq)]
167pub struct CfgDiff {
168    // Invariants: No duplicates, no atom that's both in `enable` and `disable`.
169    enable: Vec<CfgAtom>,
170    disable: Vec<CfgAtom>,
171}
172
173impl CfgDiff {
174    /// Create a new CfgDiff.
175    pub fn new(mut enable: Vec<CfgAtom>, mut disable: Vec<CfgAtom>) -> CfgDiff {
176        enable.sort();
177        enable.dedup();
178        disable.sort();
179        disable.dedup();
180        for i in (0..enable.len()).rev() {
181            if let Some(j) = disable.iter().position(|atom| *atom == enable[i]) {
182                enable.remove(i);
183                disable.remove(j);
184            }
185        }
186
187        CfgDiff { enable, disable }
188    }
189
190    /// Returns the total number of atoms changed by this diff.
191    pub fn len(&self) -> usize {
192        self.enable.len() + self.disable.len()
193    }
194
195    pub fn is_empty(&self) -> bool {
196        self.len() == 0
197    }
198}
199
200impl fmt::Display for CfgDiff {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        if !self.enable.is_empty() {
203            f.write_str("enable ")?;
204            for (i, atom) in self.enable.iter().enumerate() {
205                let sep = match i {
206                    0 => "",
207                    _ if i == self.enable.len() - 1 => " and ",
208                    _ => ", ",
209                };
210                f.write_str(sep)?;
211
212                atom.fmt(f)?;
213            }
214
215            if !self.disable.is_empty() {
216                f.write_str("; ")?;
217            }
218        }
219
220        if !self.disable.is_empty() {
221            f.write_str("disable ")?;
222            for (i, atom) in self.disable.iter().enumerate() {
223                let sep = match i {
224                    0 => "",
225                    _ if i == self.enable.len() - 1 => " and ",
226                    _ => ", ",
227                };
228                f.write_str(sep)?;
229
230                atom.fmt(f)?;
231            }
232        }
233
234        Ok(())
235    }
236}
237
238pub struct InactiveReason {
239    enabled: Vec<CfgAtom>,
240    disabled: Vec<CfgAtom>,
241}
242
243impl fmt::Display for InactiveReason {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        if !self.enabled.is_empty() {
246            for (i, atom) in self.enabled.iter().enumerate() {
247                let sep = match i {
248                    0 => "",
249                    _ if i == self.enabled.len() - 1 => " and ",
250                    _ => ", ",
251                };
252                f.write_str(sep)?;
253
254                atom.fmt(f)?;
255            }
256            let is_are = if self.enabled.len() == 1 { "is" } else { "are" };
257            write!(f, " {is_are} enabled")?;
258
259            if !self.disabled.is_empty() {
260                f.write_str(" and ")?;
261            }
262        }
263
264        if !self.disabled.is_empty() {
265            for (i, atom) in self.disabled.iter().enumerate() {
266                let sep = match i {
267                    0 => "",
268                    _ if i == self.disabled.len() - 1 => " and ",
269                    _ => ", ",
270                };
271                f.write_str(sep)?;
272
273                atom.fmt(f)?;
274            }
275            let is_are = if self.disabled.len() == 1 { "is" } else { "are" };
276            write!(f, " {is_are} disabled")?;
277        }
278
279        Ok(())
280    }
281}
282
283/// A `CfgOptions` that implements `Hash`, for the sake of hashing only.
284#[derive(Debug, Clone, PartialEq, Eq, Hash)]
285pub struct HashableCfgOptions {
286    _enabled: Box<[CfgAtom]>,
287}