]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches.rs
rustup to 2017-01-12
[rust.git] / clippy_lints / src / matches.rs
1 use rustc::hir::*;
2 use rustc::lint::*;
3 use rustc::middle::const_val::ConstVal;
4 use rustc::ty;
5 use rustc_const_eval::EvalHint::ExprTypeChecked;
6 use rustc_const_eval::ConstContext;
7 use rustc_const_math::ConstInt;
8 use std::cmp::Ordering;
9 use syntax::ast::LitKind;
10 use syntax::codemap::Span;
11 use utils::paths;
12 use utils::{match_type, snippet, span_note_and_lint, span_lint_and_then, in_external_macro, expr_block};
13 use utils::sugg::Sugg;
14
15 /// **What it does:** Checks for matches with a single arm where an `if let`
16 /// will usually suffice.
17 ///
18 /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
19 ///
20 /// **Known problems:** None.
21 ///
22 /// **Example:**
23 /// ```rust
24 /// match x {
25 ///     Some(ref foo) => bar(foo),
26 ///     _ => ()
27 /// }
28 /// ```
29 declare_lint! {
30     pub SINGLE_MATCH,
31     Warn,
32     "a match statement with a single nontrivial arm (i.e, where the other arm \
33      is `_ => {}`) instead of `if let`"
34 }
35
36 /// **What it does:** Checks for matches with a two arms where an `if let` will
37 /// usually suffice.
38 ///
39 /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
40 ///
41 /// **Known problems:** Personal style preferences may differ.
42 ///
43 /// **Example:**
44 /// ```rust
45 /// match x {
46 ///     Some(ref foo) => bar(foo),
47 ///     _ => bar(other_ref),
48 /// }
49 /// ```
50 declare_lint! {
51     pub SINGLE_MATCH_ELSE,
52     Allow,
53     "a match statement with a two arms where the second arm's pattern is a wildcard \
54      instead of `if let`"
55 }
56
57 /// **What it does:** Checks for matches where all arms match a reference,
58 /// suggesting to remove the reference and deref the matched expression
59 /// instead. It also checks for `if let &foo = bar` blocks.
60 ///
61 /// **Why is this bad?** It just makes the code less readable. That reference
62 /// destructuring adds nothing to the code.
63 ///
64 /// **Known problems:** None.
65 ///
66 /// **Example:**
67 /// ```rust
68 /// match x {
69 ///     &A(ref y) => foo(y),
70 ///     &B => bar(),
71 ///     _ => frob(&x),
72 /// }
73 /// ```
74 declare_lint! {
75     pub MATCH_REF_PATS,
76     Warn,
77     "a match or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
78 }
79
80 /// **What it does:** Checks for matches where match expression is a `bool`. It
81 /// suggests to replace the expression with an `if...else` block.
82 ///
83 /// **Why is this bad?** It makes the code less readable.
84 ///
85 /// **Known problems:** None.
86 ///
87 /// **Example:**
88 /// ```rust
89 /// let condition: bool = true;
90 /// match condition {
91 ///     true => foo(),
92 ///     false => bar(),
93 /// }
94 /// ```
95 declare_lint! {
96     pub MATCH_BOOL,
97     Warn,
98     "a match on a boolean expression instead of an `if..else` block"
99 }
100
101 /// **What it does:** Checks for overlapping match arms.
102 ///
103 /// **Why is this bad?** It is likely to be an error and if not, makes the code
104 /// less obvious.
105 ///
106 /// **Known problems:** None.
107 ///
108 /// **Example:**
109 /// ```rust
110 /// let x = 5;
111 /// match x {
112 ///     1 ... 10 => println!("1 ... 10"),
113 ///     5 ... 15 => println!("5 ... 15"),
114 ///     _ => (),
115 /// }
116 /// ```
117 declare_lint! {
118     pub MATCH_OVERLAPPING_ARM,
119     Warn,
120     "a match with overlapping arms"
121 }
122
123 #[allow(missing_copy_implementations)]
124 pub struct MatchPass;
125
126 impl LintPass for MatchPass {
127     fn get_lints(&self) -> LintArray {
128         lint_array!(SINGLE_MATCH,
129                     MATCH_REF_PATS,
130                     MATCH_BOOL,
131                     SINGLE_MATCH_ELSE,
132                     MATCH_OVERLAPPING_ARM)
133     }
134 }
135
136 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MatchPass {
137     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
138         if in_external_macro(cx, expr.span) {
139             return;
140         }
141         if let ExprMatch(ref ex, ref arms, MatchSource::Normal) = expr.node {
142             check_single_match(cx, ex, arms, expr);
143             check_match_bool(cx, ex, arms, expr);
144             check_overlapping_arms(cx, ex, arms);
145         }
146         if let ExprMatch(ref ex, ref arms, source) = expr.node {
147             check_match_ref_pats(cx, ex, arms, source, expr);
148         }
149     }
150 }
151
152 #[cfg_attr(rustfmt, rustfmt_skip)]
153 fn check_single_match(cx: &LateContext, ex: &Expr, arms: &[Arm], expr: &Expr) {
154     if arms.len() == 2 &&
155       arms[0].pats.len() == 1 && arms[0].guard.is_none() &&
156       arms[1].pats.len() == 1 && arms[1].guard.is_none() {
157         let els = if is_unit_expr(&arms[1].body) {
158             None
159         } else if let ExprBlock(_) = arms[1].body.node {
160             // matches with blocks that contain statements are prettier as `if let + else`
161             Some(&*arms[1].body)
162         } else {
163             // allow match arms with just expressions
164             return;
165         };
166         let ty = cx.tables.expr_ty(ex);
167         if ty.sty != ty::TyBool || cx.current_level(MATCH_BOOL) == Allow {
168             check_single_match_single_pattern(cx, ex, arms, expr, els);
169             check_single_match_opt_like(cx, ex, arms, expr, ty, els);
170         }
171     }
172 }
173
174 fn check_single_match_single_pattern(cx: &LateContext, ex: &Expr, arms: &[Arm], expr: &Expr, els: Option<&Expr>) {
175     if arms[1].pats[0].node == PatKind::Wild {
176         let lint = if els.is_some() {
177             SINGLE_MATCH_ELSE
178         } else {
179             SINGLE_MATCH
180         };
181         let els_str = els.map_or(String::new(), |els| format!(" else {}", expr_block(cx, els, None, "..")));
182         span_lint_and_then(cx,
183                            lint,
184                            expr.span,
185                            "you seem to be trying to use match for destructuring a single pattern. \
186                            Consider using `if let`",
187                            |db| {
188             db.span_suggestion(expr.span,
189                                "try this",
190                                format!("if let {} = {} {}{}",
191                                        snippet(cx, arms[0].pats[0].span, ".."),
192                                        snippet(cx, ex.span, ".."),
193                                        expr_block(cx, &arms[0].body, None, ".."),
194                                        els_str));
195         });
196     }
197 }
198
199 fn check_single_match_opt_like(
200     cx: &LateContext,
201     ex: &Expr,
202     arms: &[Arm],
203     expr: &Expr,
204     ty: ty::Ty,
205     els: Option<&Expr>
206 ) {
207     // list of candidate Enums we know will never get any more members
208     let candidates = &[(&paths::COW, "Borrowed"),
209                        (&paths::COW, "Cow::Borrowed"),
210                        (&paths::COW, "Cow::Owned"),
211                        (&paths::COW, "Owned"),
212                        (&paths::OPTION, "None"),
213                        (&paths::RESULT, "Err"),
214                        (&paths::RESULT, "Ok")];
215
216     let path = match arms[1].pats[0].node {
217         PatKind::TupleStruct(ref path, ref inner, _) => {
218             // contains any non wildcard patterns? e.g. Err(err)
219             if inner.iter().any(|pat| pat.node != PatKind::Wild) {
220                 return;
221             }
222             print::to_string(print::NO_ANN, |s| s.print_qpath(path, false))
223         },
224         PatKind::Binding(BindByValue(MutImmutable), _, ident, None) => ident.node.to_string(),
225         PatKind::Path(ref path) => print::to_string(print::NO_ANN, |s| s.print_qpath(path, false)),
226         _ => return,
227     };
228
229     for &(ty_path, pat_path) in candidates {
230         if &path == pat_path && match_type(cx, ty, ty_path) {
231             let lint = if els.is_some() {
232                 SINGLE_MATCH_ELSE
233             } else {
234                 SINGLE_MATCH
235             };
236             let els_str = els.map_or(String::new(), |els| format!(" else {}", expr_block(cx, els, None, "..")));
237             span_lint_and_then(cx,
238                                lint,
239                                expr.span,
240                                "you seem to be trying to use match for destructuring a single pattern. Consider \
241                                 using `if let`",
242                                |db| {
243                 db.span_suggestion(expr.span,
244                                    "try this",
245                                    format!("if let {} = {} {}{}",
246                                            snippet(cx, arms[0].pats[0].span, ".."),
247                                            snippet(cx, ex.span, ".."),
248                                            expr_block(cx, &arms[0].body, None, ".."),
249                                            els_str));
250             });
251         }
252     }
253 }
254
255 fn check_match_bool(cx: &LateContext, ex: &Expr, arms: &[Arm], expr: &Expr) {
256     // type of expression == bool
257     if cx.tables.expr_ty(ex).sty == ty::TyBool {
258         span_lint_and_then(cx,
259                            MATCH_BOOL,
260                            expr.span,
261                            "you seem to be trying to match on a boolean expression",
262                            move |db| {
263             if arms.len() == 2 && arms[0].pats.len() == 1 {
264                 // no guards
265                 let exprs = if let PatKind::Lit(ref arm_bool) = arms[0].pats[0].node {
266                     if let ExprLit(ref lit) = arm_bool.node {
267                         match lit.node {
268                             LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
269                             LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)),
270                             _ => None,
271                         }
272                     } else {
273                         None
274                     }
275                 } else {
276                     None
277                 };
278
279                 if let Some((true_expr, false_expr)) = exprs {
280                     let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) {
281                         (false, false) => {
282                             Some(format!("if {} {} else {}",
283                                          snippet(cx, ex.span, "b"),
284                                          expr_block(cx, true_expr, None, ".."),
285                                          expr_block(cx, false_expr, None, "..")))
286                         },
287                         (false, true) => {
288                             Some(format!("if {} {}", snippet(cx, ex.span, "b"), expr_block(cx, true_expr, None, "..")))
289                         },
290                         (true, false) => {
291                             let test = Sugg::hir(cx, ex, "..");
292                             Some(format!("if {} {}", !test, expr_block(cx, false_expr, None, "..")))
293                         },
294                         (true, true) => None,
295                     };
296
297                     if let Some(sugg) = sugg {
298                         db.span_suggestion(expr.span, "consider using an if/else expression", sugg);
299                     }
300                 }
301             }
302
303         });
304     }
305 }
306
307 fn check_overlapping_arms(cx: &LateContext, ex: &Expr, arms: &[Arm]) {
308     if arms.len() >= 2 && cx.tables.expr_ty(ex).is_integral() {
309         let ranges = all_ranges(cx, arms);
310         let type_ranges = type_ranges(&ranges);
311         if !type_ranges.is_empty() {
312             if let Some((start, end)) = overlapping(&type_ranges) {
313                 span_note_and_lint(cx,
314                                    MATCH_OVERLAPPING_ARM,
315                                    start.span,
316                                    "some ranges overlap",
317                                    end.span,
318                                    "overlaps with this");
319             }
320         }
321     }
322 }
323
324 fn check_match_ref_pats(cx: &LateContext, ex: &Expr, arms: &[Arm], source: MatchSource, expr: &Expr) {
325     if has_only_ref_pats(arms) {
326         if let ExprAddrOf(Mutability::MutImmutable, ref inner) = ex.node {
327             span_lint_and_then(cx,
328                                MATCH_REF_PATS,
329                                expr.span,
330                                "you don't need to add `&` to both the expression and the patterns",
331                                |db| {
332                 let inner = Sugg::hir(cx, inner, "..");
333                 let template = match_template(expr.span, source, inner);
334                 db.span_suggestion(expr.span, "try", template);
335             });
336         } else {
337             span_lint_and_then(cx,
338                                MATCH_REF_PATS,
339                                expr.span,
340                                "you don't need to add `&` to all patterns",
341                                |db| {
342                 let ex = Sugg::hir(cx, ex, "..");
343                 let template = match_template(expr.span, source, ex.deref());
344                 db.span_suggestion(expr.span,
345                                    "instead of prefixing all patterns with `&`, you can dereference the expression",
346                                    template);
347             });
348         }
349     }
350 }
351
352 /// Get all arms that are unbounded `PatRange`s.
353 fn all_ranges(cx: &LateContext, arms: &[Arm]) -> Vec<SpannedRange<ConstVal>> {
354     let constcx = ConstContext::with_tables(cx.tcx, cx.tables);
355     arms.iter()
356         .flat_map(|arm| {
357             if let Arm { ref pats, guard: None, .. } = *arm {
358                     pats.iter()
359                 } else {
360                     [].iter()
361                 }
362                 .filter_map(|pat| {
363                     if_let_chain! {[
364                     let PatKind::Range(ref lhs, ref rhs) = pat.node,
365                     let Ok(lhs) = constcx.eval(lhs, ExprTypeChecked),
366                     let Ok(rhs) = constcx.eval(rhs, ExprTypeChecked)
367                 ], {
368                     return Some(SpannedRange { span: pat.span, node: (lhs, rhs) });
369                 }}
370
371                     if_let_chain! {[
372                     let PatKind::Lit(ref value) = pat.node,
373                     let Ok(value) = constcx.eval(value, ExprTypeChecked)
374                 ], {
375                     return Some(SpannedRange { span: pat.span, node: (value.clone(), value) });
376                 }}
377
378                     None
379                 })
380         })
381         .collect()
382 }
383
384 #[derive(Debug, Eq, PartialEq)]
385 pub struct SpannedRange<T> {
386     pub span: Span,
387     pub node: (T, T),
388 }
389
390 type TypedRanges = Vec<SpannedRange<ConstInt>>;
391
392 /// Get all `Int` ranges or all `Uint` ranges. Mixed types are an error anyway and other types than
393 /// `Uint` and `Int` probably don't make sense.
394 fn type_ranges(ranges: &[SpannedRange<ConstVal>]) -> TypedRanges {
395     ranges.iter()
396         .filter_map(|range| {
397             if let (ConstVal::Integral(start), ConstVal::Integral(end)) = range.node {
398                 Some(SpannedRange {
399                     span: range.span,
400                     node: (start, end),
401                 })
402             } else {
403                 None
404             }
405         })
406         .collect()
407 }
408
409 fn is_unit_expr(expr: &Expr) -> bool {
410     match expr.node {
411         ExprTup(ref v) if v.is_empty() => true,
412         ExprBlock(ref b) if b.stmts.is_empty() && b.expr.is_none() => true,
413         _ => false,
414     }
415 }
416
417 fn has_only_ref_pats(arms: &[Arm]) -> bool {
418     let mapped = arms.iter()
419         .flat_map(|a| &a.pats)
420         .map(|p| {
421             match p.node {
422                 PatKind::Ref(..) => Some(true),  // &-patterns
423                 PatKind::Wild => Some(false),   // an "anything" wildcard is also fine
424                 _ => None,                    // any other pattern is not fine
425             }
426         })
427         .collect::<Option<Vec<bool>>>();
428     // look for Some(v) where there's at least one true element
429     mapped.map_or(false, |v| v.iter().any(|el| *el))
430 }
431
432 fn match_template(span: Span, source: MatchSource, expr: Sugg) -> String {
433     match source {
434         MatchSource::Normal => format!("match {} {{ .. }}", expr),
435         MatchSource::IfLetDesugar { .. } => format!("if let .. = {} {{ .. }}", expr),
436         MatchSource::WhileLetDesugar => format!("while let .. = {} {{ .. }}", expr),
437         MatchSource::ForLoopDesugar => span_bug!(span, "for loop desugared to match with &-patterns!"),
438         MatchSource::TryDesugar => span_bug!(span, "`?` operator desugared to match with &-patterns!"),
439     }
440 }
441
442 pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
443     where T: Copy + Ord
444 {
445     #[derive(Copy, Clone, Debug, Eq, PartialEq)]
446     enum Kind<'a, T: 'a> {
447         Start(T, &'a SpannedRange<T>),
448         End(T, &'a SpannedRange<T>),
449     }
450
451     impl<'a, T: Copy> Kind<'a, T> {
452         fn range(&self) -> &'a SpannedRange<T> {
453             match *self {
454                 Kind::Start(_, r) |
455                 Kind::End(_, r) => r,
456             }
457         }
458
459         fn value(self) -> T {
460             match self {
461                 Kind::Start(t, _) |
462                 Kind::End(t, _) => t,
463             }
464         }
465     }
466
467     impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, T> {
468         fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
469             Some(self.cmp(other))
470         }
471     }
472
473     impl<'a, T: Copy + Ord> Ord for Kind<'a, T> {
474         fn cmp(&self, other: &Self) -> Ordering {
475             self.value().cmp(&other.value())
476         }
477     }
478
479     let mut values = Vec::with_capacity(2 * ranges.len());
480
481     for r in ranges {
482         values.push(Kind::Start(r.node.0, r));
483         values.push(Kind::End(r.node.1, r));
484     }
485
486     values.sort();
487
488     for (a, b) in values.iter().zip(values.iter().skip(1)) {
489         match (a, b) {
490             (&Kind::Start(_, ra), &Kind::End(_, rb)) => {
491                 if ra.node != rb.node {
492                     return Some((ra, rb));
493                 }
494             },
495             (&Kind::End(a, _), &Kind::Start(b, _)) if a != b => (),
496             _ => return Some((a.range(), b.range())),
497         }
498     }
499
500     None
501 }