]> git.lizzy.rs Git - rust.git/blob - src/copies.rs
fix nightly
[rust.git] / src / copies.rs
1 use rustc::lint::*;
2 use rustc::middle::ty;
3 use rustc_front::hir::*;
4 use std::collections::HashMap;
5 use std::collections::hash_map::Entry;
6 use syntax::parse::token::InternedString;
7 use syntax::util::small_vector::SmallVector;
8 use utils::{SpanlessEq, SpanlessHash};
9 use utils::{get_parent_expr, in_macro, span_note_and_lint};
10
11 /// **What it does:** This lint checks for consecutive `ifs` with the same condition. This lint is
12 /// `Warn` by default.
13 ///
14 /// **Why is this bad?** This is probably a copy & paste error.
15 ///
16 /// **Known problems:** Hopefully none.
17 ///
18 /// **Example:** `if a == b { .. } else if a == b { .. }`
19 declare_lint! {
20     pub IFS_SAME_COND,
21     Warn,
22     "consecutive `ifs` with the same condition"
23 }
24
25 /// **What it does:** This lint checks for `if/else` with the same body as the *then* part and the
26 /// *else* part. This lint is `Warn` by default.
27 ///
28 /// **Why is this bad?** This is probably a copy & paste error.
29 ///
30 /// **Known problems:** Hopefully none.
31 ///
32 /// **Example:** `if .. { 42 } else { 42 }`
33 declare_lint! {
34     pub IF_SAME_THEN_ELSE,
35     Warn,
36     "if with the same *then* and *else* blocks"
37 }
38
39 /// **What it does:** This lint checks for `match` with identical arm bodies.
40 ///
41 /// **Why is this bad?** This is probably a copy & paste error.
42 ///
43 /// **Known problems:** Hopefully none.
44 ///
45 /// **Example:**
46 /// ```rust,ignore
47 /// match foo {
48 ///     Bar => bar(),
49 ///     Quz => quz(),
50 ///     Baz => bar(), // <= oups
51 /// }
52 /// ```
53 declare_lint! {
54     pub MATCH_SAME_ARMS,
55     Warn,
56     "`match` with identical arm bodies"
57 }
58
59 #[derive(Copy, Clone, Debug)]
60 pub struct CopyAndPaste;
61
62 impl LintPass for CopyAndPaste {
63     fn get_lints(&self) -> LintArray {
64         lint_array![
65             IFS_SAME_COND,
66             IF_SAME_THEN_ELSE,
67             MATCH_SAME_ARMS
68         ]
69     }
70 }
71
72 impl LateLintPass for CopyAndPaste {
73     fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
74         if !in_macro(cx, expr.span) {
75             // skip ifs directly in else, it will be checked in the parent if
76             if let Some(&Expr{node: ExprIf(_, _, Some(ref else_expr)), ..}) = get_parent_expr(cx, expr) {
77                 if else_expr.id == expr.id {
78                     return;
79                 }
80             }
81
82             let (conds, blocks) = if_sequence(expr);
83             lint_same_then_else(cx, blocks.as_slice());
84             lint_same_cond(cx, conds.as_slice());
85             lint_match_arms(cx, expr);
86         }
87     }
88 }
89
90 /// Implementation of `IF_SAME_THEN_ELSE`.
91 fn lint_same_then_else(cx: &LateContext, blocks: &[&Block]) {
92     let hash : &Fn(&&Block) -> u64 = &|block| -> u64 {
93         let mut h = SpanlessHash::new(cx);
94         h.hash_block(block);
95         h.finish()
96     };
97
98     let eq : &Fn(&&Block, &&Block) -> bool = &|&lhs, &rhs| -> bool {
99         SpanlessEq::new(cx).eq_block(lhs, rhs)
100     };
101
102     if let Some((i, j)) = search_same(blocks, hash, eq) {
103         span_note_and_lint(cx, IF_SAME_THEN_ELSE, j.span, "this `if` has identical blocks", i.span, "same as this");
104     }
105 }
106
107 /// Implementation of `IFS_SAME_COND`.
108 fn lint_same_cond(cx: &LateContext, conds: &[&Expr]) {
109     let hash : &Fn(&&Expr) -> u64 = &|expr| -> u64 {
110         let mut h = SpanlessHash::new(cx);
111         h.hash_expr(expr);
112         h.finish()
113     };
114
115     let eq : &Fn(&&Expr, &&Expr) -> bool = &|&lhs, &rhs| -> bool {
116         SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs)
117     };
118
119     if let Some((i, j)) = search_same(conds, hash, eq) {
120         span_note_and_lint(cx, IFS_SAME_COND, j.span, "this `if` has the same condition as a previous if", i.span, "same as this");
121     }
122 }
123
124 /// Implementation if `MATCH_SAME_ARMS`.
125 fn lint_match_arms(cx: &LateContext, expr: &Expr) {
126     let hash = |arm: &Arm| -> u64 {
127         let mut h = SpanlessHash::new(cx);
128         h.hash_expr(&arm.body);
129         h.finish()
130     };
131
132     let eq = |lhs: &Arm, rhs: &Arm| -> bool {
133         SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) &&
134             // all patterns should have the same bindings
135             bindings(cx, &lhs.pats[0]) == bindings(cx, &rhs.pats[0])
136     };
137
138     if let ExprMatch(_, ref arms, MatchSource::Normal) = expr.node {
139         if let Some((i, j)) = search_same(&**arms, hash, eq) {
140             span_note_and_lint(cx, MATCH_SAME_ARMS, j.body.span, "this `match` has identical arm bodies", i.body.span, "same as this");
141         }
142     }
143 }
144
145 /// Return the list of condition expressions and the list of blocks in a sequence of `if/else`.
146 /// Eg. would return `([a, b], [c, d, e])` for the expression
147 /// `if a { c } else if b { d } else { e }`.
148 fn if_sequence(mut expr: &Expr) -> (SmallVector<&Expr>, SmallVector<&Block>) {
149     let mut conds = SmallVector::zero();
150     let mut blocks = SmallVector::zero();
151
152     while let ExprIf(ref cond, ref then_block, ref else_expr) = expr.node {
153         conds.push(&**cond);
154         blocks.push(&**then_block);
155
156         if let Some(ref else_expr) = *else_expr {
157             expr = else_expr;
158         }
159         else {
160             break;
161         }
162     }
163
164     // final `else {..}`
165     if !blocks.is_empty() {
166         if let ExprBlock(ref block) = expr.node {
167             blocks.push(&**block);
168         }
169     }
170
171     (conds, blocks)
172 }
173
174 /// Return the list of bindings in a pattern.
175 fn bindings<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat) -> HashMap<InternedString, ty::Ty<'tcx>> {
176     fn bindings_impl<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat, map: &mut HashMap<InternedString, ty::Ty<'tcx>>) {
177         match pat.node {
178             PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => bindings_impl(cx, pat, map),
179             PatKind::TupleStruct(_, Some(ref pats)) => {
180                 for pat in pats {
181                     bindings_impl(cx, pat, map);
182                 }
183             }
184             PatKind::Ident(_, ref ident, ref as_pat) => {
185                 if let Entry::Vacant(v) = map.entry(ident.node.name.as_str()) {
186                     v.insert(cx.tcx.pat_ty(pat));
187                 }
188                 if let Some(ref as_pat) = *as_pat {
189                     bindings_impl(cx, as_pat, map);
190                 }
191             },
192             PatKind::Struct(_, ref fields, _) => {
193                 for pat in fields {
194                     bindings_impl(cx, &pat.node.pat, map);
195                 }
196             }
197             PatKind::Tup(ref fields) => {
198                 for pat in fields {
199                     bindings_impl(cx, pat, map);
200                 }
201             }
202             PatKind::Vec(ref lhs, ref mid, ref rhs) => {
203                 for pat in lhs {
204                     bindings_impl(cx, pat, map);
205                 }
206                 if let Some(ref mid) = *mid {
207                     bindings_impl(cx, mid, map);
208                 }
209                 for pat in rhs {
210                     bindings_impl(cx, pat, map);
211                 }
212             }
213             PatKind::TupleStruct(..) | PatKind::Lit(..) | PatKind::QPath(..) | PatKind::Range(..) | PatKind::Wild | PatKind::Path(..) => (),
214         }
215     }
216
217     let mut result = HashMap::new();
218     bindings_impl(cx, pat, &mut result);
219     result
220 }
221
222 fn search_same<T, Hash, Eq>(exprs: &[T],
223                             hash: Hash,
224                             eq: Eq) -> Option<(&T, &T)>
225 where Hash: Fn(&T) -> u64,
226       Eq: Fn(&T, &T) -> bool {
227     // common cases
228     if exprs.len() < 2 {
229         return None;
230     }
231     else if exprs.len() == 2 {
232         return if eq(&exprs[0], &exprs[1]) {
233             Some((&exprs[0], &exprs[1]))
234         }
235         else {
236             None
237         }
238     }
239
240     let mut map : HashMap<_, Vec<&_>> = HashMap::with_capacity(exprs.len());
241
242     for expr in exprs {
243         match map.entry(hash(expr)) {
244             Entry::Occupied(o) => {
245                 for o in o.get() {
246                     if eq(&o, expr) {
247                         return Some((&o, expr))
248                     }
249                 }
250             }
251             Entry::Vacant(v) => { v.insert(vec![expr]); }
252         }
253     }
254
255     None
256 }