]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/suspicious_trait_impl.rs
Auto merge of #5437 - rabisg0:should-impl-trait, r=flip1995
[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, _, _) | hir::ExprKind::AssignOp(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             // or operator assignment 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, _)
76                         | hir::ExprKind::AssignOp(..) => return,
77                         _ => {},
78                     }
79                 }
80                 parent_expr = cx.tcx.hir().get_parent_node(parent_expr);
81             }
82             // as a parent node
83             let mut visitor = BinaryExprVisitor { in_binary_expr: false };
84             walk_expr(&mut visitor, expr);
85
86             if visitor.in_binary_expr {
87                 return;
88             }
89
90             if let Some(impl_trait) = check_binop(
91                 cx,
92                 expr,
93                 binop.node,
94                 &["Add", "Sub", "Mul", "Div"],
95                 &[
96                     hir::BinOpKind::Add,
97                     hir::BinOpKind::Sub,
98                     hir::BinOpKind::Mul,
99                     hir::BinOpKind::Div,
100                 ],
101             ) {
102                 span_lint(
103                     cx,
104                     SUSPICIOUS_ARITHMETIC_IMPL,
105                     binop.span,
106                     &format!(r#"Suspicious use of binary operator in `{}` impl"#, impl_trait),
107                 );
108             }
109
110             if let Some(impl_trait) = check_binop(
111                 cx,
112                 expr,
113                 binop.node,
114                 &[
115                     "AddAssign",
116                     "SubAssign",
117                     "MulAssign",
118                     "DivAssign",
119                     "BitAndAssign",
120                     "BitOrAssign",
121                     "BitXorAssign",
122                     "RemAssign",
123                     "ShlAssign",
124                     "ShrAssign",
125                 ],
126                 &[
127                     hir::BinOpKind::Add,
128                     hir::BinOpKind::Sub,
129                     hir::BinOpKind::Mul,
130                     hir::BinOpKind::Div,
131                     hir::BinOpKind::BitAnd,
132                     hir::BinOpKind::BitOr,
133                     hir::BinOpKind::BitXor,
134                     hir::BinOpKind::Rem,
135                     hir::BinOpKind::Shl,
136                     hir::BinOpKind::Shr,
137                 ],
138             ) {
139                 span_lint(
140                     cx,
141                     SUSPICIOUS_OP_ASSIGN_IMPL,
142                     binop.span,
143                     &format!(r#"Suspicious use of binary operator in `{}` impl"#, impl_trait),
144                 );
145             }
146         }
147     }
148 }
149
150 fn check_binop(
151     cx: &LateContext<'_, '_>,
152     expr: &hir::Expr<'_>,
153     binop: hir::BinOpKind,
154     traits: &[&'static str],
155     expected_ops: &[hir::BinOpKind],
156 ) -> Option<&'static str> {
157     let mut trait_ids = vec![];
158     let [krate, module] = crate::utils::paths::OPS_MODULE;
159
160     for &t in traits {
161         let path = [krate, module, t];
162         if let Some(trait_id) = get_trait_def_id(cx, &path) {
163             trait_ids.push(trait_id);
164         } else {
165             return None;
166         }
167     }
168
169     // Get the actually implemented trait
170     let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id);
171
172     if_chain! {
173         if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn);
174         if let Some(idx) = trait_ids.iter().position(|&tid| tid == trait_ref.path.res.def_id());
175         if binop != expected_ops[idx];
176         then{
177             return Some(traits[idx])
178         }
179     }
180
181     None
182 }
183
184 struct BinaryExprVisitor {
185     in_binary_expr: bool,
186 }
187
188 impl<'a, 'tcx> Visitor<'tcx> for BinaryExprVisitor {
189     type Map = Map<'tcx>;
190
191     fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
192         match expr.kind {
193             hir::ExprKind::Binary(..)
194             | hir::ExprKind::Unary(hir::UnOp::UnNot, _)
195             | hir::ExprKind::Unary(hir::UnOp::UnNeg, _)
196             | hir::ExprKind::AssignOp(..) => self.in_binary_expr = true,
197             _ => {},
198         }
199
200         walk_expr(self, expr);
201     }
202     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
203         NestedVisitorMap::None
204     }
205 }