1use std::fmt::{self, Write};
10
11use rustc_hash::FxHashSet;
12
13use crate::{CfgAtom, CfgDiff, CfgExpr, CfgOptions, InactiveReason};
14
15pub struct DnfExpr {
17 conjunctions: Vec<Conjunction>,
18}
19
20struct Conjunction {
21 literals: Vec<Literal>,
22}
23
24struct Literal {
25 negate: bool,
26 var: Option<CfgAtom>, }
28
29impl DnfExpr {
30 pub fn new(expr: &CfgExpr) -> Self {
31 let builder = Builder { expr: DnfExpr { conjunctions: Vec::new() } };
32
33 builder.lower(expr)
34 }
35
36 pub fn why_inactive(&self, opts: &CfgOptions) -> Option<InactiveReason> {
44 let mut res = InactiveReason { enabled: Vec::new(), disabled: Vec::new() };
45
46 for conj in &self.conjunctions {
47 let mut conj_is_true = true;
48 for lit in &conj.literals {
49 let atom = lit.var.as_ref()?;
50 let enabled = opts.enabled.contains(atom);
51 if lit.negate == enabled {
52 conj_is_true = false;
54
55 if enabled {
56 res.enabled.push(atom.clone());
57 } else {
58 res.disabled.push(atom.clone());
59 }
60 }
61 }
62
63 if conj_is_true {
64 return None;
66 }
67 }
68
69 res.enabled.sort_unstable();
70 res.enabled.dedup();
71 res.disabled.sort_unstable();
72 res.disabled.dedup();
73 Some(res)
74 }
75
76 pub fn compute_enable_hints<'a>(
78 &'a self,
79 opts: &'a CfgOptions,
80 ) -> impl Iterator<Item = CfgDiff> + 'a {
81 self.conjunctions.iter().filter_map(move |conj| {
84 let mut enable = FxHashSet::default();
85 let mut disable = FxHashSet::default();
86 for lit in &conj.literals {
87 let atom = lit.var.as_ref()?;
88 let enabled = opts.enabled.contains(atom);
89 if lit.negate && enabled {
90 disable.insert(atom.clone());
91 }
92 if !lit.negate && !enabled {
93 enable.insert(atom.clone());
94 }
95 }
96
97 for lit in &conj.literals {
99 let atom = lit.var.as_ref()?;
100 let enabled = enable.contains(atom)
101 || (opts.enabled.contains(atom) && !disable.contains(atom));
102 if enabled == lit.negate {
103 return None;
104 }
105 }
106
107 if enable.is_empty() && disable.is_empty() {
108 return None;
109 }
110
111 let mut diff = CfgDiff {
112 enable: enable.into_iter().collect(),
113 disable: disable.into_iter().collect(),
114 };
115
116 diff.enable.sort_unstable();
118 diff.disable.sort_unstable();
119
120 Some(diff)
121 })
122 }
123}
124
125impl fmt::Display for DnfExpr {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 if self.conjunctions.len() != 1 {
128 f.write_str("any(")?;
129 }
130 for (i, conj) in self.conjunctions.iter().enumerate() {
131 if i != 0 {
132 f.write_str(", ")?;
133 }
134
135 conj.fmt(f)?;
136 }
137 if self.conjunctions.len() != 1 {
138 f.write_char(')')?;
139 }
140
141 Ok(())
142 }
143}
144
145impl Conjunction {
146 fn new(parts: Box<[CfgExpr]>) -> Self {
147 let mut literals = Vec::new();
148 for part in parts.into_vec() {
149 match part {
150 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
151 literals.push(Literal::new(part));
152 }
153 CfgExpr::All(conj) => {
154 literals.extend(Conjunction::new(conj).literals);
156 }
157 CfgExpr::Any(_) => unreachable!("disjunction in conjunction"),
158 }
159 }
160
161 Self { literals }
162 }
163}
164
165impl fmt::Display for Conjunction {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 if self.literals.len() != 1 {
168 f.write_str("all(")?;
169 }
170 for (i, lit) in self.literals.iter().enumerate() {
171 if i != 0 {
172 f.write_str(", ")?;
173 }
174
175 lit.fmt(f)?;
176 }
177 if self.literals.len() != 1 {
178 f.write_str(")")?;
179 }
180
181 Ok(())
182 }
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) => var.fmt(f)?,
208 None => f.write_str("<invalid>")?,
209 }
210
211 if self.negate {
212 f.write_char(')')?;
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(Box::new([expr])));
231 }
232 CfgExpr::All(conj) => {
233 self.expr.conjunctions.push(Conjunction::new(conj));
234 }
235 CfgExpr::Any(disj) => {
236 let mut disj = disj.into_vec();
237 disj.reverse();
238 while let Some(conj) = disj.pop() {
239 match conj {
240 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::All(_) | CfgExpr::Not(_) => {
241 self.expr.conjunctions.push(Conjunction::new(Box::new([conj])));
242 }
243 CfgExpr::Any(inner_disj) => {
244 disj.extend(inner_disj.into_vec().into_iter().rev());
246 }
247 }
248 }
249 }
250 }
251
252 self.expr
253 }
254}
255
256fn make_dnf(expr: CfgExpr) -> CfgExpr {
257 match expr {
258 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => expr,
259 CfgExpr::Any(e) => flatten(CfgExpr::Any(e.into_vec().into_iter().map(make_dnf).collect())),
260 CfgExpr::All(e) => {
261 let e = e.into_vec().into_iter().map(make_dnf).collect::<Vec<_>>();
262
263 flatten(CfgExpr::Any(distribute_conj(&e).into_boxed_slice()))
264 }
265 }
266}
267
268fn distribute_conj(conj: &[CfgExpr]) -> Vec<CfgExpr> {
270 fn go(out: &mut Vec<CfgExpr>, with: &mut Vec<CfgExpr>, rest: &[CfgExpr]) {
271 match rest {
272 [head, tail @ ..] => match head {
273 CfgExpr::Any(disj) => {
274 for part in disj.iter() {
275 with.push(part.clone());
276 go(out, with, tail);
277 with.pop();
278 }
279 }
280 _ => {
281 with.push(head.clone());
282 go(out, with, tail);
283 with.pop();
284 }
285 },
286 _ => {
287 out.push(CfgExpr::All(with.clone().into_boxed_slice()));
289 }
290 }
291 }
292
293 let mut out = Vec::new(); let mut with = Vec::new();
295
296 go(&mut out, &mut with, conj);
297
298 out
299}
300
301fn make_nnf(expr: &CfgExpr) -> CfgExpr {
302 match expr {
303 CfgExpr::Invalid | CfgExpr::Atom(_) => expr.clone(),
304 CfgExpr::Any(expr) => CfgExpr::Any(expr.iter().map(make_nnf).collect()),
305 CfgExpr::All(expr) => CfgExpr::All(expr.iter().map(make_nnf).collect()),
306 CfgExpr::Not(operand) => make_nnf_neg(operand),
307 }
308}
309
310fn make_nnf_neg(operand: &CfgExpr) -> CfgExpr {
311 match operand {
312 CfgExpr::Invalid => CfgExpr::Not(Box::new(CfgExpr::Invalid)), CfgExpr::Atom(atom) => CfgExpr::Not(Box::new(CfgExpr::Atom(atom.clone()))),
316 CfgExpr::Not(expr) => make_nnf(expr),
318 CfgExpr::Any(inner) => CfgExpr::All(inner.iter().map(make_nnf_neg).collect()),
320 CfgExpr::All(inner) => CfgExpr::Any(inner.iter().map(make_nnf_neg).collect()),
322 }
323}
324
325fn flatten(expr: CfgExpr) -> CfgExpr {
327 match expr {
328 CfgExpr::All(inner) => CfgExpr::All(
329 inner
330 .iter()
331 .flat_map(|e| match e {
332 CfgExpr::All(inner) => inner.as_ref(),
333 _ => std::slice::from_ref(e),
334 })
335 .cloned()
336 .collect(),
337 ),
338 CfgExpr::Any(inner) => CfgExpr::Any(
339 inner
340 .iter()
341 .flat_map(|e| match e {
342 CfgExpr::Any(inner) => inner.as_ref(),
343 _ => std::slice::from_ref(e),
344 })
345 .cloned()
346 .collect(),
347 ),
348 _ => expr,
349 }
350}