]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/assign_ops.rs
rustup https://github.com/rust-lang/rust/pull/68944
[rust.git] / clippy_lints / src / assign_ops.rs
index ad77ee3a3fa84a997547be376c74bbbff1b7441c..b6fb6fdd2cc04aa95b6c87299f2b08139b4942be 100644 (file)
-use crate::utils::{get_trait_def_id, implements_trait, snippet_opt, span_lint_and_then, SpanlessEq};
+use crate::utils::{
+    get_trait_def_id, implements_trait, snippet_opt, span_lint_and_then, trait_ref_of_method, SpanlessEq,
+};
 use crate::utils::{higher, sugg};
 use if_chain::if_chain;
-use rustc::hir;
-use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
-use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
-use rustc::{declare_tool_lint, lint_array};
+use rustc::hir::map::Map;
 use rustc_errors::Applicability;
-use syntax::ast;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
 
-/// **What it does:** Checks for `a = a op b` or `a = b commutative_op a`
-/// patterns.
-///
-/// **Why is this bad?** These can be written as the shorter `a op= b`.
-///
-/// **Known problems:** While forbidden by the spec, `OpAssign` traits may have
-/// implementations that differ from the regular `Op` impl.
-///
-/// **Example:**
-/// ```rust
-/// let mut a = 5;
-/// ...
-/// a = a + b;
-/// ```
 declare_clippy_lint! {
+    /// **What it does:** Checks for `a = a op b` or `a = b commutative_op a`
+    /// patterns.
+    ///
+    /// **Why is this bad?** These can be written as the shorter `a op= b`.
+    ///
+    /// **Known problems:** While forbidden by the spec, `OpAssign` traits may have
+    /// implementations that differ from the regular `Op` impl.
+    ///
+    /// **Example:**
+    /// ```rust
+    /// let mut a = 5;
+    /// let b = 0;
+    /// // ...
+    /// a = a + b;
+    /// ```
     pub ASSIGN_OP_PATTERN,
     style,
     "assigning the result of an operation on a variable to that same variable"
 }
 
-/// **What it does:** Checks for `a op= a op b` or `a op= b op a` patterns.
-///
-/// **Why is this bad?** Most likely these are bugs where one meant to write `a
-/// op= b`.
-///
-/// **Known problems:** Clippy cannot know for sure if `a op= a op b` should have
-/// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore it suggests both.
-/// If `a op= a op b` is really the correct behaviour it should be
-/// written as `a = a op a op b` as it's less confusing.
-///
-/// **Example:**
-/// ```rust
-/// let mut a = 5;
-/// ...
-/// a += a + b;
-/// ```
 declare_clippy_lint! {
+    /// **What it does:** Checks for `a op= a op b` or `a op= b op a` patterns.
+    ///
+    /// **Why is this bad?** Most likely these are bugs where one meant to write `a
+    /// op= b`.
+    ///
+    /// **Known problems:** Clippy cannot know for sure if `a op= a op b` should have
+    /// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both.
+    /// If `a op= a op b` is really the correct behaviour it should be
+    /// written as `a = a op a op b` as it's less confusing.
+    ///
+    /// **Example:**
+    /// ```rust
+    /// let mut a = 5;
+    /// let b = 2;
+    /// // ...
+    /// a += a + b;
+    /// ```
     pub MISREFACTORED_ASSIGN_OP,
     complexity,
     "having a variable on both sides of an assign op"
 }
 
-#[derive(Copy, Clone, Default)]
-pub struct AssignOps;
-
-impl LintPass for AssignOps {
-    fn get_lints(&self) -> LintArray {
-        lint_array!(ASSIGN_OP_PATTERN, MISREFACTORED_ASSIGN_OP)
-    }
-}
+declare_lint_pass!(AssignOps => [ASSIGN_OP_PATTERN, MISREFACTORED_ASSIGN_OP]);
 
 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AssignOps {
-    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr) {
-        match &expr.node {
+    #[allow(clippy::too_many_lines)]
+    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) {
+        match &expr.kind {
             hir::ExprKind::AssignOp(op, lhs, rhs) => {
-                if let hir::ExprKind::Binary(binop, l, r) = &rhs.node {
-                    if op.node == binop.node {
-                        let lint = |assignee: &hir::Expr, rhs_other: &hir::Expr| {
-                            span_lint_and_then(
-                                cx,
-                                MISREFACTORED_ASSIGN_OP,
-                                expr.span,
-                                "variable appears on both sides of an assignment operation",
-                                |db| {
-                                    if let (Some(snip_a), Some(snip_r)) =
-                                        (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs_other.span))
-                                    {
-                                        let a = &sugg::Sugg::hir(cx, assignee, "..");
-                                        let r = &sugg::Sugg::hir(cx, rhs, "..");
-                                        let long =
-                                            format!("{} = {}", snip_a, sugg::make_binop(higher::binop(op.node), a, r));
-                                        db.span_suggestion_with_applicability(
-                                            expr.span,
-                                            &format!(
-                                                "Did you mean {} = {} {} {} or {}? Consider replacing it with",
-                                                snip_a,
-                                                snip_a,
-                                                op.node.as_str(),
-                                                snip_r,
-                                                long
-                                            ),
-                                            format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
-                                            Applicability::MachineApplicable,
-                                        );
-                                        db.span_suggestion_with_applicability(
-                                            expr.span,
-                                            "or",
-                                            long,
-                                            Applicability::MachineApplicable, // snippet
-                                        );
-                                    }
-                                },
-                            );
-                        };
-                        // lhs op= l op r
-                        if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, l) {
-                            lint(lhs, r);
-                        }
-                        // lhs op= l commutative_op r
-                        if is_commutative(op.node) && SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, r) {
-                            lint(lhs, l);
-                        }
+                if let hir::ExprKind::Binary(binop, l, r) = &rhs.kind {
+                    if op.node != binop.node {
+                        return;
+                    }
+                    // lhs op= l op r
+                    if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, l) {
+                        lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, r);
+                    }
+                    // lhs op= l commutative_op r
+                    if is_commutative(op.node) && SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, r) {
+                        lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, l);
                     }
                 }
             },
