cfg/
lib.rs

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