]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches.rs
Add wild and struct handling
[rust.git] / clippy_lints / src / matches.rs
1 use crate::consts::{constant, miri_to_const, Constant};
2 use crate::utils::paths;
3 use crate::utils::sugg::Sugg;
4 use crate::utils::usage::is_unused;
5 use crate::utils::{
6     expr_block, get_arg_name, in_macro, is_allowed, is_expn_of, is_refutable, is_wild, match_qpath, match_type,
7     match_var, multispan_sugg, remove_blocks, snippet, snippet_block, snippet_with_applicability, span_lint_and_help,
8     span_lint_and_note, span_lint_and_sugg, span_lint_and_then, walk_ptrs_ty,
9 };
10 use if_chain::if_chain;
11 use rustc::lint::in_external_macro;
12 use rustc::ty::{self, Ty};
13 use rustc_errors::Applicability;
14 use rustc_hir::def::CtorKind;
15 use rustc_hir::*;
16 use rustc_lint::{LateContext, LateLintPass, LintContext};
17 use rustc_session::{declare_tool_lint, impl_lint_pass};
18 use rustc_span::source_map::Span;
19 use std::cmp::Ordering;
20 use std::collections::Bound;
21 use syntax::ast::LitKind;
22
23 declare_clippy_lint! {
24     /// **What it does:** Checks for matches with a single arm where an `if let`
25     /// will usually suffice.
26     ///
27     /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
28     ///
29     /// **Known problems:** None.
30     ///
31     /// **Example:**
32     /// ```rust
33     /// # fn bar(stool: &str) {}
34     /// # let x = Some("abc");
35     /// match x {
36     ///     Some(ref foo) => bar(foo),
37     ///     _ => (),
38     /// }
39     /// ```
40     pub SINGLE_MATCH,
41     style,
42     "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
43 }
44
45 declare_clippy_lint! {
46     /// **What it does:** Checks for matches with two arms where an `if let else` will
47     /// usually suffice.
48     ///
49     /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
50     ///
51     /// **Known problems:** Personal style preferences may differ.
52     ///
53     /// **Example:**
54     ///
55     /// Using `match`:
56     ///
57     /// ```rust
58     /// # fn bar(foo: &usize) {}
59     /// # let other_ref: usize = 1;
60     /// # let x: Option<&usize> = Some(&1);
61     /// match x {
62     ///     Some(ref foo) => bar(foo),
63     ///     _ => bar(&other_ref),
64     /// }
65     /// ```
66     ///
67     /// Using `if let` with `else`:
68     ///
69     /// ```rust
70     /// # fn bar(foo: &usize) {}
71     /// # let other_ref: usize = 1;
72     /// # let x: Option<&usize> = Some(&1);
73     /// if let Some(ref foo) = x {
74     ///     bar(foo);
75     /// } else {
76     ///     bar(&other_ref);
77     /// }
78     /// ```
79     pub SINGLE_MATCH_ELSE,
80     pedantic,
81     "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
82 }
83
84 declare_clippy_lint! {
85     /// **What it does:** Checks for matches where all arms match a reference,
86     /// suggesting to remove the reference and deref the matched expression
87     /// instead. It also checks for `if let &foo = bar` blocks.
88     ///
89     /// **Why is this bad?** It just makes the code less readable. That reference
90     /// destructuring adds nothing to the code.
91     ///
92     /// **Known problems:** None.
93     ///
94     /// **Example:**
95     /// ```rust,ignore
96     /// match x {
97     ///     &A(ref y) => foo(y),
98     ///     &B => bar(),
99     ///     _ => frob(&x),
100     /// }
101     /// ```
102     pub MATCH_REF_PATS,
103     style,
104     "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
105 }
106
107 declare_clippy_lint! {
108     /// **What it does:** Checks for matches where match expression is a `bool`. It
109     /// suggests to replace the expression with an `if...else` block.
110     ///
111     /// **Why is this bad?** It makes the code less readable.
112     ///
113     /// **Known problems:** None.
114     ///
115     /// **Example:**
116     /// ```rust
117     /// # fn foo() {}
118     /// # fn bar() {}
119     /// let condition: bool = true;
120     /// match condition {
121     ///     true => foo(),
122     ///     false => bar(),
123     /// }
124     /// ```
125     /// Use if/else instead:
126     /// ```rust
127     /// # fn foo() {}
128     /// # fn bar() {}
129     /// let condition: bool = true;
130     /// if condition {
131     ///     foo();
132     /// } else {
133     ///     bar();
134     /// }
135     /// ```
136     pub MATCH_BOOL,
137     style,
138     "a `match` on a boolean expression instead of an `if..else` block"
139 }
140
141 declare_clippy_lint! {
142     /// **What it does:** Checks for overlapping match arms.
143     ///
144     /// **Why is this bad?** It is likely to be an error and if not, makes the code
145     /// less obvious.
146     ///
147     /// **Known problems:** None.
148     ///
149     /// **Example:**
150     /// ```rust
151     /// let x = 5;
152     /// match x {
153     ///     1...10 => println!("1 ... 10"),
154     ///     5...15 => println!("5 ... 15"),
155     ///     _ => (),
156     /// }
157     /// ```
158     pub MATCH_OVERLAPPING_ARM,
159     style,
160     "a `match` with overlapping arms"
161 }
162
163 declare_clippy_lint! {
164     /// **What it does:** Checks for arm which matches all errors with `Err(_)`
165     /// and take drastic actions like `panic!`.
166     ///
167     /// **Why is this bad?** It is generally a bad practice, just like
168     /// catching all exceptions in java with `catch(Exception)`
169     ///
170     /// **Known problems:** None.
171     ///
172     /// **Example:**
173     /// ```rust
174     /// let x: Result<i32, &str> = Ok(3);
175     /// match x {
176     ///     Ok(_) => println!("ok"),
177     ///     Err(_) => panic!("err"),
178     /// }
179     /// ```
180     pub MATCH_WILD_ERR_ARM,
181     style,
182     "a `match` with `Err(_)` arm and take drastic actions"
183 }
184
185 declare_clippy_lint! {
186     /// **What it does:** Checks for match which is used to add a reference to an
187     /// `Option` value.
188     ///
189     /// **Why is this bad?** Using `as_ref()` or `as_mut()` instead is shorter.
190     ///
191     /// **Known problems:** None.
192     ///
193     /// **Example:**
194     /// ```rust
195     /// let x: Option<()> = None;
196     /// let r: Option<&()> = match x {
197     ///     None => None,
198     ///     Some(ref v) => Some(v),
199     /// };
200     /// ```
201     pub MATCH_AS_REF,
202     complexity,
203     "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
204 }
205
206 declare_clippy_lint! {
207     /// **What it does:** Checks for wildcard enum matches using `_`.
208     ///
209     /// **Why is this bad?** New enum variants added by library updates can be missed.
210     ///
211     /// **Known problems:** Suggested replacements may be incorrect if guards exhaustively cover some
212     /// variants, and also may not use correct path to enum if it's not present in the current scope.
213     ///
214     /// **Example:**
215     /// ```rust
216     /// # enum Foo { A(usize), B(usize) }
217     /// # let x = Foo::B(1);
218     /// match x {
219     ///     A => {},
220     ///     _ => {},
221     /// }
222     /// ```
223     pub WILDCARD_ENUM_MATCH_ARM,
224     restriction,
225     "a wildcard enum match arm using `_`"
226 }
227
228 declare_clippy_lint! {
229     /// **What it does:** Checks for wildcard pattern used with others patterns in same match arm.
230     ///
231     /// **Why is this bad?** Wildcard pattern already covers any other pattern as it will match anyway.
232     /// It makes the code less readable, especially to spot wildcard pattern use in match arm.
233     ///
234     /// **Known problems:** None.
235     ///
236     /// **Example:**
237     /// ```rust
238     /// match "foo" {
239     ///     "a" => {},
240     ///     "bar" | _ => {},
241     /// }
242     /// ```
243     pub WILDCARD_IN_OR_PATTERNS,
244     complexity,
245     "a wildcard pattern used with others patterns in same match arm"
246 }
247
248 declare_clippy_lint! {
249     /// **What it does:** Checks for matches being used to destructure a single-variant enum
250     /// or tuple struct where a `let` will suffice.
251     ///
252     /// **Why is this bad?** Just readability – `let` doesn't nest, whereas a `match` does.
253     ///
254     /// **Known problems:** None.
255     ///
256     /// **Example:**
257     /// ```rust
258     /// enum Wrapper {
259     ///     Data(i32),
260     /// }
261     ///
262     /// let wrapper = Wrapper::Data(42);
263     ///
264     /// let data = match wrapper {
265     ///     Wrapper::Data(i) => i,
266     /// };
267     /// ```
268     ///
269     /// The correct use would be:
270     /// ```rust
271     /// enum Wrapper {
272     ///     Data(i32),
273     /// }
274     ///
275     /// let wrapper = Wrapper::Data(42);
276     /// let Wrapper::Data(data) = wrapper;
277     /// ```
278     pub INFALLIBLE_DESTRUCTURING_MATCH,
279     style,
280     "a `match` statement with a single infallible arm instead of a `let`"
281 }
282
283 declare_clippy_lint! {
284     /// **What it does:** Checks for useless match that binds to only one value.
285     ///
286     /// **Why is this bad?** Readability and needless complexity.
287     ///
288     /// **Known problems:** None.
289     ///
290     /// **Example:**
291     /// ```rust
292     /// # let a = 1;
293     /// # let b = 2;
294     ///
295     /// // Bad
296     /// match (a, b) {
297     ///     (c, d) => {
298     ///         // useless match
299     ///     }
300     /// }
301     ///
302     /// // Good
303     /// let (c, d) = (a, b);
304     /// ```
305     pub MATCH_SINGLE_BINDING,
306     complexity,
307     "a match with a single binding instead of using `let` statement"
308 }
309
310 #[derive(Default)]
311 pub struct Matches {
312     infallible_destructuring_match_linted: bool,
313 }
314
315 impl_lint_pass!(Matches => [
316     SINGLE_MATCH,
317     MATCH_REF_PATS,
318     MATCH_BOOL,
319     SINGLE_MATCH_ELSE,
320     MATCH_OVERLAPPING_ARM,
321     MATCH_WILD_ERR_ARM,
322     MATCH_AS_REF,
323     WILDCARD_ENUM_MATCH_ARM,
324     WILDCARD_IN_OR_PATTERNS,
325     MATCH_SINGLE_BINDING,
326     INFALLIBLE_DESTRUCTURING_MATCH
327 ]);
328
329 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Matches {
330     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
331         if in_external_macro(cx.sess(), expr.span) {
332             return;
333         }
334         if let ExprKind::Match(ref ex, ref arms, MatchSource::Normal) = expr.kind {
335             check_single_match(cx, ex, arms, expr);
336             check_match_bool(cx, ex, arms, expr);
337             check_overlapping_arms(cx, ex, arms);
338             check_wild_err_arm(cx, ex, arms);
339             check_wild_enum_match(cx, ex, arms);
340             check_match_as_ref(cx, ex, arms, expr);
341             check_wild_in_or_pats(cx, arms);
342
343             if self.infallible_destructuring_match_linted {
344                 self.infallible_destructuring_match_linted = false;
345             } else {
346                 check_match_single_binding(cx, ex, arms, expr);
347             }
348         }
349         if let ExprKind::Match(ref ex, ref arms, _) = expr.kind {
350             check_match_ref_pats(cx, ex, arms, expr);
351         }
352     }
353
354     fn check_local(&mut self, cx: &LateContext<'a, 'tcx>, local: &'tcx Local<'_>) {
355         if_chain! {
356             if let Some(ref expr) = local.init;
357             if let ExprKind::Match(ref target, ref arms, MatchSource::Normal) = expr.kind;
358             if arms.len() == 1 && arms[0].guard.is_none();
359             if let PatKind::TupleStruct(
360                 QPath::Resolved(None, ref variant_name), ref args, _) = arms[0].pat.kind;
361             if args.len() == 1;
362             if let Some(arg) = get_arg_name(&args[0]);
363             let body = remove_blocks(&arms[0].body);
364             if match_var(body, arg);
365
366             then {
367                 let mut applicability = Applicability::MachineApplicable;
368                 self.infallible_destructuring_match_linted = true;
369                 span_lint_and_sugg(
370                     cx,
371                     INFALLIBLE_DESTRUCTURING_MATCH,
372                     local.span,
373                     "you seem to be trying to use `match` to destructure a single infallible pattern. \
374                     Consider using `let`",
375                     "try this",
376                     format!(
377                         "let {}({}) = {};",
378                         snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
379                         snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
380                         snippet_with_applicability(cx, target.span, "..", &mut applicability),
381                     ),
382                     applicability,
383                 );
384             }
385         }
386     }
387 }
388
389 #[rustfmt::skip]
390 fn check_single_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
391     if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
392         if let PatKind::Or(..) = arms[0].pat.kind {
393             // don't lint for or patterns for now, this makes
394             // the lint noisy in unnecessary situations
395             return;
396         }
397         let els = remove_blocks(&arms[1].body);
398         let els = if is_unit_expr(els) {
399             None
400         } else if let ExprKind::Block(_, _) = els.kind {
401             // matches with blocks that contain statements are prettier as `if let + else`
402             Some(els)
403         } else {
404             // allow match arms with just expressions
405             return;
406         };
407         let ty = cx.tables.expr_ty(ex);
408         if ty.kind != ty::Bool || is_allowed(cx, MATCH_BOOL, ex.hir_id) {
409             check_single_match_single_pattern(cx, ex, arms, expr, els);
410             check_single_match_opt_like(cx, ex, arms, expr, ty, els);
411         }
412     }
413 }
414
415 fn check_single_match_single_pattern(
416     cx: &LateContext<'_, '_>,
417     ex: &Expr<'_>,
418     arms: &[Arm<'_>],
419     expr: &Expr<'_>,
420     els: Option<&Expr<'_>>,
421 ) {
422     if is_wild(&arms[1].pat) {
423         report_single_match_single_pattern(cx, ex, arms, expr, els);
424     }
425 }
426
427 fn report_single_match_single_pattern(
428     cx: &LateContext<'_, '_>,
429     ex: &Expr<'_>,
430     arms: &[Arm<'_>],
431     expr: &Expr<'_>,
432     els: Option<&Expr<'_>>,
433 ) {
434     let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH };
435     let els_str = els.map_or(String::new(), |els| {
436         format!(" else {}", expr_block(cx, els, None, ".."))
437     });
438     span_lint_and_sugg(
439         cx,
440         lint,
441         expr.span,
442         "you seem to be trying to use match for destructuring a single pattern. Consider using `if \
443          let`",
444         "try this",
445         format!(
446             "if let {} = {} {}{}",
447             snippet(cx, arms[0].pat.span, ".."),
448             snippet(cx, ex.span, ".."),
449             expr_block(cx, &arms[0].body, None, ".."),
450             els_str,
451         ),
452         Applicability::HasPlaceholders,
453     );
454 }
455
456 fn check_single_match_opt_like(
457     cx: &LateContext<'_, '_>,
458     ex: &Expr<'_>,
459     arms: &[Arm<'_>],
460     expr: &Expr<'_>,
461     ty: Ty<'_>,
462     els: Option<&Expr<'_>>,
463 ) {
464     // list of candidate `Enum`s we know will never get any more members
465     let candidates = &[
466         (&paths::COW, "Borrowed"),
467         (&paths::COW, "Cow::Borrowed"),
468         (&paths::COW, "Cow::Owned"),
469         (&paths::COW, "Owned"),
470         (&paths::OPTION, "None"),
471         (&paths::RESULT, "Err"),
472         (&paths::RESULT, "Ok"),
473     ];
474
475     let path = match arms[1].pat.kind {
476         PatKind::TupleStruct(ref path, ref inner, _) => {
477             // Contains any non wildcard patterns (e.g., `Err(err)`)?
478             if !inner.iter().all(is_wild) {
479                 return;
480             }
481             print::to_string(print::NO_ANN, |s| s.print_qpath(path, false))
482         },
483         PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => ident.to_string(),
484         PatKind::Path(ref path) => print::to_string(print::NO_ANN, |s| s.print_qpath(path, false)),
485         _ => return,
486     };
487
488     for &(ty_path, pat_path) in candidates {
489         if path == *pat_path && match_type(cx, ty, ty_path) {
490             report_single_match_single_pattern(cx, ex, arms, expr, els);
491         }
492     }
493 }
494
495 fn check_match_bool(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
496     // Type of expression is `bool`.
497     if cx.tables.expr_ty(ex).kind == ty::Bool {
498         span_lint_and_then(
499             cx,
500             MATCH_BOOL,
501             expr.span,
502             "you seem to be trying to match on a boolean expression",
503             move |db| {
504                 if arms.len() == 2 {
505                     // no guards
506                     let exprs = if let PatKind::Lit(ref arm_bool) = arms[0].pat.kind {
507                         if let ExprKind::Lit(ref lit) = arm_bool.kind {
508                             match lit.node {
509                                 LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
510                                 LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)),
511                                 _ => None,
512                             }
513                         } else {
514                             None
515                         }
516                     } else {
517                         None
518                     };
519
520                     if let Some((true_expr, false_expr)) = exprs {
521                         let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) {
522                             (false, false) => Some(format!(
523                                 "if {} {} else {}",
524                                 snippet(cx, ex.span, "b"),
525                                 expr_block(cx, true_expr, None, ".."),
526                                 expr_block(cx, false_expr, None, "..")
527                             )),
528                             (false, true) => Some(format!(
529                                 "if {} {}",
530                                 snippet(cx, ex.span, "b"),
531                                 expr_block(cx, true_expr, None, "..")
532                             )),
533                             (true, false) => {
534                                 let test = Sugg::hir(cx, ex, "..");
535                                 Some(format!("if {} {}", !test, expr_block(cx, false_expr, None, "..")))
536                             },
537                             (true, true) => None,
538                         };
539
540                         if let Some(sugg) = sugg {
541                             db.span_suggestion(
542                                 expr.span,
543                                 "consider using an `if`/`else` expression",
544                                 sugg,
545                                 Applicability::HasPlaceholders,
546                             );
547                         }
548                     }
549                 }
550             },
551         );
552     }
553 }
554
555 fn check_overlapping_arms<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
556     if arms.len() >= 2 && cx.tables.expr_ty(ex).is_integral() {
557         let ranges = all_ranges(cx, arms, cx.tables.expr_ty(ex));
558         let type_ranges = type_ranges(&ranges);
559         if !type_ranges.is_empty() {
560             if let Some((start, end)) = overlapping(&type_ranges) {
561                 span_lint_and_note(
562                     cx,
563                     MATCH_OVERLAPPING_ARM,
564                     start.span,
565                     "some ranges overlap",
566                     end.span,
567                     "overlaps with this",
568                 );
569             }
570         }
571     }
572 }
573
574 fn check_wild_err_arm(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
575     let ex_ty = walk_ptrs_ty(cx.tables.expr_ty(ex));
576     if match_type(cx, ex_ty, &paths::RESULT) {
577         for arm in arms {
578             if let PatKind::TupleStruct(ref path, ref inner, _) = arm.pat.kind {
579                 let path_str = print::to_string(print::NO_ANN, |s| s.print_qpath(path, false));
580                 if path_str == "Err" {
581                     let mut matching_wild = inner.iter().any(is_wild);
582                     let mut ident_bind_name = String::from("_");
583                     if !matching_wild {
584                         // Looking for unused bindings (i.e.: `_e`)
585                         inner.iter().for_each(|pat| {
586                             if let PatKind::Binding(.., ident, None) = &pat.kind {
587                                 if ident.as_str().starts_with('_') && is_unused(ident, arm.body) {
588                                     ident_bind_name = (&ident.name.as_str()).to_string();
589                                     matching_wild = true;
590                                 }
591                             }
592                         });
593                     }
594                     if_chain! {
595                         if matching_wild;
596                         if let ExprKind::Block(ref block, _) = arm.body.kind;
597                         if is_panic_block(block);
598                         then {
599                             // `Err(_)` or `Err(_e)` arm with `panic!` found
600                             span_lint_and_note(cx,
601                                 MATCH_WILD_ERR_ARM,
602                                 arm.pat.span,
603                                 &format!("`Err({})` matches all errors", &ident_bind_name),
604                                 arm.pat.span,
605                                 "match each error separately or use the error output",
606                             );
607                         }
608                     }
609                 }
610             }
611         }
612     }
613 }
614
615 fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
616     let ty = cx.tables.expr_ty(ex);
617     if !ty.is_enum() {
618         // If there isn't a nice closed set of possible values that can be conveniently enumerated,
619         // don't complain about not enumerating the mall.
620         return;
621     }
622
623     // First pass - check for violation, but don't do much book-keeping because this is hopefully
624     // the uncommon case, and the book-keeping is slightly expensive.
625     let mut wildcard_span = None;
626     let mut wildcard_ident = None;
627     for arm in arms {
628         if let PatKind::Wild = arm.pat.kind {
629             wildcard_span = Some(arm.pat.span);
630         } else if let PatKind::Binding(_, _, ident, None) = arm.pat.kind {
631             wildcard_span = Some(arm.pat.span);
632             wildcard_ident = Some(ident);
633         }
634     }
635
636     if let Some(wildcard_span) = wildcard_span {
637         // Accumulate the variants which should be put in place of the wildcard because they're not
638         // already covered.
639
640         let mut missing_variants = vec![];
641         if let ty::Adt(def, _) = ty.kind {
642             for variant in &def.variants {
643                 missing_variants.push(variant);
644             }
645         }
646
647         for arm in arms {
648             if arm.guard.is_some() {
649                 // Guards mean that this case probably isn't exhaustively covered. Technically
650                 // this is incorrect, as we should really check whether each variant is exhaustively
651                 // covered by the set of guards that cover it, but that's really hard to do.
652                 continue;
653             }
654             if let PatKind::Path(ref path) = arm.pat.kind {
655                 if let QPath::Resolved(_, p) = path {
656                     missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
657                 }
658             } else if let PatKind::TupleStruct(ref path, ..) = arm.pat.kind {
659                 if let QPath::Resolved(_, p) = path {
660                     missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
661                 }
662             }
663         }
664
665         let mut suggestion: Vec<String> = missing_variants
666             .iter()
667             .map(|v| {
668                 let suffix = match v.ctor_kind {
669                     CtorKind::Fn => "(..)",
670                     CtorKind::Const | CtorKind::Fictive => "",
671                 };
672                 let ident_str = if let Some(ident) = wildcard_ident {
673                     format!("{} @ ", ident.name)
674                 } else {
675                     String::new()
676                 };
677                 // This path assumes that the enum type is imported into scope.
678                 format!("{}{}{}", ident_str, cx.tcx.def_path_str(v.def_id), suffix)
679             })
680             .collect();
681
682         if suggestion.is_empty() {
683             return;
684         }
685
686         let mut message = "wildcard match will miss any future added variants";
687
688         if let ty::Adt(def, _) = ty.kind {
689             if def.is_variant_list_non_exhaustive() {
690                 message = "match on non-exhaustive enum doesn't explicitly match all known variants";
691                 suggestion.push(String::from("_"));
692             }
693         }
694
695         span_lint_and_sugg(
696             cx,
697             WILDCARD_ENUM_MATCH_ARM,
698             wildcard_span,
699             message,
700             "try this",
701             suggestion.join(" | "),
702             Applicability::MachineApplicable,
703         )
704     }
705 }
706
707 // If the block contains only a `panic!` macro (as expression or statement)
708 fn is_panic_block(block: &Block<'_>) -> bool {
709     match (&block.expr, block.stmts.len(), block.stmts.first()) {
710         (&Some(ref exp), 0, _) => {
711             is_expn_of(exp.span, "panic").is_some() && is_expn_of(exp.span, "unreachable").is_none()
712         },
713         (&None, 1, Some(stmt)) => {
714             is_expn_of(stmt.span, "panic").is_some() && is_expn_of(stmt.span, "unreachable").is_none()
715         },
716         _ => false,
717     }
718 }
719
720 fn check_match_ref_pats(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
721     if has_only_ref_pats(arms) {
722         let mut suggs = Vec::new();
723         let (title, msg) = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, ref inner) = ex.kind {
724             let span = ex.span.source_callsite();
725             suggs.push((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
726             (
727                 "you don't need to add `&` to both the expression and the patterns",
728                 "try",
729             )
730         } else {
731             let span = ex.span.source_callsite();
732             suggs.push((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string()));
733             (
734                 "you don't need to add `&` to all patterns",
735                 "instead of prefixing all patterns with `&`, you can dereference the expression",
736             )
737         };
738
739         suggs.extend(arms.iter().filter_map(|a| {
740             if let PatKind::Ref(ref refp, _) = a.pat.kind {
741                 Some((a.pat.span, snippet(cx, refp.span, "..").to_string()))
742             } else {
743                 None
744             }
745         }));
746
747         span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |db| {
748             if !expr.span.from_expansion() {
749                 multispan_sugg(db, msg.to_owned(), suggs);
750             }
751         });
752     }
753 }
754
755 fn check_match_as_ref(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
756     if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
757         let arm_ref: Option<BindingAnnotation> = if is_none_arm(&arms[0]) {
758             is_ref_some_arm(&arms[1])
759         } else if is_none_arm(&arms[1]) {
760             is_ref_some_arm(&arms[0])
761         } else {
762             None
763         };
764         if let Some(rb) = arm_ref {
765             let suggestion = if rb == BindingAnnotation::Ref {
766                 "as_ref"
767             } else {
768                 "as_mut"
769             };
770
771             let output_ty = cx.tables.expr_ty(expr);
772             let input_ty = cx.tables.expr_ty(ex);
773
774             let cast = if_chain! {
775                 if let ty::Adt(_, substs) = input_ty.kind;
776                 let input_ty = substs.type_at(0);
777                 if let ty::Adt(_, substs) = output_ty.kind;
778                 let output_ty = substs.type_at(0);
779                 if let ty::Ref(_, output_ty, _) = output_ty.kind;
780                 if input_ty != output_ty;
781                 then {
782                     ".map(|x| x as _)"
783                 } else {
784                     ""
785                 }
786             };
787
788             let mut applicability = Applicability::MachineApplicable;
789             span_lint_and_sugg(
790                 cx,
791                 MATCH_AS_REF,
792                 expr.span,
793                 &format!("use `{}()` instead", suggestion),
794                 "try this",
795                 format!(
796                     "{}.{}(){}",
797                     snippet_with_applicability(cx, ex.span, "_", &mut applicability),
798                     suggestion,
799                     cast,
800                 ),
801                 applicability,
802             )
803         }
804     }
805 }
806
807 fn check_wild_in_or_pats(cx: &LateContext<'_, '_>, arms: &[Arm<'_>]) {
808     for arm in arms {
809         if let PatKind::Or(ref fields) = arm.pat.kind {
810             // look for multiple fields in this arm that contains at least one Wild pattern
811             if fields.len() > 1 && fields.iter().any(is_wild) {
812                 span_lint_and_help(
813                     cx,
814                     WILDCARD_IN_OR_PATTERNS,
815                     arm.pat.span,
816                     "wildcard pattern covers any other pattern as it will match anyway.",
817                     "Consider handling `_` separately.",
818                 );
819             }
820         }
821     }
822 }
823
824 fn check_match_single_binding(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
825     if in_macro(expr.span) || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
826         return;
827     }
828     let matched_vars = ex.span;
829     let bind_names = arms[0].pat.span;
830     let match_body = remove_blocks(&arms[0].body);
831     let mut snippet_body = if match_body.span.from_expansion() {
832         Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
833     } else {
834         snippet_block(cx, match_body.span, "..").to_owned().to_string()
835     };
836
837     // Do we need to add ';' to suggestion ?
838     if_chain! {
839         if let ExprKind::Block(block, _) = &arms[0].body.kind;
840         if block.stmts.len() == 1;
841         if let StmtKind::Semi(s) = block.stmts.get(0).unwrap().kind;
842         then {
843             match s.kind {
844                 ExprKind::Block(_, _) => (),
845                 _ => {
846                     // expr_ty(body) == ()
847                     if cx.tables.expr_ty(&arms[0].body).is_unit() {
848                         snippet_body.push(';');
849                     }
850                 }
851             }
852         }
853     }
854
855     match arms[0].pat.kind {
856         PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
857             span_lint_and_sugg(
858                 cx,
859                 MATCH_SINGLE_BINDING,
860                 expr.span,
861                 "this match could be written as a `let` statement",
862                 "consider using `let` statement",
863                 format!(
864                     "let {} = {};\n{}",
865                     snippet(cx, bind_names, ".."),
866                     snippet(cx, matched_vars, ".."),
867                     snippet_body
868                 ),
869                 Applicability::MachineApplicable,
870             );
871         },
872         PatKind::Wild => {
873             span_lint_and_sugg(
874                 cx,
875                 MATCH_SINGLE_BINDING,
876                 expr.span,
877                 "this match could be replaced by its body itself",
878                 "consider using the match body instead",
879                 snippet_body,
880                 Applicability::MachineApplicable,
881             );
882         },
883         _ => (),
884     }
885 }
886
887 /// Gets all arms that are unbounded `PatRange`s.
888 fn all_ranges<'a, 'tcx>(
889     cx: &LateContext<'a, 'tcx>,
890     arms: &'tcx [Arm<'_>],
891     ty: Ty<'tcx>,
892 ) -> Vec<SpannedRange<Constant>> {
893     arms.iter()
894         .flat_map(|arm| {
895             if let Arm {
896                 ref pat, guard: None, ..
897             } = *arm
898             {
899                 if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
900                     let lhs = match lhs {
901                         Some(lhs) => constant(cx, cx.tables, lhs)?.0,
902                         None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?,
903                     };
904                     let rhs = match rhs {
905                         Some(rhs) => constant(cx, cx.tables, rhs)?.0,
906                         None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?,
907                     };
908                     let rhs = match range_end {
909                         RangeEnd::Included => Bound::Included(rhs),
910                         RangeEnd::Excluded => Bound::Excluded(rhs),
911                     };
912                     return Some(SpannedRange {
913                         span: pat.span,
914                         node: (lhs, rhs),
915                     });
916                 }
917
918                 if let PatKind::Lit(ref value) = pat.kind {
919                     let value = constant(cx, cx.tables, value)?.0;
920                     return Some(SpannedRange {
921                         span: pat.span,
922                         node: (value.clone(), Bound::Included(value)),
923                     });
924                 }
925             }
926             None
927         })
928         .collect()
929 }
930
931 #[derive(Debug, Eq, PartialEq)]
932 pub struct SpannedRange<T> {
933     pub span: Span,
934     pub node: (T, Bound<T>),
935 }
936
937 type TypedRanges = Vec<SpannedRange<u128>>;
938
939 /// Gets all `Int` ranges or all `Uint` ranges. Mixed types are an error anyway
940 /// and other types than
941 /// `Uint` and `Int` probably don't make sense.
942 fn type_ranges(ranges: &[SpannedRange<Constant>]) -> TypedRanges {
943     ranges
944         .iter()
945         .filter_map(|range| match range.node {
946             (Constant::Int(start), Bound::Included(Constant::Int(end))) => Some(SpannedRange {
947                 span: range.span,
948                 node: (start, Bound::Included(end)),
949             }),
950             (Constant::Int(start), Bound::Excluded(Constant::Int(end))) => Some(SpannedRange {
951                 span: range.span,
952                 node: (start, Bound::Excluded(end)),
953             }),
954             (Constant::Int(start), Bound::Unbounded) => Some(SpannedRange {
955                 span: range.span,
956                 node: (start, Bound::Unbounded),
957             }),
958             _ => None,
959         })
960         .collect()
961 }
962
963 fn is_unit_expr(expr: &Expr<'_>) -> bool {
964     match expr.kind {
965         ExprKind::Tup(ref v) if v.is_empty() => true,
966         ExprKind::Block(ref b, _) if b.stmts.is_empty() && b.expr.is_none() => true,
967         _ => false,
968     }
969 }
970
971 // Checks if arm has the form `None => None`
972 fn is_none_arm(arm: &Arm<'_>) -> bool {
973     match arm.pat.kind {
974         PatKind::Path(ref path) if match_qpath(path, &paths::OPTION_NONE) => true,
975         _ => false,
976     }
977 }
978
979 // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
980 fn is_ref_some_arm(arm: &Arm<'_>) -> Option<BindingAnnotation> {
981     if_chain! {
982         if let PatKind::TupleStruct(ref path, ref pats, _) = arm.pat.kind;
983         if pats.len() == 1 && match_qpath(path, &paths::OPTION_SOME);
984         if let PatKind::Binding(rb, .., ident, _) = pats[0].kind;
985         if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
986         if let ExprKind::Call(ref e, ref args) = remove_blocks(&arm.body).kind;
987         if let ExprKind::Path(ref some_path) = e.kind;
988         if match_qpath(some_path, &paths::OPTION_SOME) && args.len() == 1;
989         if let ExprKind::Path(ref qpath) = args[0].kind;
990         if let &QPath::Resolved(_, ref path2) = qpath;
991         if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
992         then {
993             return Some(rb)
994         }
995     }
996     None
997 }
998
999 fn has_only_ref_pats(arms: &[Arm<'_>]) -> bool {
1000     let mapped = arms
1001         .iter()
1002         .map(|a| {
1003             match a.pat.kind {
1004                 PatKind::Ref(..) => Some(true), // &-patterns
1005                 PatKind::Wild => Some(false),   // an "anything" wildcard is also fine
1006                 _ => None,                      // any other pattern is not fine
1007             }
1008         })
1009         .collect::<Option<Vec<bool>>>();
1010     // look for Some(v) where there's at least one true element
1011     mapped.map_or(false, |v| v.iter().any(|el| *el))
1012 }
1013
1014 pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
1015 where
1016     T: Copy + Ord,
1017 {
1018     #[derive(Copy, Clone, Debug, Eq, PartialEq)]
1019     enum Kind<'a, T> {
1020         Start(T, &'a SpannedRange<T>),
1021         End(Bound<T>, &'a SpannedRange<T>),
1022     }
1023
1024     impl<'a, T: Copy> Kind<'a, T> {
1025         fn range(&self) -> &'a SpannedRange<T> {
1026             match *self {
1027                 Kind::Start(_, r) | Kind::End(_, r) => r,
1028             }
1029         }
1030
1031         fn value(self) -> Bound<T> {
1032             match self {
1033                 Kind::Start(t, _) => Bound::Included(t),
1034                 Kind::End(t, _) => t,
1035             }
1036         }
1037     }
1038
1039     impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, T> {
1040         fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1041             Some(self.cmp(other))
1042         }
1043     }
1044
1045     impl<'a, T: Copy + Ord> Ord for Kind<'a, T> {
1046         fn cmp(&self, other: &Self) -> Ordering {
1047             match (self.value(), other.value()) {
1048                 (Bound::Included(a), Bound::Included(b)) | (Bound::Excluded(a), Bound::Excluded(b)) => a.cmp(&b),
1049                 // Range patterns cannot be unbounded (yet)
1050                 (Bound::Unbounded, _) | (_, Bound::Unbounded) => unimplemented!(),
1051                 (Bound::Included(a), Bound::Excluded(b)) => match a.cmp(&b) {
1052                     Ordering::Equal => Ordering::Greater,
1053                     other => other,
1054                 },
1055                 (Bound::Excluded(a), Bound::Included(b)) => match a.cmp(&b) {
1056                     Ordering::Equal => Ordering::Less,
1057                     other => other,
1058                 },
1059             }
1060         }
1061     }
1062
1063     let mut values = Vec::with_capacity(2 * ranges.len());
1064
1065     for r in ranges {
1066         values.push(Kind::Start(r.node.0, r));
1067         values.push(Kind::End(r.node.1, r));
1068     }
1069
1070     values.sort();
1071
1072     for (a, b) in values.iter().zip(values.iter().skip(1)) {
1073         match (a, b) {
1074             (&Kind::Start(_, ra), &Kind::End(_, rb)) => {
1075                 if ra.node != rb.node {
1076                     return Some((ra, rb));
1077                 }
1078             },
1079             (&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (),
1080             _ => return Some((a.range(), b.range())),
1081         }
1082     }
1083
1084     None
1085 }