ra_ap_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    pub fn append(&mut self, other: CfgOptions) {
120        // Do not call `insert_any_atom()`, as it'll check for `true` and `false`, but this is not
121        // needed since we already checked for that when constructing `other`. Furthermore, this
122        // will always err, as `other` inevitably contains `true` (just as we do).
123        self.enabled.extend(other.enabled);
124    }
125}
126
127impl Extend<CfgAtom> for CfgOptions {
128    fn extend<T: IntoIterator<Item = CfgAtom>>(&mut self, iter: T) {
129        iter.into_iter().for_each(|cfg_flag| self.insert_any_atom(cfg_flag));
130    }
131}
132
133impl IntoIterator for CfgOptions {
134    type Item = <FxHashSet<CfgAtom> as IntoIterator>::Item;
135
136    type IntoIter = <FxHashSet<CfgAtom> as IntoIterator>::IntoIter;
137
138    fn into_iter(self) -> Self::IntoIter {
139        <FxHashSet<CfgAtom> as IntoIterator>::into_iter(self.enabled)
140    }
141}
142
143impl<'a> IntoIterator for &'a CfgOptions {
144    type Item = <&'a FxHashSet<CfgAtom> as IntoIterator>::Item;
145
146    type IntoIter = <&'a FxHashSet<CfgAtom> as IntoIterator>::IntoIter;
147
148    fn into_iter(self) -> Self::IntoIter {
149        <&FxHashSet<CfgAtom> as IntoIterator>::into_iter(&self.enabled)
150    }
151}
152
153impl FromIterator<CfgAtom> for CfgOptions {
154    fn from_iter<T: IntoIterator<Item = CfgAtom>>(iter: T) -> Self {
155        let mut options = CfgOptions::default();
156        options.extend(iter);
157        options
158    }
159}
160
161#[derive(Default, Clone, Debug, PartialEq, Eq)]
162pub struct CfgDiff {
163    // Invariants: No duplicates, no atom that's both in `enable` and `disable`.
164    enable: Vec<CfgAtom>,
165    disable: Vec<CfgAtom>,
166}
167
168impl CfgDiff {
169    /// Create a new CfgDiff.
170    pub fn new(mut enable: Vec<CfgAtom>, mut disable: Vec<CfgAtom>) -> CfgDiff {
171        enable.sort();
172        enable.dedup();
173        disable.sort();
174        disable.dedup();
175        for i in (0..enable.len()).rev() {
176            if let Some(j) = disable.iter().position(|atom| *atom == enable[i]) {
177                enable.remove(i);
178                disable.remove(j);
179            }
180        }
181
182        CfgDiff { enable, disable }
183    }
184
185    /// Returns the total number of atoms changed by this diff.
186    pub fn len(&self) -> usize {
187        self.enable.len() + self.disable.len()
188    }
189
190    pub fn is_empty(&self) -> bool {
191        self.len() == 0
192    }
193}
194
195impl fmt::Display for CfgDiff {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        if !self.enable.is_empty() {
198            f.write_str("enable ")?;
199            for (i, atom) in self.enable.iter().enumerate() {
200                let sep = match i {
201                    0 => "",
202                    _ if i == self.enable.len() - 1 => " and ",
203                    _ => ", ",
204                };
205                f.write_str(sep)?;
206
207                atom.fmt(f)?;
208            }
209
210            if !self.disable.is_empty() {
211                f.write_str("; ")?;
212            }
213        }
214
215        if !self.disable.is_empty() {
216            f.write_str("disable ")?;
217            for (i, atom) in self.disable.iter().enumerate() {
218                let sep = match i {
219                    0 => "",
220                    _ if i == self.enable.len() - 1 => " and ",
221                    _ => ", ",
222                };
223                f.write_str(sep)?;
224
225                atom.fmt(f)?;
226            }
227        }
228
229        Ok(())
230    }
231}
232
233pub struct InactiveReason {
234    enabled: Vec<CfgAtom>,
235    disabled: Vec<CfgAtom>,
236}
237
238impl fmt::Display for InactiveReason {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        if !self.enabled.is_empty() {
241            for (i, atom) in self.enabled.iter().enumerate() {
242                let sep = match i {
243                    0 => "",
244                    _ if i == self.enabled.len() - 1 => " and ",
245                    _ => ", ",
246                };
247                f.write_str(sep)?;
248
249                atom.fmt(f)?;
250            }
251            let is_are = if self.enabled.len() == 1 { "is" } else { "are" };
252            write!(f, " {is_are} enabled")?;
253
254            if !self.disabled.is_empty() {
255                f.write_str(" and ")?;
256            }
257        }
258
259        if !self.disabled.is_empty() {
260            for (i, atom) in self.disabled.iter().enumerate() {
261                let sep = match i {
262                    0 => "",
263                    _ if i == self.disabled.len() - 1 => " and ",
264                    _ => ", ",
265                };
266                f.write_str(sep)?;
267
268                atom.fmt(f)?;
269            }
270            let is_are = if self.disabled.len() == 1 { "is" } else { "are" };
271            write!(f, " {is_are} disabled")?;
272        }
273
274        Ok(())
275    }
276}
277
278/// A `CfgOptions` that implements `Hash`, for the sake of hashing only.
279#[derive(Debug, Clone, PartialEq, Eq, Hash)]
280pub struct HashableCfgOptions {
281    _enabled: Box<[CfgAtom]>,
282}