]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/suspicious_trait_impl.rs
Auto merge of #5365 - mgr-inz-rafal:issue4983_bool_updates, r=yaahc
[rust.git] / clippy_lints / src / suspicious_trait_impl.rs
1 use crate::utils::{get_trait_def_id, span_lint, trait_ref_of_method};
2 use if_chain::if_chain;
3 use rustc_hir as hir;
4 use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_middle::hir::map::Map;
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
8
9 declare_clippy_lint! {
10     /// **What it does:** Lints for suspicious operations in impls of arithmetic operators, e.g.
11     /// subtracting elements in an Add impl.
12     ///
13     /// **Why this is bad?** This is probably a typo or copy-and-paste error and not intended.
14     ///
15     /// **Known problems:** None.
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     pub SUSPICIOUS_ARITHMETIC_IMPL,
28     correctness,
29     "suspicious use of operators in impl of arithmetic trait"
30 }
31
32 declare_clippy_lint! {
33     /// **What it does:** Lints for suspicious operations in impls of OpAssign, e.g.
34     /// subtracting elements in an AddAssign impl.
35     ///
36     /// **Why this is bad?** This is probably a typo or copy-and-paste error and not intended.
37     ///
38     /// **Known problems:** None.
39     ///
40     /// **Example:**
41     /// ```ignore
42     /// impl AddAssign for Foo {
43     ///     fn add_assign(&mut self, other: Foo) {
44     ///         *self = *self - other;
45     ///     }
46     /// }
47     /// ```
48     pub SUSPICIOUS_OP_ASSIGN_IMPL,
49     correctness,
50     "suspicious use of operators in impl of OpAssign trait"
51 }
52
53 declare_lint_pass!(SuspiciousImpl => [SUSPICIOUS_ARITHMETIC_IMPL, SUSPICIOUS_OP_ASSIGN_IMPL]);
54
55 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for SuspiciousImpl {
56     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) {
57         if let hir::ExprKind::Binary(binop, _, _) = expr.kind {
58             match binop.node {
59                 hir::BinOpKind::Eq
60                 | hir::BinOpKind::Lt
61                 | hir::BinOpKind::Le
62                 | hir::BinOpKind::Ne
63                 | hir::BinOpKind::Ge
64                 | hir::BinOpKind::Gt => return,
65                 _ => {},
66             }
67             // Check if the binary expression is part of another bi/unary expression
68             // as a child node
69             let mut parent_expr = cx.tcx.hir().get_parent_node(expr.hir_id);
70             while parent_expr != hir::CRATE_HIR_ID {
71                 if let hir::Node::Expr(e) = cx.tcx.hir().get(parent_expr) {
72                     match e.kind {
73                         hir::ExprKind::Binary(..)
74                         | hir::ExprKind::Unary(hir::UnOp::UnNot, _)
75                         | hir::ExprKind::Unary(hir::UnOp::UnNeg, _) => return,
76                         _ => {},
77                     }
78                 }
79                 parent_expr = cx.tcx.hir().get_parent_node(parent_expr);
80             }
81             // as a parent node
82             let mut visitor = BinaryExprVisitor { in_binary_expr: false };
83             walk_expr(&mut visitor, expr);
84
85             if visitor.in_binary_expr {
86                 return;
87             }
88
89             if let Some(impl_trait) = check_binop(
90                 cx,
91                 expr,
92                 binop.node,
93                 &["Add", "Sub", "Mul", "Div"],
94                 &[
95                     hir::BinOpKind::Add,
96                     hir::BinOpKind::Sub,
97                     hir::BinOpKind::Mul,
98                     hir::BinOpKind::Div,
99                 ],
100             ) {
101                 span_lint(
102                     cx,
103                     SUSPICIOUS_ARITHMETIC_IMPL,
104                     binop.span,
105                     &format!(r#"Suspicious use of binary operator in `{}` impl"#, impl_trait),
106                 );
107             }
108
109             if let Some(impl_trait) = check_binop(
110                 cx,
111                 expr,
112                 binop.node,
113                 &[
114                     "AddAssign",
115                     "SubAssign",
116                     "MulAssign",
117                     "DivAssign",
118                     "BitAndAssign",
119                     "BitOrAssign",
120                     "BitXorAssign",
121                     "RemAssign",
122                     "ShlAssign",
123                     "ShrAssign",
124                 ],
125                 &[
126                     hir::BinOpKind::Add,
127                     hir::BinOpKind::Sub,
128                     hir::BinOpKind::Mul,
129                     hir::BinOpKind::Div,
130                     hir::BinOpKind::BitAnd,
131                     hir::BinOpKind::BitOr,
132                     hir::BinOpKind::BitXor,
133                     hir::BinOpKind::Rem,
134                     hir::BinOpKind::Shl,
135                     hir::BinOpKind::Shr,
136                 ],
137             ) {
138                 span_lint(
139                     cx,
140                     SUSPICIOUS_OP_ASSIGN_IMPL,
141                     binop.span,
142                     &format!(r#"Suspicious use of binary operator in `{}` impl"#, impl_trait),
143                 );
144             }
145         }
146     }
147 }
148
149 fn check_binop(
150     cx: &LateContext<'_, '_>,
151     expr: &hir::Expr<'_>,
152     binop: hir::BinOpKind,
153     traits: &[&'static str],
154     expected_ops: &[hir::BinOpKind],
155 ) -> Option<&'static str> {
156     let mut trait_ids = vec![];
157     let [krate, module] = crate::utils::paths::OPS_MODULE;
158
159     for &t in traits {
160         let path = [krate, module, t];
161         if let Some(trait_id) = get_trait_def_id(cx, &path) {
162             trait_ids.push(trait_id);
163         } else {
164             return None;
165         }
166     }
167
168     // Get the actually implemented trait
169     let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id);
170
171     if_chain! {
172         if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn);
173         if let Some(idx) = trait_ids.iter().position(|&tid| tid == trait_ref.path.res.def_id());
174         if binop != expected_ops[idx];
175         then{
176             return Some(traits[idx])
177         }
178     }
179
180     None
181 }
182
183 struct BinaryExprVisitor {
184     in_binary_expr: bool,
185 }
186
187 impl<'a, 'tcx> Visitor<'tcx> for BinaryExprVisitor {
188     type Map = Map<'tcx>;
189
190     fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
191         match expr.kind {
192             hir::ExprKind::Binary(..)
193             | hir::ExprKind::Unary(hir::UnOp::UnNot, _)
194             | hir::ExprKind::Unary(hir::UnOp::UnNeg, _) => self.in_binary_expr = true,
195             _ => {},
196         }
197
198         walk_expr(self, expr);
199     }
200     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
201         NestedVisitorMap::None
202     }
203 }