]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/suspicious_trait_impl.rs
Auto merge of #96745 - ehuss:even-more-attribute-validation, r=cjgillot
[rust.git] / clippy_lints / src / suspicious_trait_impl.rs
1 use clippy_utils::diagnostics::span_lint;
2 use clippy_utils::{binop_traits, trait_ref_of_method, BINOP_TRAITS, OP_ASSIGN_TRAITS};
3 use if_chain::if_chain;
4 use rustc_hir as hir;
5 use rustc_hir::intravisit::{walk_expr, Visitor};
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
8
9 declare_clippy_lint! {
10     /// ### What it does
11     /// Lints for suspicious operations in impls of arithmetic operators, e.g.
12     /// subtracting elements in an Add impl.
13     ///
14     /// ### Why is this bad?
15     /// This is probably a typo or copy-and-paste error and not intended.
16     ///
17     /// ### Example
18     /// ```ignore
19     /// impl Add for Foo {
20     ///     type Output = Foo;
21     ///
22     ///     fn add(self, other: Foo) -> Foo {
23     ///         Foo(self.0 - other.0)
24     ///     }
25     /// }
26     /// ```
27     #[clippy::version = "pre 1.29.0"]
28     pub SUSPICIOUS_ARITHMETIC_IMPL,
29     suspicious,
30     "suspicious use of operators in impl of arithmetic trait"
31 }
32
33 declare_clippy_lint! {
34     /// ### What it does
35     /// Lints for suspicious operations in impls of OpAssign, e.g.
36     /// subtracting elements in an AddAssign impl.
37     ///
38     /// ### Why is this bad?
39     /// This is probably a typo or copy-and-paste error and not intended.
40     ///
41     /// ### Example
42     /// ```ignore
43     /// impl AddAssign for Foo {
44     ///     fn add_assign(&mut self, other: Foo) {
45     ///         *self = *self - other;
46     ///     }
47     /// }
48     /// ```
49     #[clippy::version = "pre 1.29.0"]
50     pub SUSPICIOUS_OP_ASSIGN_IMPL,
51     suspicious,
52     "suspicious use of operators in impl of OpAssign trait"
53 }
54
55 declare_lint_pass!(SuspiciousImpl => [SUSPICIOUS_ARITHMETIC_IMPL, SUSPICIOUS_OP_ASSIGN_IMPL]);
56
57 impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl {
58     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
59         if_chain! {
60             if let hir::ExprKind::Binary(binop, _, _) | hir::ExprKind::AssignOp(binop, ..) = expr.kind;
61             if let Some((binop_trait_lang, op_assign_trait_lang)) = binop_traits(binop.node);
62             if let Ok(binop_trait_id) = cx.tcx.lang_items().require(binop_trait_lang);
63             if let Ok(op_assign_trait_id) = cx.tcx.lang_items().require(op_assign_trait_lang);
64
65             // Check for more than one binary operation in the implemented function
66             // Linting when multiple operations are involved can result in false positives
67             let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id);
68             if let hir::Node::ImplItem(impl_item) = cx.tcx.hir().get_by_def_id(parent_fn);
69             if let hir::ImplItemKind::Fn(_, body_id) = impl_item.kind;
70             let body = cx.tcx.hir().body(body_id);
71             let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id);
72             if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn);
73             let trait_id = trait_ref.path.res.def_id();
74             if ![binop_trait_id, op_assign_trait_id].contains(&trait_id);
75             if let Some(&(_, lint)) = [
76                 (&BINOP_TRAITS, SUSPICIOUS_ARITHMETIC_IMPL),
77                 (&OP_ASSIGN_TRAITS, SUSPICIOUS_OP_ASSIGN_IMPL),
78             ]
79                 .iter()
80                 .find(|&(ts, _)| ts.iter().any(|&t| Ok(trait_id) == cx.tcx.lang_items().require(t)));
81             if count_binops(&body.value) == 1;
82             then {
83                 span_lint(
84                     cx,
85                     lint,
86                     binop.span,
87                     &format!("suspicious use of `{}` in `{}` impl", binop.node.as_str(), cx.tcx.item_name(trait_id)),
88                 );
89             }
90         }
91     }
92 }
93
94 fn count_binops(expr: &hir::Expr<'_>) -> u32 {
95     let mut visitor = BinaryExprVisitor::default();
96     visitor.visit_expr(expr);
97     visitor.nb_binops
98 }
99
100 #[derive(Default)]
101 struct BinaryExprVisitor {
102     nb_binops: u32,
103 }
104
105 impl<'tcx> Visitor<'tcx> for BinaryExprVisitor {
106     fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
107         match expr.kind {
108             hir::ExprKind::Binary(..)
109             | hir::ExprKind::Unary(hir::UnOp::Not | hir::UnOp::Neg, _)
110             | hir::ExprKind::AssignOp(..) => self.nb_binops += 1,
111             _ => {},
112         }
113
114         walk_expr(self, expr);
115     }
116 }