-            hir::ExprKind::Assign(assignee, e) => {
-                if let hir::ExprKind::Binary(op, l, r) = &e.node {
-                    #[allow(clippy::cyclomatic_complexity)]
-                    let lint = |assignee: &hir::Expr, rhs: &hir::Expr| {
+            hir::ExprKind::Assign(assignee, e, _) => {
+                if let hir::ExprKind::Binary(op, l, r) = &e.kind {
+                    #[allow(clippy::cognitive_complexity)]
+                    let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| {
                         let ty = cx.tables.expr_ty(assignee);
                         let rty = cx.tables.expr_ty(rhs);
                         macro_rules! ops {
@@ -128,22 +90,17 @@ macro_rules! ops {
                                 match $op {
                                     $(hir::BinOpKind::$trait_name => {
                                         let [krate, module] = crate::utils::paths::OPS_MODULE;
-                                        let path = [krate, module, concat!(stringify!($trait_name), "Assign")];
+                                        let path: [&str; 3] = [krate, module, concat!(stringify!($trait_name), "Assign")];
                                         let trait_id = if let Some(trait_id) = get_trait_def_id($cx, &path) {
                                             trait_id
                                         } else {
                                             return; // useless if the trait doesn't exist
                                         };
                                         // check that we are not inside an `impl AssignOp` of this exact operation
-                                        let parent_fn = cx.tcx.hir().get_parent(e.id);
-                                        let parent_impl = cx.tcx.hir().get_parent(parent_fn);
-                                        // the crate node is the only one that is not in the map
+                                        let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id);
                                         if_chain! {
-                                            if parent_impl != ast::CRATE_NODE_ID;
-                                            if let hir::Node::Item(item) = cx.tcx.hir().get(parent_impl);
-                                            if let hir::ItemKind::Impl(_, _, _, _, Some(trait_ref), _, _) =
-                                                &item.node;
-                                            if trait_ref.path.def.def_id() == trait_id;
+                                            if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn);
+                                            if trait_ref.path.res.def_id() == trait_id;
                                             then { return; }
                                         }
                                         implements_trait($cx, $ty, trait_id, &[$rty])
@@ -179,7 +136,7 @@ macro_rules! ops {
                                     if let (Some(snip_a), Some(snip_r)) =
                                         (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span))
                                     {
-                                        db.span_suggestion_with_applicability(
+                                        db.span_suggestion(
                                             expr.span,
                                             "replace it with",
                                             format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
@@ -230,29 +187,76 @@ macro_rules! ops {
     }
 }
 
+fn lint_misrefactored_assign_op(
+    cx: &LateContext<'_, '_>,
+    expr: &hir::Expr<'_>,
+    op: hir::BinOp,
+    rhs: &hir::Expr<'_>,
+    assignee: &hir::Expr<'_>,
+    rhs_other: &hir::Expr<'_>,
+) {
+    span_lint_and_then(
+        cx,
+        MISREFACTORED_ASSIGN_OP,
+        expr.span,
+        "variable appears on both sides of an assignment operation",
+        |db| {
+            if let (Some(snip_a), Some(snip_r)) = (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs_other.span)) {
+                let a = &sugg::Sugg::hir(cx, assignee, "..");
+                let r = &sugg::Sugg::hir(cx, rhs, "..");
+                let long = format!("{} = {}", snip_a, sugg::make_binop(higher::binop(op.node), a, r));
+                db.span_suggestion(
+                    expr.span,
+                    &format!(
+                        "Did you mean `{} = {} {} {}` or `{}`? Consider replacing it with",
+                        snip_a,
+                        snip_a,
+                        op.node.as_str(),
+                        snip_r,
+                        long
+                    ),
+                    format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
+                    Applicability::MaybeIncorrect,
+                );
+                db.span_suggestion(
+                    expr.span,
+                    "or",
+                    long,
+                    Applicability::MaybeIncorrect, // snippet
+                );
+            }
+        },
+    );
+}
+
+#[must_use]
 fn is_commutative(op: hir::BinOpKind) -> bool {
-    use rustc::hir::BinOpKind::*;
+    use rustc_hir::BinOpKind::{
+        Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
+    };
     match op {
         Add | Mul | And | Or | BitXor | BitAnd | BitOr | Eq | Ne => true,
         Sub | Div | Rem | Shl | Shr | Lt | Le | Ge | Gt => false,
     }
 }
 
-struct ExprVisitor<'a, 'tcx: 'a> {
-    assignee: &'a hir::Expr,
+struct ExprVisitor<'a, 'tcx> {
+    assignee: &'a hir::Expr<'a>,
     counter: u8,
     cx: &'a LateContext<'a, 'tcx>,
 }
 
-impl<'a, 'tcx: 'a> Visitor<'tcx> for ExprVisitor<'a, 'tcx> {
-    fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
+impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> {
+    type Map = Map<'tcx>;
+
+    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
         if SpanlessEq::new(self.cx).ignore_fn().eq_expr(self.assignee, expr) {
             self.counter += 1;
         }
 
         walk_expr(self, expr);
     }
-    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
+    fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
         NestedVisitorMap::None
     }
 }