aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Schievink <[email protected]>2020-10-21 18:54:04 +0100
committerJonas Schievink <[email protected]>2020-10-22 16:57:00 +0100
commit68b17986c7c272d9be8df9a7abb9b162329b9d65 (patch)
tree82bd50239ada5dc6b9952f353c1e6acde07fe337
parent2bc4c1ff31b6ef0ca1848a7ce12f981b93dcded0 (diff)
Implement DNF-based `#[cfg]` introspection
-rw-r--r--Cargo.lock1
-rw-r--r--crates/cfg/Cargo.toml1
-rw-r--r--crates/cfg/src/cfg_expr.rs35
-rw-r--r--crates/cfg/src/dnf.rs477
-rw-r--r--crates/cfg/src/lib.rs110
5 files changed, 622 insertions, 2 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 65c8de719..d435ec2ea 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -162,6 +162,7 @@ checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
162name = "cfg" 162name = "cfg"
163version = "0.0.0" 163version = "0.0.0"
164dependencies = [ 164dependencies = [
165 "expect-test",
165 "mbe", 166 "mbe",
166 "rustc-hash", 167 "rustc-hash",
167 "syntax", 168 "syntax",
diff --git a/crates/cfg/Cargo.toml b/crates/cfg/Cargo.toml
index a6785ee8e..c68e391c1 100644
--- a/crates/cfg/Cargo.toml
+++ b/crates/cfg/Cargo.toml
@@ -17,3 +17,4 @@ tt = { path = "../tt", version = "0.0.0" }
17[dev-dependencies] 17[dev-dependencies]
18mbe = { path = "../mbe" } 18mbe = { path = "../mbe" }
19syntax = { path = "../syntax" } 19syntax = { path = "../syntax" }
20expect-test = "1.0"
diff --git a/crates/cfg/src/cfg_expr.rs b/crates/cfg/src/cfg_expr.rs
index db3655b74..3f12a3fa8 100644
--- a/crates/cfg/src/cfg_expr.rs
+++ b/crates/cfg/src/cfg_expr.rs
@@ -2,12 +2,12 @@
2//! 2//!
3//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation 3//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation
4 4
5use std::slice::Iter as SliceIter; 5use std::{fmt, slice::Iter as SliceIter};
6 6
7use tt::SmolStr; 7use tt::SmolStr;
8 8
9/// A simple configuration value passed in from the outside. 9/// A simple configuration value passed in from the outside.
10#[derive(Debug, Clone, PartialEq, Eq, Hash)] 10#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
11pub enum CfgAtom { 11pub enum CfgAtom {
12 /// eg. `#[cfg(test)]` 12 /// eg. `#[cfg(test)]`
13 Flag(SmolStr), 13 Flag(SmolStr),
@@ -18,6 +18,37 @@ pub enum CfgAtom {
18 KeyValue { key: SmolStr, value: SmolStr }, 18 KeyValue { key: SmolStr, value: SmolStr },
19} 19}
20 20
21impl 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
43impl 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
21#[derive(Debug, Clone, PartialEq, Eq)] 52#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum CfgExpr { 53pub enum CfgExpr {
23 Invalid, 54 Invalid,
diff --git a/crates/cfg/src/dnf.rs b/crates/cfg/src/dnf.rs
new file mode 100644
index 000000000..35f946e6f
--- /dev/null
+++ b/crates/cfg/src/dnf.rs
@@ -0,0 +1,477 @@
1//! Disjunctive Normal Form construction.
2//!
3//! Algorithm from <https://www.cs.drexel.edu/~jjohnson/2015-16/fall/CS270/Lectures/3/dnf.pdf>,
4//! which would have been much easier to read if it used pattern matching. It's also missing the
5//! entire "distribute ANDs over ORs" part, which is not trivial. Oh well.
6//!
7//! This is currently both messy and inefficient. Feel free to improve, there are unit tests.
8
9use std::fmt;
10
11use rustc_hash::FxHashSet;
12
13use crate::{CfgAtom, CfgDiff, CfgExpr, CfgOptions, InactiveReason};
14
15/// A `#[cfg]` directive in Disjunctive Normal Form (DNF).
16pub struct DnfExpr {
17 conjunctions: Vec<Conjunction>,
18}
19
20impl DnfExpr {
21 pub fn new(expr: CfgExpr) -> Self {
22 let builder = Builder { expr: DnfExpr { conjunctions: Vec::new() } };
23
24 builder.lower(expr.clone())
25 }
26
27 /// Computes a list of present or absent atoms in `opts` that cause this expression to evaluate
28 /// to `false`.
29 ///
30 /// Note that flipping a subset of these atoms might be sufficient to make the whole expression
31 /// evaluate to `true`. For that, see `compute_enable_hints`.
32 ///
33 /// Returns `None` when `self` is already true, or contains errors.
34 pub fn why_inactive(&self, opts: &CfgOptions) -> Option<InactiveReason> {
35 let mut res = InactiveReason { enabled: Vec::new(), disabled: Vec::new() };
36
37 for conj in &self.conjunctions {
38 let mut conj_is_true = true;
39 for lit in &conj.literals {
40 let atom = lit.var.as_ref()?;
41 let enabled = opts.enabled.contains(atom);
42 if lit.negate == enabled {
43 // Literal is false, but needs to be true for this conjunction.
44 conj_is_true = false;
45
46 if enabled {
47 res.enabled.push(atom.clone());
48 } else {
49 res.disabled.push(atom.clone());
50 }
51 }
52 }
53
54 if conj_is_true {
55 // This expression is not actually inactive.
56 return None;
57 }
58 }
59
60 res.enabled.sort_unstable();
61 res.enabled.dedup();
62 res.disabled.sort_unstable();
63 res.disabled.dedup();
64 Some(res)
65 }
66
67 /// Returns `CfgDiff` objects that would enable this directive if applied to `opts`.
68 pub fn compute_enable_hints<'a>(
69 &'a self,
70 opts: &'a CfgOptions,
71 ) -> impl Iterator<Item = CfgDiff> + 'a {
72 // A cfg is enabled if any of `self.conjunctions` evaluate to `true`.
73
74 self.conjunctions.iter().filter_map(move |conj| {
75 let mut enable = FxHashSet::default();
76 let mut disable = FxHashSet::default();
77 for lit in &conj.literals {
78 let atom = lit.var.as_ref()?;
79 let enabled = opts.enabled.contains(atom);
80 if lit.negate && enabled {
81 disable.insert(atom.clone());
82 }
83 if !lit.negate && !enabled {
84 enable.insert(atom.clone());
85 }
86 }
87
88 // Check that this actually makes `conj` true.
89 for lit in &conj.literals {
90 let atom = lit.var.as_ref()?;
91 let enabled = enable.contains(atom)
92 || (opts.enabled.contains(atom) && !disable.contains(atom));
93 if enabled == lit.negate {
94 return None;
95 }
96 }
97
98 if enable.is_empty() && disable.is_empty() {
99 return None;
100 }
101
102 let mut diff = CfgDiff {
103 enable: enable.into_iter().collect(),
104 disable: disable.into_iter().collect(),
105 };
106
107 // Undo the FxHashMap randomization for consistent output.
108 diff.enable.sort_unstable();
109 diff.disable.sort_unstable();
110
111 Some(diff)
112 })
113 }
114}
115
116impl fmt::Display for DnfExpr {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 if self.conjunctions.len() != 1 {
119 write!(f, "any(")?;
120 }
121 for (i, conj) in self.conjunctions.iter().enumerate() {
122 if i != 0 {
123 f.write_str(", ")?;
124 }
125
126 write!(f, "{}", conj)?;
127 }
128 if self.conjunctions.len() != 1 {
129 write!(f, ")")?;
130 }
131
132 Ok(())
133 }
134}
135
136struct Conjunction {
137 literals: Vec<Literal>,
138}
139
140impl Conjunction {
141 fn new(parts: Vec<CfgExpr>) -> Self {
142 let mut literals = Vec::new();
143 for part in parts {
144 match part {
145 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
146 literals.push(Literal::new(part));
147 }
148 CfgExpr::All(conj) => {
149 // Flatten.
150 literals.extend(Conjunction::new(conj).literals);
151 }
152 CfgExpr::Any(_) => unreachable!("disjunction in conjunction"),
153 }
154 }
155
156 Self { literals }
157 }
158}
159
160impl fmt::Display for Conjunction {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 if self.literals.len() != 1 {
163 write!(f, "all(")?;
164 }
165 for (i, lit) in self.literals.iter().enumerate() {
166 if i != 0 {
167 f.write_str(", ")?;
168 }
169
170 write!(f, "{}", lit)?;
171 }
172 if self.literals.len() != 1 {
173 write!(f, ")")?;
174 }
175
176 Ok(())
177 }
178}
179
180struct Literal {
181 negate: bool,
182 var: Option<CfgAtom>, // None = Invalid
183}
184
185impl Literal {
186 fn new(expr: CfgExpr) -> Self {
187 match expr {
188 CfgExpr::Invalid => Self { negate: false, var: None },
189 CfgExpr::Atom(atom) => Self { negate: false, var: Some(atom) },
190 CfgExpr::Not(expr) => match *expr {
191 CfgExpr::Invalid => Self { negate: true, var: None },
192 CfgExpr::Atom(atom) => Self { negate: true, var: Some(atom) },
193 _ => unreachable!("non-atom {:?}", expr),
194 },
195 CfgExpr::Any(_) | CfgExpr::All(_) => unreachable!("non-literal {:?}", expr),
196 }
197 }
198}
199
200impl fmt::Display for Literal {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 if self.negate {
203 write!(f, "not(")?;
204 }
205
206 match &self.var {
207 Some(var) => write!(f, "{}", var)?,
208 None => f.write_str("<invalid>")?,
209 }
210
211 if self.negate {
212 write!(f, ")")?;
213 }
214
215 Ok(())
216 }
217}
218
219struct Builder {
220 expr: DnfExpr,
221}
222
223impl Builder {
224 fn lower(mut self, expr: CfgExpr) -> DnfExpr {
225 let expr = make_nnf(expr);
226 let expr = make_dnf(expr);
227
228 match expr {
229 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
230 self.expr.conjunctions.push(Conjunction::new(vec![expr]));
231 }
232 CfgExpr::All(conj) => {
233 self.expr.conjunctions.push(Conjunction::new(conj));
234 }
235 CfgExpr::Any(mut disj) => {
236 disj.reverse();
237 while let Some(conj) = disj.pop() {
238 match conj {
239 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::All(_) | CfgExpr::Not(_) => {
240 self.expr.conjunctions.push(Conjunction::new(vec![conj]));
241 }
242 CfgExpr::Any(inner_disj) => {
243 // Flatten.
244 disj.extend(inner_disj.into_iter().rev());
245 }
246 }
247 }
248 }
249 }
250
251 self.expr
252 }
253}
254
255fn make_dnf(expr: CfgExpr) -> CfgExpr {
256 match expr {
257 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => expr,
258 CfgExpr::Any(e) => CfgExpr::Any(e.into_iter().map(|expr| make_dnf(expr)).collect()),
259 CfgExpr::All(e) => {
260 let e = e.into_iter().map(|expr| make_nnf(expr)).collect::<Vec<_>>();
261
262 CfgExpr::Any(distribute_conj(&e))
263 }
264 }
265}
266
267/// Turns a conjunction of expressions into a disjunction of expressions.
268fn distribute_conj(conj: &[CfgExpr]) -> Vec<CfgExpr> {
269 fn go(out: &mut Vec<CfgExpr>, with: &mut Vec<CfgExpr>, rest: &[CfgExpr]) {
270 match rest {
271 [head, tail @ ..] => match head {
272 CfgExpr::Any(disj) => {
273 for part in disj {
274 with.push(part.clone());
275 go(out, with, tail);
276 with.pop();
277 }
278 }
279 _ => {
280 with.push(head.clone());
281 go(out, with, tail);
282 with.pop();
283 }
284 },
285 _ => {
286 // Turn accumulated parts into a new conjunction.
287 out.push(CfgExpr::All(with.clone()));
288 }
289 }
290 }
291
292 let mut out = Vec::new();
293 let mut with = Vec::new();
294
295 go(&mut out, &mut with, conj);
296
297 out
298}
299
300fn make_nnf(expr: CfgExpr) -> CfgExpr {
301 match expr {
302 CfgExpr::Invalid | CfgExpr::Atom(_) => expr,
303 CfgExpr::Any(expr) => CfgExpr::Any(expr.into_iter().map(|expr| make_nnf(expr)).collect()),
304 CfgExpr::All(expr) => CfgExpr::All(expr.into_iter().map(|expr| make_nnf(expr)).collect()),
305 CfgExpr::Not(operand) => match *operand {
306 CfgExpr::Invalid | CfgExpr::Atom(_) => CfgExpr::Not(operand.clone()), // Original negated expr
307 CfgExpr::Not(expr) => {
308 // Remove double negation.
309 make_nnf(*expr)
310 }
311 // Convert negated conjunction/disjunction using DeMorgan's Law.
312 CfgExpr::Any(inner) => CfgExpr::All(
313 inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
314 ),
315 CfgExpr::All(inner) => CfgExpr::Any(
316 inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
317 ),
318 },
319 }
320}
321
322#[cfg(test)]
323mod test {
324 use expect_test::{expect, Expect};
325 use mbe::ast_to_token_tree;
326 use syntax::{ast, AstNode};
327
328 use super::*;
329
330 fn check_dnf(input: &str, expect: Expect) {
331 let (tt, _) = {
332 let source_file = ast::SourceFile::parse(input).ok().unwrap();
333 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
334 ast_to_token_tree(&tt).unwrap()
335 };
336 let cfg = CfgExpr::parse(&tt);
337 let actual = format!("#![cfg({})]", DnfExpr::new(cfg));
338 expect.assert_eq(&actual);
339 }
340
341 fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) {
342 let (tt, _) = {
343 let source_file = ast::SourceFile::parse(input).ok().unwrap();
344 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
345 ast_to_token_tree(&tt).unwrap()
346 };
347 let cfg = CfgExpr::parse(&tt);
348 let dnf = DnfExpr::new(cfg);
349 let why_inactive = dnf.why_inactive(opts).unwrap().to_string();
350 expect.assert_eq(&why_inactive);
351 }
352
353 #[track_caller]
354 fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) {
355 let (tt, _) = {
356 let source_file = ast::SourceFile::parse(input).ok().unwrap();
357 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
358 ast_to_token_tree(&tt).unwrap()
359 };
360 let cfg = CfgExpr::parse(&tt);
361 let dnf = DnfExpr::new(cfg);
362 let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::<Vec<_>>();
363 assert_eq!(hints, expected_hints);
364 }
365
366 #[test]
367 fn smoke() {
368 check_dnf("#![cfg(test)]", expect![[r#"#![cfg(test)]"#]]);
369 check_dnf("#![cfg(not(test))]", expect![[r#"#![cfg(not(test))]"#]]);
370 check_dnf("#![cfg(not(not(test)))]", expect![[r#"#![cfg(test)]"#]]);
371
372 check_dnf("#![cfg(all(a, b))]", expect![[r#"#![cfg(all(a, b))]"#]]);
373 check_dnf("#![cfg(any(a, b))]", expect![[r#"#![cfg(any(a, b))]"#]]);
374
375 check_dnf("#![cfg(not(a))]", expect![[r#"#![cfg(not(a))]"#]]);
376 }
377
378 #[test]
379 fn distribute() {
380 check_dnf("#![cfg(all(any(a, b), c))]", expect![[r#"#![cfg(any(all(a, c), all(b, c)))]"#]]);
381 check_dnf("#![cfg(all(c, any(a, b)))]", expect![[r#"#![cfg(any(all(c, a), all(c, b)))]"#]]);
382 check_dnf(
383 "#![cfg(all(any(a, b), any(c, d)))]",
384 expect![[r#"#![cfg(any(all(a, c), all(a, d), all(b, c), all(b, d)))]"#]],
385 );
386
387 check_dnf(
388 "#![cfg(all(any(a, b, c), any(d, e, f), g))]",
389 expect![[
390 r#"#![cfg(any(all(a, d, g), all(a, e, g), all(a, f, g), all(b, d, g), all(b, e, g), all(b, f, g), all(c, d, g), all(c, e, g), all(c, f, g)))]"#
391 ]],
392 );
393 }
394
395 #[test]
396 fn demorgan() {
397 check_dnf("#![cfg(not(all(a, b)))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
398 check_dnf("#![cfg(not(any(a, b)))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
399
400 check_dnf("#![cfg(not(all(not(a), b)))]", expect![[r#"#![cfg(any(a, not(b)))]"#]]);
401 check_dnf("#![cfg(not(any(a, not(b))))]", expect![[r#"#![cfg(all(not(a), b))]"#]]);
402 }
403
404 #[test]
405 fn nested() {
406 check_dnf(
407 "#![cfg(all(any(a), not(all(any(b)))))]",
408 expect![[r#"#![cfg(all(a, not(b)))]"#]],
409 );
410
411 check_dnf("#![cfg(any(any(a, b)))]", expect![[r#"#![cfg(any(a, b))]"#]]);
412 check_dnf("#![cfg(not(any(any(a, b))))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
413 check_dnf("#![cfg(all(all(a, b)))]", expect![[r#"#![cfg(all(a, b))]"#]]);
414 check_dnf("#![cfg(not(all(all(a, b))))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
415 }
416
417 #[test]
418 fn hints() {
419 let mut opts = CfgOptions::default();
420
421 check_enable_hints("#![cfg(test)]", &opts, &["enable test"]);
422 check_enable_hints("#![cfg(not(test))]", &opts, &[]);
423
424 check_enable_hints("#![cfg(any(a, b))]", &opts, &["enable a", "enable b"]);
425 check_enable_hints("#![cfg(any(b, a))]", &opts, &["enable b", "enable a"]);
426
427 check_enable_hints("#![cfg(all(a, b))]", &opts, &["enable a and b"]);
428
429 opts.insert_atom("test".into());
430
431 check_enable_hints("#![cfg(test)]", &opts, &[]);
432 check_enable_hints("#![cfg(not(test))]", &opts, &["disable test"]);
433 }
434
435 /// Tests that we don't suggest hints for cfgs that express an inconsistent formula.
436 #[test]
437 fn hints_impossible() {
438 let mut opts = CfgOptions::default();
439
440 check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
441
442 opts.insert_atom("test".into());
443
444 check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
445 }
446
447 #[test]
448 fn why_inactive() {
449 let mut opts = CfgOptions::default();
450 opts.insert_atom("test".into());
451 opts.insert_atom("test2".into());
452
453 check_why_inactive("#![cfg(a)]", &opts, expect![["a is disabled"]]);
454 check_why_inactive("#![cfg(not(test))]", &opts, expect![["test is enabled"]]);
455
456 check_why_inactive(
457 "#![cfg(all(not(test), not(test2)))]",
458 &opts,
459 expect![["test and test2 are enabled"]],
460 );
461 check_why_inactive(
462 "#![cfg(all(not(test), a))]",
463 &opts,
464 expect![["test is enabled and a is disabled"]],
465 );
466 check_why_inactive(
467 "#![cfg(all(not(test), test2, a))]",
468 &opts,
469 expect![["test is enabled and a is disabled"]],
470 );
471 check_why_inactive(
472 "#![cfg(all(not(test), not(test2), a))]",
473 &opts,
474 expect![["test and test2 are enabled and a is disabled"]],
475 );
476 }
477}
diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs
index 35f540ac3..0b0734213 100644
--- a/crates/cfg/src/lib.rs
+++ b/crates/cfg/src/lib.rs
@@ -1,11 +1,15 @@
1//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator 1//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator
2 2
3mod cfg_expr; 3mod cfg_expr;
4mod dnf;
5
6use std::fmt;
4 7
5use rustc_hash::FxHashSet; 8use rustc_hash::FxHashSet;
6use tt::SmolStr; 9use tt::SmolStr;
7 10
8pub use cfg_expr::{CfgAtom, CfgExpr}; 11pub use cfg_expr::{CfgAtom, CfgExpr};
12pub use dnf::DnfExpr;
9 13
10/// Configuration options used for conditional compilition on items with `cfg` attributes. 14/// Configuration options used for conditional compilition on items with `cfg` attributes.
11/// We have two kind of options in different namespaces: atomic options like `unix`, and 15/// We have two kind of options in different namespaces: atomic options like `unix`, and
@@ -40,4 +44,110 @@ impl CfgOptions {
40 self.enabled.insert(atom.clone()); 44 self.enabled.insert(atom.clone());
41 } 45 }
42 } 46 }
47
48 pub fn apply_diff(&mut self, diff: CfgDiff) {
49 for atom in diff.enable {
50 self.enabled.insert(atom);
51 }
52
53 for atom in diff.disable {
54 self.enabled.remove(&atom);
55 }
56 }
57}
58
59pub struct CfgDiff {
60 // Invariants: No duplicates, no atom that's both in `enable` and `disable`.
61 enable: Vec<CfgAtom>,
62 disable: Vec<CfgAtom>,
63}
64
65impl CfgDiff {
66 /// Returns the total number of atoms changed by this diff.
67 pub fn len(&self) -> usize {
68 self.enable.len() + self.disable.len()
69 }
70}
71
72impl fmt::Display for CfgDiff {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 if !self.enable.is_empty() {
75 f.write_str("enable ")?;
76 for (i, atom) in self.enable.iter().enumerate() {
77 let sep = match i {
78 0 => "",
79 _ if i == self.enable.len() - 1 => " and ",
80 _ => ", ",
81 };
82 f.write_str(sep)?;
83
84 write!(f, "{}", atom)?;
85 }
86
87 if !self.disable.is_empty() {
88 f.write_str("; ")?;
89 }
90 }
91
92 if !self.disable.is_empty() {
93 f.write_str("disable ")?;
94 for (i, atom) in self.disable.iter().enumerate() {
95 let sep = match i {
96 0 => "",
97 _ if i == self.enable.len() - 1 => " and ",
98 _ => ", ",
99 };
100 f.write_str(sep)?;
101
102 write!(f, "{}", atom)?;
103 }
104 }
105
106 Ok(())
107 }
108}
109
110pub struct InactiveReason {
111 enabled: Vec<CfgAtom>,
112 disabled: Vec<CfgAtom>,
113}
114
115impl fmt::Display for InactiveReason {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 if !self.enabled.is_empty() {
118 for (i, atom) in self.enabled.iter().enumerate() {
119 let sep = match i {
120 0 => "",
121 _ if i == self.enabled.len() - 1 => " and ",
122 _ => ", ",
123 };
124 f.write_str(sep)?;
125
126 write!(f, "{}", atom)?;
127 }
128 let is_are = if self.enabled.len() == 1 { "is" } else { "are" };
129 write!(f, " {} enabled", is_are)?;
130
131 if !self.disabled.is_empty() {
132 f.write_str(" and ")?;
133 }
134 }
135
136 if !self.disabled.is_empty() {
137 for (i, atom) in self.disabled.iter().enumerate() {
138 let sep = match i {
139 0 => "",
140 _ if i == self.enabled.len() - 1 => " and ",
141 _ => ", ",
142 };
143 f.write_str(sep)?;
144
145 write!(f, "{}", atom)?;
146 }
147 let is_are = if self.disabled.len() == 1 { "is" } else { "are" };
148 write!(f, " {} disabled", is_are)?;
149 }
150
151 Ok(())
152 }
43} 153}