From dd6bee3b3f653d01415b9be3245654b036390718 Mon Sep 17 00:00:00 2001
From: Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>
Date: Thu, 24 Mar 2016 15:37:17 +0100
Subject: [PATCH] collect stats on bool ops and negations in an expression

---
 src/booleans.rs                | 89 ++++++++++++++++++++--------------
 tests/compile-fail/booleans.rs | 10 +++-
 2 files changed, 61 insertions(+), 38 deletions(-)

diff --git a/src/booleans.rs b/src/booleans.rs
index ea05d65ac9c..9da594424ff 100644
--- a/src/booleans.rs
+++ b/src/booleans.rs
@@ -180,19 +180,35 @@ fn simple_negate(b: Bool) -> Bool {
     }
 }
 
-fn terminal_stats(b: &Bool) -> [usize; 32] {
-    fn recurse(b: &Bool, stats: &mut [usize; 32]) {
+#[derive(Default)]
+struct Stats {
+    terminals: [usize; 32],
+    negations: usize,
+    ops: usize,
+}
+
+fn terminal_stats(b: &Bool) -> Stats {
+    fn recurse(b: &Bool, stats: &mut Stats) {
         match *b {
-            True | False => {},
-            Not(ref inner) => recurse(inner, stats),
-            And(ref v) | Or(ref v) => for inner in v {
-                recurse(inner, stats)
+            True | False => stats.ops += 1,
+            Not(ref inner) => {
+                match **inner {
+                    And(_) | Or(_) => stats.ops += 1, // brackets are also operations
+                    _ => stats.negations += 1,
+                }
+                recurse(inner, stats);
             },
-            Term(n) => stats[n as usize] += 1,
+            And(ref v) | Or(ref v) => {
+                stats.ops += v.len() - 1;
+                for inner in v {
+                    recurse(inner, stats);
+                }
+            },
+            Term(n) => stats.terminals[n as usize] += 1,
         }
     }
     use quine_mc_cluskey::Bool::*;
-    let mut stats = [0; 32];
+    let mut stats = Stats::default();
     recurse(b, &mut stats);
     stats
 }
@@ -217,40 +233,39 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
                 }
                 simplified.push(simple_negated);
             }
-            if !simplified.iter().any(|s| *s == expr) {
-                let mut improvements = Vec::new();
-                'simplified: for suggestion in &simplified {
-                    let simplified_stats = terminal_stats(&suggestion);
-                    let mut improvement = false;
-                    for i in 0..32 {
-                        // ignore any "simplifications" that end up requiring a terminal more often than in the original expression
-                        if stats[i] < simplified_stats[i] {
-                            continue 'simplified;
-                        }
-                        // if the number of occurrences of a terminal doesn't increase, this expression is a candidate for improvement
-                        improvement = true;
-                        if stats[i] != 0 && simplified_stats[i] == 0 {
-                            span_lint_and_then(self.0, LOGIC_BUG, e.span, "this boolean expression contains a logic bug", |db| {
-                                db.span_help(h2q.terminals[i].span, "this expression can be optimized out by applying boolean operations to the outer expression");
-                                db.span_suggestion(e.span, "it would look like the following", suggest(self.0, suggestion, &h2q.terminals));
-                            });
-                            // don't also lint `NONMINIMAL_BOOL`
-                            improvements.clear();
-                            break 'simplified;
-                        }
+            let mut improvements = Vec::new();
+            'simplified: for suggestion in &simplified {
+                let simplified_stats = terminal_stats(&suggestion);
+                let mut improvement = false;
+                for i in 0..32 {
+                    // ignore any "simplifications" that end up requiring a terminal more often than in the original expression
+                    if stats.terminals[i] < simplified_stats.terminals[i] {
+                        continue 'simplified;
                     }
-                    if improvement {
-                        improvements.push(suggestion);
+                    if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
+                        span_lint_and_then(self.0, LOGIC_BUG, e.span, "this boolean expression contains a logic bug", |db| {
+                            db.span_help(h2q.terminals[i].span, "this expression can be optimized out by applying boolean operations to the outer expression");
+                            db.span_suggestion(e.span, "it would look like the following", suggest(self.0, suggestion, &h2q.terminals));
+                        });
+                        // don't also lint `NONMINIMAL_BOOL`
+                        return;
                     }
+                    // if the number of occurrences of a terminal decreases or any of the stats decreases while none increases
+                    improvement = (stats.terminals[i] > simplified_stats.terminals[i]) ||
+                        (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops) ||
+                        (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
                 }
-                if !improvements.is_empty() {
-                    span_lint_and_then(self.0, NONMINIMAL_BOOL, e.span, "this boolean expression can be simplified", |db| {
-                        for suggestion in &improvements {
-                            db.span_suggestion(e.span, "try", suggest(self.0, suggestion, &h2q.terminals));
-                        }
-                    });
+                if improvement {
+                    improvements.push(suggestion);
                 }
             }
+            if !improvements.is_empty() {
+                span_lint_and_then(self.0, NONMINIMAL_BOOL, e.span, "this boolean expression can be simplified", |db| {
+                    for suggestion in &improvements {
+                        db.span_suggestion(e.span, "try", suggest(self.0, suggestion, &h2q.terminals));
+                    }
+                });
+            }
         }
     }
 }
diff --git a/tests/compile-fail/booleans.rs b/tests/compile-fail/booleans.rs
index 4630a4635b4..5d39c44e7bf 100644
--- a/tests/compile-fail/booleans.rs
+++ b/tests/compile-fail/booleans.rs
@@ -2,11 +2,13 @@
 #![plugin(clippy)]
 #![deny(nonminimal_bool, logic_bug)]
 
-#[allow(unused)]
+#[allow(unused, many_single_char_names)]
 fn main() {
     let a: bool = unimplemented!();
     let b: bool = unimplemented!();
     let c: bool = unimplemented!();
+    let d: bool = unimplemented!();
+    let e: bool = unimplemented!();
     let _ = a && b || a; //~ ERROR this boolean expression contains a logic bug
     //|~ HELP for further information visit
     //|~ HELP this expression can be optimized out
@@ -36,5 +38,11 @@ fn main() {
     // don't lint on cfgs
     let _ = cfg!(you_shall_not_not_pass) && a;
 
+    let _ = a || !b || !c || !d || !e;
+
     let _ = !(a && b || c);
+
+    let _ = !(!a && b); //~ ERROR this boolean expression can be simplified
+    //|~ HELP for further information visit
+    //|~ SUGGESTION let _ = !b || a;
 }