]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches.rs
Merge pull request #1414 from samueltardieu/no-short-circuit-if
[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::eval_const_expr_partial;
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.tcx.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.tcx.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.tcx.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     arms.iter()
355         .flat_map(|arm| {
356             if let Arm { ref pats, guard: None, .. } = *arm {
357                     pats.iter()
358                 } else {
359                     [].iter()
360                 }
361                 .filter_map(|pat| {
362                     if_let_chain! {[
363                     let PatKind::Range(ref lhs, ref rhs) = pat.node,
364                     let Ok(lhs) = eval_const_expr_partial(cx.tcx, lhs, ExprTypeChecked, None),
365                     let Ok(rhs) = eval_const_expr_partial(cx.tcx, rhs, ExprTypeChecked, None)
366                 ], {
367                     return Some(SpannedRange { span: pat.span, node: (lhs, rhs) });
368                 }}
369
370                     if_let_chain! {[
371                     let PatKind::Lit(ref value) = pat.node,
372                     let Ok(value) = eval_const_expr_partial(cx.tcx, value, ExprTypeChecked, None)
373                 ], {
374                     return Some(SpannedRange { span: pat.span, node: (value.clone(), value) });
375                 }}
376
377                     None
378                 })
379         })
380         .collect()
381 }
382
383 #[derive(Debug, Eq, PartialEq)]
384 pub struct SpannedRange<T> {
385     pub span: Span,
386     pub node: (T, T),
387 }
388
389 type TypedRanges = Vec<SpannedRange<ConstInt>>;
390
391 /// Get all `Int` ranges or all `Uint` ranges. Mixed types are an error anyway and other types than
392 /// `Uint` and `Int` probably don't make sense.
393 fn type_ranges(ranges: &[SpannedRange<ConstVal>]) -> TypedRanges {
394     ranges.iter()
395         .filter_map(|range| {
396             if let (ConstVal::Integral(start), ConstVal::Integral(end)) = range.node {
397                 Some(SpannedRange {
398                     span: range.span,
399                     node: (start, end),
400                 })
401             } else {
402                 None
403             }
404         })
405         .collect()
406 }
407
408 fn is_unit_expr(expr: &Expr) -> bool {
409     match expr.node {
410         ExprTup(ref v) if v.is_empty() => true,
411         ExprBlock(ref b) if b.stmts.is_empty() && b.expr.is_none() => true,
412         _ => false,
413     }
414 }
415
416 fn has_only_ref_pats(arms: &[Arm]) -> bool {
417     let mapped = arms.iter()
418         .flat_map(|a| &a.pats)
419         .map(|p| {
420             match p.node {
421                 PatKind::Ref(..) => Some(true),  // &-patterns
422                 PatKind::Wild => Some(false),   // an "anything" wildcard is also fine
423                 _ => None,                    // any other pattern is not fine
424             }
425         })
426         .collect::<Option<Vec<bool>>>();
427     // look for Some(v) where there's at least one true element
428     mapped.map_or(false, |v| v.iter().any(|el| *el))
429 }
430
431 fn match_template(span: Span, source: MatchSource, expr: Sugg) -> String {
432     match source {
433         MatchSource::Normal => format!("match {} {{ .. }}", expr),
434         MatchSource::IfLetDesugar { .. } => format!("if let .. = {} {{ .. }}", expr),
435         MatchSource::WhileLetDesugar => format!("while let .. = {} {{ .. }}", expr),
436         MatchSource::ForLoopDesugar => span_bug!(span, "for loop desugared to match with &-patterns!"),
437         MatchSource::TryDesugar => span_bug!(span, "`?` operator desugared to match with &-patterns!"),
438     }
439 }
440
441 pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
442     where T: Copy + Ord
443 {
444     #[derive(Copy, Clone, Debug, Eq, PartialEq)]
445     enum Kind<'a, T: 'a> {
446         Start(T, &'a SpannedRange<T>),
447         End(T, &'a SpannedRange<T>),
448     }
449
450     impl<'a, T: Copy> Kind<'a, T> {
451         fn range(&self) -> &'a SpannedRange<T> {
452             match *self {
453                 Kind::Start(_, r) |
454                 Kind::End(_, r) => r,
455             }
456         }
457
458         fn value(self) -> T {
459             match self {
460                 Kind::Start(t, _) |
461                 Kind::End(t, _) => t,
462             }
463         }
464     }
465
466     impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, T> {
467         fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
468             Some(self.cmp(other))
469         }
470     }
471
472     impl<'a, T: Copy + Ord> Ord for Kind<'a, T> {
473         fn cmp(&self, other: &Self) -> Ordering {
474             self.value().cmp(&other.value())
475         }
476     }
477
478     let mut values = Vec::with_capacity(2 * ranges.len());
479
480     for r in ranges {
481         values.push(Kind::Start(r.node.0, r));
482         values.push(Kind::End(r.node.1, r));
483     }
484
485     values.sort();
486
487     for (a, b) in values.iter().zip(values.iter().skip(1)) {
488         match (a, b) {
489             (&Kind::Start(_, ra), &Kind::End(_, rb)) => {
490                 if ra.node != rb.node {
491                     return Some((ra, rb));
492                 }
493             },
494             (&Kind::End(a, _), &Kind::Start(b, _)) if a != b => (),
495             _ => return Some((a.range(), b.range())),
496         }
497     }
498
499     None
500 }