]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/copies.rs
Fix lines that exceed max width manually
[rust.git] / clippy_lints / src / copies.rs
1 use rustc::lint::*;
2 use rustc::ty::Ty;
3 use rustc::hir::*;
4 use std::collections::HashMap;
5 use std::collections::hash_map::Entry;
6 use syntax::symbol::InternedString;
7 use syntax::util::small_vector::SmallVector;
8 use utils::{SpanlessEq, SpanlessHash};
9 use utils::{get_parent_expr, in_macro, snippet, span_lint_and_then, span_note_and_lint};
10
11 /// **What it does:** Checks for consecutive `if`s with the same condition.
12 ///
13 /// **Why is this bad?** This is probably a copy & paste error.
14 ///
15 /// **Known problems:** Hopefully none.
16 ///
17 /// **Example:**
18 /// ```rust
19 /// if a == b {
20 ///     …
21 /// } else if a == b {
22 ///     …
23 /// }
24 /// ```
25 ///
26 /// Note that this lint ignores all conditions with a function call as it could
27 /// have side effects:
28 ///
29 /// ```rust
30 /// if foo() {
31 ///     …
32 /// } else if foo() { // not linted
33 ///     …
34 /// }
35 /// ```
36 declare_lint! {
37     pub IFS_SAME_COND,
38     Warn,
39     "consecutive `ifs` with the same condition"
40 }
41
42 /// **What it does:** Checks for `if/else` with the same body as the *then* part
43 /// and the *else* part.
44 ///
45 /// **Why is this bad?** This is probably a copy & paste error.
46 ///
47 /// **Known problems:** Hopefully none.
48 ///
49 /// **Example:**
50 /// ```rust
51 /// let foo = if … {
52 ///     42
53 /// } else {
54 ///     42
55 /// };
56 /// ```
57 declare_lint! {
58     pub IF_SAME_THEN_ELSE,
59     Warn,
60     "if with the same *then* and *else* blocks"
61 }
62
63 /// **What it does:** Checks for `match` with identical arm bodies.
64 ///
65 /// **Why is this bad?** This is probably a copy & paste error. If arm bodies
66 /// are the same on purpose, you can factor them
67 /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
68 ///
69 /// **Known problems:** False positive possible with order dependent `match`
70 /// (see issue
71 /// [#860](https://github.com/rust-lang-nursery/rust-clippy/issues/860)).
72 ///
73 /// **Example:**
74 /// ```rust,ignore
75 /// match foo {
76 ///     Bar => bar(),
77 ///     Quz => quz(),
78 ///     Baz => bar(), // <= oops
79 /// }
80 /// ```
81 ///
82 /// This should probably be
83 /// ```rust,ignore
84 /// match foo {
85 ///     Bar => bar(),
86 ///     Quz => quz(),
87 ///     Baz => baz(), // <= fixed
88 /// }
89 /// ```
90 ///
91 /// or if the original code was not a typo:
92 /// ```rust,ignore
93 /// match foo {
94 ///     Bar | Baz => bar(), // <= shows the intent better
95 ///     Quz => quz(),
96 /// }
97 /// ```
98 declare_lint! {
99     pub MATCH_SAME_ARMS,
100     Warn,
101     "`match` with identical arm bodies"
102 }
103
104 #[derive(Copy, Clone, Debug)]
105 pub struct CopyAndPaste;
106
107 impl LintPass for CopyAndPaste {
108     fn get_lints(&self) -> LintArray {
109         lint_array![IFS_SAME_COND, IF_SAME_THEN_ELSE, MATCH_SAME_ARMS]
110     }
111 }
112
113 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CopyAndPaste {
114     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
115         if !in_macro(expr.span) {
116             // skip ifs directly in else, it will be checked in the parent if
117             if let Some(&Expr {
118                 node: ExprIf(_, _, Some(ref else_expr)),
119                 ..
120             }) = get_parent_expr(cx, expr)
121             {
122                 if else_expr.id == expr.id {
123                     return;
124                 }
125             }
126
127             let (conds, blocks) = if_sequence(expr);
128             lint_same_then_else(cx, &blocks);
129             lint_same_cond(cx, &conds);
130             lint_match_arms(cx, expr);
131         }
132     }
133 }
134
135 /// Implementation of `IF_SAME_THEN_ELSE`.
136 fn lint_same_then_else(cx: &LateContext, blocks: &[&Block]) {
137     let hash: &Fn(&&Block) -> u64 = &|block| -> u64 {
138         let mut h = SpanlessHash::new(cx);
139         h.hash_block(block);
140         h.finish()
141     };
142
143     let eq: &Fn(&&Block, &&Block) -> bool = &|&lhs, &rhs| -> bool { SpanlessEq::new(cx).eq_block(lhs, rhs) };
144
145     if let Some((i, j)) = search_same(blocks, hash, eq) {
146         span_note_and_lint(
147             cx,
148             IF_SAME_THEN_ELSE,
149             j.span,
150             "this `if` has identical blocks",
151             i.span,
152             "same as this",
153         );
154     }
155 }
156
157 /// Implementation of `IFS_SAME_COND`.
158 fn lint_same_cond(cx: &LateContext, conds: &[&Expr]) {
159     let hash: &Fn(&&Expr) -> u64 = &|expr| -> u64 {
160         let mut h = SpanlessHash::new(cx);
161         h.hash_expr(expr);
162         h.finish()
163     };
164
165     let eq: &Fn(&&Expr, &&Expr) -> bool = &|&lhs, &rhs| -> bool { SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs) };
166
167     if let Some((i, j)) = search_same(conds, hash, eq) {
168         span_note_and_lint(
169             cx,
170             IFS_SAME_COND,
171             j.span,
172             "this `if` has the same condition as a previous if",
173             i.span,
174             "same as this",
175         );
176     }
177 }
178
179 /// Implementation if `MATCH_SAME_ARMS`.
180 fn lint_match_arms(cx: &LateContext, expr: &Expr) {
181     let hash = |arm: &Arm| -> u64 {
182         let mut h = SpanlessHash::new(cx);
183         h.hash_expr(&arm.body);
184         h.finish()
185     };
186
187     let eq = |lhs: &Arm, rhs: &Arm| -> bool {
188         // Arms with a guard are ignored, those can’t always be merged together
189         lhs.guard.is_none() && rhs.guard.is_none() &&
190             SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) &&
191             // all patterns should have the same bindings
192             bindings(cx, &lhs.pats[0]) == bindings(cx, &rhs.pats[0])
193     };
194
195     if let ExprMatch(_, ref arms, MatchSource::Normal) = expr.node {
196         if let Some((i, j)) = search_same(arms, hash, eq) {
197             span_lint_and_then(
198                 cx,
199                 MATCH_SAME_ARMS,
200                 j.body.span,
201                 "this `match` has identical arm bodies",
202                 |db| {
203                     db.span_note(i.body.span, "same as this");
204
205                     // Note: this does not use `span_suggestion` on purpose: there is no clean way
206                     // to
207                     // remove the other arm. Building a span and suggest to replace it to "" makes
208                     // an
209                     // even more confusing error message. Also in order not to make up a span for
210                     // the
211                     // whole pattern, the suggestion is only shown when there is only one pattern.
212                     // The
213                     // user should know about `|` if they are already using it…
214
215                     if i.pats.len() == 1 && j.pats.len() == 1 {
216                         let lhs = snippet(cx, i.pats[0].span, "<pat1>");
217                         let rhs = snippet(cx, j.pats[0].span, "<pat2>");
218
219                         if let PatKind::Wild = j.pats[0].node {
220                             // if the last arm is _, then i could be integrated into _
221                             // note that i.pats[0] cannot be _, because that would mean that we're
222                             // hiding all the subsequent arms, and rust won't compile
223                             db.span_note(
224                                 i.body.span,
225                                 &format!("`{}` has the same arm body as the `_` wildcard, consider removing it`", lhs),
226                             );
227                         } else {
228                             db.span_note(i.body.span, &format!("consider refactoring into `{} | {}`", lhs, rhs));
229                         }
230                     }
231                 },
232             );
233         }
234     }
235 }
236
237 /// Return the list of condition expressions and the list of blocks in a
238 /// sequence of `if/else`.
239 /// Eg. would return `([a, b], [c, d, e])` for the expression
240 /// `if a { c } else if b { d } else { e }`.
241 fn if_sequence(mut expr: &Expr) -> (SmallVector<&Expr>, SmallVector<&Block>) {
242     let mut conds = SmallVector::new();
243     let mut blocks: SmallVector<&Block> = SmallVector::new();
244
245     while let ExprIf(ref cond, ref then_expr, ref else_expr) = expr.node {
246         conds.push(&**cond);
247         if let ExprBlock(ref block) = then_expr.node {
248             blocks.push(block);
249         } else {
250             panic!("ExprIf node is not an ExprBlock");
251         }
252
253         if let Some(ref else_expr) = *else_expr {
254             expr = else_expr;
255         } else {
256             break;
257         }
258     }
259
260     // final `else {..}`
261     if !blocks.is_empty() {
262         if let ExprBlock(ref block) = expr.node {
263             blocks.push(&**block);
264         }
265     }
266
267     (conds, blocks)
268 }
269
270 /// Return the list of bindings in a pattern.
271 fn bindings<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat) -> HashMap<InternedString, Ty<'tcx>> {
272     fn bindings_impl<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat, map: &mut HashMap<InternedString, Ty<'tcx>>) {
273         match pat.node {
274             PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => bindings_impl(cx, pat, map),
275             PatKind::TupleStruct(_, ref pats, _) => for pat in pats {
276                 bindings_impl(cx, pat, map);
277             },
278             PatKind::Binding(_, _, ref ident, ref as_pat) => {
279                 if let Entry::Vacant(v) = map.entry(ident.node.as_str()) {
280                     v.insert(cx.tables.pat_ty(pat));
281                 }
282                 if let Some(ref as_pat) = *as_pat {
283                     bindings_impl(cx, as_pat, map);
284                 }
285             },
286             PatKind::Struct(_, ref fields, _) => for pat in fields {
287                 bindings_impl(cx, &pat.node.pat, map);
288             },
289             PatKind::Tuple(ref fields, _) => for pat in fields {
290                 bindings_impl(cx, pat, map);
291             },
292             PatKind::Slice(ref lhs, ref mid, ref rhs) => {
293                 for pat in lhs {
294                     bindings_impl(cx, pat, map);
295                 }
296                 if let Some(ref mid) = *mid {
297                     bindings_impl(cx, mid, map);
298                 }
299                 for pat in rhs {
300                     bindings_impl(cx, pat, map);
301                 }
302             },
303             PatKind::Lit(..) | PatKind::Range(..) | PatKind::Wild | PatKind::Path(..) => (),
304         }
305     }
306
307     let mut result = HashMap::new();
308     bindings_impl(cx, pat, &mut result);
309     result
310 }
311
312 fn search_same<T, Hash, Eq>(exprs: &[T], hash: Hash, eq: Eq) -> Option<(&T, &T)>
313 where
314     Hash: Fn(&T) -> u64,
315     Eq: Fn(&T, &T) -> bool,
316 {
317     // common cases
318     if exprs.len() < 2 {
319         return None;
320     } else if exprs.len() == 2 {
321         return if eq(&exprs[0], &exprs[1]) {
322             Some((&exprs[0], &exprs[1]))
323         } else {
324             None
325         };
326     }
327
328     let mut map: HashMap<_, Vec<&_>> = HashMap::with_capacity(exprs.len());
329
330     for expr in exprs {
331         match map.entry(hash(expr)) {
332             Entry::Occupied(o) => for o in o.get() {
333                 if eq(o, expr) {
334                     return Some((o, expr));
335                 }
336             },
337             Entry::Vacant(v) => {
338                 v.insert(vec![expr]);
339             },
340         }
341     }
342
343     None
344 }