]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/booleans.rs
Use span_suggestion_with_applicability instead of span_suggestion
[rust.git] / clippy_lints / src / booleans.rs
index 7c7dbe8088330c316eb9ba83e18216512b9c5b6b..85f6eeb19ef9f22294710dd27dfd218ed3aa6f83 100644 (file)
@@ -1,10 +1,12 @@
-use rustc::lint::{LintArray, LateLintPass, LateContext, LintPass};
-use rustc::hir::*;
-use rustc::hir::intravisit::*;
-use syntax::ast::{LitKind, DUMMY_NODE_ID, NodeId};
-use syntax::codemap::{DUMMY_SP, dummy_spanned, Span};
-use syntax::util::ThinVec;
-use utils::{span_lint_and_then, in_macro, snippet_opt, SpanlessEq};
+use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
+use crate::rustc::{declare_tool_lint, lint_array};
+use crate::rustc::hir::*;
+use crate::rustc::hir::intravisit::*;
+use crate::syntax::ast::{LitKind, NodeId, DUMMY_NODE_ID};
+use crate::syntax::source_map::{dummy_spanned, Span, DUMMY_SP};
+use crate::rustc_data_structures::thin_vec::ThinVec;
+use crate::utils::{in_macro, paths, match_type, snippet_opt, span_lint_and_then, SpanlessEq, get_trait_def_id, implements_trait};
+use crate::rustc_errors::Applicability;
 
 /// **What it does:** Checks for boolean expressions that can be written more
 /// concisely.
@@ -20,9 +22,9 @@
 /// if a && true  // should be: if a
 /// if !(a == b)  // should be: if a != b
 /// ```
-declare_lint! {
+declare_clippy_lint! {
     pub NONMINIMAL_BOOL,
-    Allow,
+    complexity,
     "boolean expressions that can be written more concisely"
 }
 
 /// if a && b || a { ... }
 /// ```
 /// The `b` is unnecessary, the expression is equivalent to `if a`.
-declare_lint! {
+declare_clippy_lint! {
     pub LOGIC_BUG,
-    Warn,
+    correctness,
     "boolean expressions that contain terminals which can be eliminated"
 }
 
+// For each pairs, both orders are considered.
+const METHODS_WITH_NEGATION: [(&str, &str); 2] = [
+    ("is_some", "is_none"),
+    ("is_err", "is_ok"),
+];
+
 #[derive(Copy, Clone)]
 pub struct NonminimalBool;
 
@@ -63,7 +71,7 @@ fn check_fn(
         _: Span,
         _: NodeId,
     ) {
-        NonminimalBoolVisitor { cx: cx }.visit_body(body)
+        NonminimalBoolVisitor { cx }.visit_body(body)
     }
 }
 
@@ -78,9 +86,9 @@ struct Hir2Qmm<'a, 'tcx: 'a, 'v> {
 }
 
 impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
-    fn extract(&mut self, op: BinOp_, a: &[&'v Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
+    fn extract(&mut self, op: BinOpKind, a: &[&'v Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
         for a in a {
-            if let ExprBinary(binop, ref lhs, ref rhs) = a.node {
+            if let ExprKind::Binary(binop, ref lhs, ref rhs) = a.node {
                 if binop.node == op {
                     v = self.extract(op, &[lhs, rhs], v)?;
                     continue;
@@ -95,166 +103,188 @@ fn run(&mut self, e: &'v Expr) -> Result<Bool, String> {
         // prevent folding of `cfg!` macros and the like
         if !in_macro(e.span) {
             match e.node {
-                ExprUnary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
-                ExprBinary(binop, ref lhs, ref rhs) => {
-                    match binop.node {
-                        BiOr => return Ok(Bool::Or(self.extract(BiOr, &[lhs, rhs], Vec::new())?)),
-                        BiAnd => return Ok(Bool::And(self.extract(BiAnd, &[lhs, rhs], Vec::new())?)),
-                        _ => (),
-                    }
+                ExprKind::Unary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
+                ExprKind::Binary(binop, ref lhs, ref rhs) => match binop.node {
+                    BinOpKind::Or => return Ok(Bool::Or(self.extract(BinOpKind::Or, &[lhs, rhs], Vec::new())?)),
+                    BinOpKind::And => return Ok(Bool::And(self.extract(BinOpKind::And, &[lhs, rhs], Vec::new())?)),
+                    _ => (),
                 },
-                ExprLit(ref lit) => {
-                    match lit.node {
-                        LitKind::Bool(true) => return Ok(Bool::True),
-                        LitKind::Bool(false) => return Ok(Bool::False),
-                        _ => (),
-                    }
+                ExprKind::Lit(ref lit) => match lit.node {
+                    LitKind::Bool(true) => return Ok(Bool::True),
+                    LitKind::Bool(false) => return Ok(Bool::False),
+                    _ => (),
                 },
                 _ => (),
             }
         }
         for (n, expr) in self.terminals.iter().enumerate() {
             if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
-                #[allow(cast_possible_truncation)] return Ok(Bool::Term(n as u8));
+                #[allow(clippy::cast_possible_truncation)]
+                return Ok(Bool::Term(n as u8));
             }
             let negated = match e.node {
-                ExprBinary(binop, ref lhs, ref rhs) => {
+                ExprKind::Binary(binop, ref lhs, ref rhs) => {
+
+                    if !implements_ord(self.cx, lhs) {
+                        continue;
+                    }
+
                     let mk_expr = |op| {
                         Expr {
                             id: DUMMY_NODE_ID,
                             hir_id: DUMMY_HIR_ID,
                             span: DUMMY_SP,
                             attrs: ThinVec::new(),
-                            node: ExprBinary(dummy_spanned(op), lhs.clone(), rhs.clone()),
+                            node: ExprKind::Binary(dummy_spanned(op), lhs.clone(), rhs.clone()),
                         }
                     };
                     match binop.node {
-                        BiEq => mk_expr(BiNe),
-                        BiNe => mk_expr(BiEq),
-                        BiGt => mk_expr(BiLe),
-                        BiGe => mk_expr(BiLt),
-                        BiLt => mk_expr(BiGe),
-                        BiLe => mk_expr(BiGt),
+                        BinOpKind::Eq => mk_expr(BinOpKind::Ne),
+                        BinOpKind::Ne => mk_expr(BinOpKind::Eq),
+                        BinOpKind::Gt => mk_expr(BinOpKind::Le),
+                        BinOpKind::Ge => mk_expr(BinOpKind::Lt),
+                        BinOpKind::Lt => mk_expr(BinOpKind::Ge),
+                        BinOpKind::Le => mk_expr(BinOpKind::Gt),
                         _ => continue,
                     }
                 },
                 _ => continue,
             };
             if SpanlessEq::new(self.cx).ignore_fn().eq_expr(&negated, expr) {
-                #[allow(cast_possible_truncation)] return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
+                #[allow(clippy::cast_possible_truncation)]
+                return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
             }
         }
         let n = self.terminals.len();
         self.terminals.push(e);
         if n < 32 {
-            #[allow(cast_possible_truncation)] Ok(Bool::Term(n as u8))
+            #[allow(clippy::cast_possible_truncation)]
+            Ok(Bool::Term(n as u8))
         } else {
             Err("too many literals".to_owned())
         }
     }
 }
 
-fn suggest(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr]) -> String {
-    fn recurse(brackets: bool, cx: &LateContext, suggestion: &Bool, terminals: &[&Expr], mut s: String) -> String {
+struct SuggestContext<'a, 'tcx: 'a, 'v> {
+    terminals: &'v [&'v Expr],
+    cx: &'a LateContext<'a, 'tcx>,
+    output: String,
+    simplified: bool,
+}
+
+impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> {
+    fn snip(&self, e: &Expr) -> Option<String> {
+        snippet_opt(self.cx, e.span)
+    }
+
+    fn simplify_not(&self, expr: &Expr) -> Option<String> {
+        match expr.node {
+            ExprKind::Binary(binop, ref lhs, ref rhs) => {
+
+                if !implements_ord(self.cx, lhs) {
+                    return None;
+                }
+
+                match binop.node {
+                    BinOpKind::Eq => Some(" != "),
+                    BinOpKind::Ne => Some(" == "),
+                    BinOpKind::Lt => Some(" >= "),
+                    BinOpKind::Gt => Some(" <= "),
+                    BinOpKind::Le => Some(" > "),
+                    BinOpKind::Ge => Some(" < "),
+                    _ => None,
+                }.and_then(|op| Some(format!("{}{}{}", self.snip(lhs)?, op, self.snip(rhs)?)))
+            },
+            ExprKind::MethodCall(ref path, _, ref args) if args.len() == 1 => {
+                let type_of_receiver = self.cx.tables.expr_ty(&args[0]);
+                if !match_type(self.cx, type_of_receiver, &paths::OPTION) &&
+                    !match_type(self.cx, type_of_receiver, &paths::RESULT) {
+                        return None;
+                }
+                METHODS_WITH_NEGATION
+                    .iter().cloned()
+                    .flat_map(|(a, b)| vec![(a, b), (b, a)])
+                    .find(|&(a, _)| a == path.ident.as_str())
+                    .and_then(|(_, neg_method)| Some(format!("{}.{}()", self.snip(&args[0])?, neg_method)))
+            },
+            _ => None,
+        }
+    }
+
+    fn recurse(&mut self, suggestion: &Bool) -> Option<()> {
         use quine_mc_cluskey::Bool::*;
-        let snip = |e: &Expr| snippet_opt(cx, e.span).expect("don't try to improve booleans created by macros");
         match *suggestion {
             True => {
-                s.push_str("true");
-                s
+                self.output.push_str("true");
             },
             False => {
-                s.push_str("false");
-                s
+                self.output.push_str("false");
             },
-            Not(ref inner) => {
-                match **inner {
-                    And(_) | Or(_) => {
-                        s.push('!');
-                        recurse(true, cx, inner, terminals, s)
-                    },
-                    Term(n) => {
-                        if let ExprBinary(binop, ref lhs, ref rhs) = terminals[n as usize].node {
-                            let op = match binop.node {
-                                BiEq => " != ",
-                                BiNe => " == ",
-                                BiLt => " >= ",
-                                BiGt => " <= ",
-                                BiLe => " > ",
-                                BiGe => " < ",
-                                _ => {
-                                    s.push('!');
-                                    return recurse(true, cx, inner, terminals, s);
-                                },
-                            };
-                            s.push_str(&snip(lhs));
-                            s.push_str(op);
-                            s.push_str(&snip(rhs));
-                            s
-                        } else {
-                            s.push('!');
-                            recurse(false, cx, inner, terminals, s)
-                        }
-                    },
-                    _ => {
-                        s.push('!');
-                        recurse(false, cx, inner, terminals, s)
-                    },
-                }
+            Not(ref inner) => match **inner {
+                And(_) | Or(_) => {
+                    self.output.push('!');
+                    self.output.push('(');
+                    self.recurse(inner);
+                    self.output.push(')');
+                },
+                Term(n) => {
+                    let terminal = self.terminals[n as usize];
+                    if let Some(str) = self.simplify_not(terminal) {
+                        self.simplified = true;
+                        self.output.push_str(&str)
+                    } else {
+                        self.output.push('!');
+                        let snip = self.snip(terminal)?;
+                        self.output.push_str(&snip);
+                    }
+                },
+                True | False | Not(_) => {
+                    self.output.push('!');
+                    self.recurse(inner)?;
+                },
             },
             And(ref v) => {
-                if brackets {
-                    s.push('(');
-                }
-                if let Or(_) = v[0] {
-                    s = recurse(true, cx, &v[0], terminals, s);
-                } else {
-                    s = recurse(false, cx, &v[0], terminals, s);
-                }
-                for inner in &v[1..] {
-                    s.push_str(" && ");
+                for (index, inner) in v.iter().enumerate() {
+                    if index > 0 {
+                        self.output.push_str(" && ");
+                    }
                     if let Or(_) = *inner {
-                        s = recurse(true, cx, inner, terminals, s);
+                        self.output.push('(');
+                        self.recurse(inner);
+                        self.output.push(')');
                     } else {
-                        s = recurse(false, cx, inner, terminals, s);
+                        self.recurse(inner);
                     }
                 }
-                if brackets {
-                    s.push(')');
-                }
-                s
             },
             Or(ref v) => {
-                if brackets {
-                    s.push('(');
-                }
-                s = recurse(false, cx, &v[0], terminals, s);
-                for inner in &v[1..] {
-                    s.push_str(" || ");
-                    s = recurse(false, cx, inner, terminals, s);
-                }
-                if brackets {
-                    s.push(')');
+                for (index, inner) in v.iter().enumerate() {
+                    if index > 0 {
+                        self.output.push_str(" || ");
+                    }
+                    self.recurse(inner);
                 }
-                s
             },
             Term(n) => {
-                if brackets {
-                    if let ExprBinary(..) = terminals[n as usize].node {
-                        s.push('(');
-                    }
-                }
-                s.push_str(&snip(terminals[n as usize]));
-                if brackets {
-                    if let ExprBinary(..) = terminals[n as usize].node {
-                        s.push(')');
-                    }
-                }
-                s
+                let snip = self.snip(self.terminals[n as usize])?;
+                self.output.push_str(&snip);
             },
         }
+        Some(())
     }
-    recurse(false, cx, suggestion, terminals, String::new())
+}
+
+// The boolean part of the return indicates whether some simplifications have been applied.
+fn suggest(cx: &LateContext<'_, '_>, suggestion: &Bool, terminals: &[&Expr]) -> (String, bool) {
+    let mut suggest_context = SuggestContext {
+        terminals,
+        cx,
+        output: String::new(),
+        simplified: false,
+    };
+    suggest_context.recurse(suggestion);
+    (suggest_context.output, suggest_context.simplified)
 }
 
 fn simple_negate(b: Bool) -> Bool {
@@ -319,7 +349,6 @@ fn bool_expr(&self, e: &Expr) {
             cx: self.cx,
         };
         if let Ok(expr) = h2q.run(e) {
-
             if h2q.terminals.len() > 8 {
                 // QMC has exponentially slow behavior as the number of terminals increases
                 // 8 is reasonable, it takes approximately 0.2 seconds.
@@ -360,12 +389,13 @@ fn bool_expr(&self, e: &Expr) {
                                 db.span_help(
                                     h2q.terminals[i].span,
                                     "this expression can be optimized out by applying boolean operations to the \
-                                          outer expression",
+                                     outer expression",
                                 );
-                                db.span_suggestion(
+                                db.span_suggestion_with_applicability(
                                     e.span,
                                     "it would look like the following",
-                                    suggest(self.cx, suggestion, &h2q.terminals),
+                                    suggest(self.cx, suggestion, &h2q.terminals).0,
+                                    Applicability::Unspecified,
                                 );
                             },
                         );
@@ -374,30 +404,34 @@ fn bool_expr(&self, e: &Expr) {
                     }
                     // 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);
+                    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 improvement {
                     improvements.push(suggestion);
                 }
             }
-            if !improvements.is_empty() {
+            let nonminimal_bool_lint = |suggestions| {
                 span_lint_and_then(
                     self.cx,
                     NONMINIMAL_BOOL,
                     e.span,
                     "this boolean expression can be simplified",
-                    |db| {
-                        db.span_suggestions(
-                            e.span,
-                            "try",
-                            improvements
-                                .into_iter()
-                                .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals))
-                                .collect(),
-                        );
-                    },
+                    |db| { db.span_suggestions(e.span, "try", suggestions); },
+                );
+            };
+            if improvements.is_empty() {
+                let suggest = suggest(self.cx, &expr, &h2q.terminals);
+                if suggest.1 {
+                    nonminimal_bool_lint(vec![suggest.0])
+                }
+            } else {
+                nonminimal_bool_lint(
+                    improvements
+                        .into_iter()
+                        .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals).0)
+                        .collect()
                 );
             }
         }
@@ -410,13 +444,11 @@ fn visit_expr(&mut self, e: &'tcx Expr) {
             return;
         }
         match e.node {
-            ExprBinary(binop, _, _) if binop.node == BiOr || binop.node == BiAnd => self.bool_expr(e),
-            ExprUnary(UnNot, ref inner) => {
-                if self.cx.tables.node_types()[inner.hir_id].is_bool() {
-                    self.bool_expr(e);
-                } else {
-                    walk_expr(self, e);
-                }
+            ExprKind::Binary(binop, _, _) if binop.node == BinOpKind::Or || binop.node == BinOpKind::And => self.bool_expr(e),
+            ExprKind::Unary(UnNot, ref inner) => if self.cx.tables.node_types()[inner.hir_id].is_bool() {
+                self.bool_expr(e);
+            } else {
+                walk_expr(self, e);
             },
             _ => walk_expr(self, e),
         }
@@ -425,3 +457,10 @@ fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
         NestedVisitorMap::None
     }
 }
+
+
+fn implements_ord<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, expr: &Expr) -> bool {
+    let ty = cx.tables.expr_ty(expr);
+    get_trait_def_id(cx, &paths::ORD)
+        .map_or(false, |id| implements_trait(cx, ty, id, &[]))
+}