]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches/mod.rs
Split out `match_as_ref`
[rust.git] / clippy_lints / src / matches / mod.rs
1 use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
2 use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability};
3 use clippy_utils::sugg::Sugg;
4 use clippy_utils::{
5     get_parent_expr, is_refutable, is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, strip_pat_refs,
6 };
7 use core::iter::once;
8 use if_chain::if_chain;
9 use rustc_errors::Applicability;
10 use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, PatKind, QPath};
11 use rustc_lint::{LateContext, LateLintPass};
12 use rustc_middle::ty;
13 use rustc_semver::RustcVersion;
14 use rustc_session::{declare_tool_lint, impl_lint_pass};
15
16 mod match_as_ref;
17 mod match_bool;
18 mod match_like_matches;
19 mod match_same_arms;
20 mod match_wild_enum;
21 mod match_wild_err_arm;
22 mod overlapping_arms;
23 mod redundant_pattern_match;
24 mod single_match;
25
26 declare_clippy_lint! {
27     /// ### What it does
28     /// Checks for matches with a single arm where an `if let`
29     /// will usually suffice.
30     ///
31     /// ### Why is this bad?
32     /// Just readability – `if let` nests less than a `match`.
33     ///
34     /// ### Example
35     /// ```rust
36     /// # fn bar(stool: &str) {}
37     /// # let x = Some("abc");
38     /// // Bad
39     /// match x {
40     ///     Some(ref foo) => bar(foo),
41     ///     _ => (),
42     /// }
43     ///
44     /// // Good
45     /// if let Some(ref foo) = x {
46     ///     bar(foo);
47     /// }
48     /// ```
49     #[clippy::version = "pre 1.29.0"]
50     pub SINGLE_MATCH,
51     style,
52     "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
53 }
54
55 declare_clippy_lint! {
56     /// ### What it does
57     /// Checks for matches with two arms where an `if let else` will
58     /// usually suffice.
59     ///
60     /// ### Why is this bad?
61     /// Just readability – `if let` nests less than a `match`.
62     ///
63     /// ### Known problems
64     /// Personal style preferences may differ.
65     ///
66     /// ### Example
67     /// Using `match`:
68     ///
69     /// ```rust
70     /// # fn bar(foo: &usize) {}
71     /// # let other_ref: usize = 1;
72     /// # let x: Option<&usize> = Some(&1);
73     /// match x {
74     ///     Some(ref foo) => bar(foo),
75     ///     _ => bar(&other_ref),
76     /// }
77     /// ```
78     ///
79     /// Using `if let` with `else`:
80     ///
81     /// ```rust
82     /// # fn bar(foo: &usize) {}
83     /// # let other_ref: usize = 1;
84     /// # let x: Option<&usize> = Some(&1);
85     /// if let Some(ref foo) = x {
86     ///     bar(foo);
87     /// } else {
88     ///     bar(&other_ref);
89     /// }
90     /// ```
91     #[clippy::version = "pre 1.29.0"]
92     pub SINGLE_MATCH_ELSE,
93     pedantic,
94     "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
95 }
96
97 declare_clippy_lint! {
98     /// ### What it does
99     /// Checks for matches where all arms match a reference,
100     /// suggesting to remove the reference and deref the matched expression
101     /// instead. It also checks for `if let &foo = bar` blocks.
102     ///
103     /// ### Why is this bad?
104     /// It just makes the code less readable. That reference
105     /// destructuring adds nothing to the code.
106     ///
107     /// ### Example
108     /// ```rust,ignore
109     /// // Bad
110     /// match x {
111     ///     &A(ref y) => foo(y),
112     ///     &B => bar(),
113     ///     _ => frob(&x),
114     /// }
115     ///
116     /// // Good
117     /// match *x {
118     ///     A(ref y) => foo(y),
119     ///     B => bar(),
120     ///     _ => frob(x),
121     /// }
122     /// ```
123     #[clippy::version = "pre 1.29.0"]
124     pub MATCH_REF_PATS,
125     style,
126     "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
127 }
128
129 declare_clippy_lint! {
130     /// ### What it does
131     /// Checks for matches where match expression is a `bool`. It
132     /// suggests to replace the expression with an `if...else` block.
133     ///
134     /// ### Why is this bad?
135     /// It makes the code less readable.
136     ///
137     /// ### Example
138     /// ```rust
139     /// # fn foo() {}
140     /// # fn bar() {}
141     /// let condition: bool = true;
142     /// match condition {
143     ///     true => foo(),
144     ///     false => bar(),
145     /// }
146     /// ```
147     /// Use if/else instead:
148     /// ```rust
149     /// # fn foo() {}
150     /// # fn bar() {}
151     /// let condition: bool = true;
152     /// if condition {
153     ///     foo();
154     /// } else {
155     ///     bar();
156     /// }
157     /// ```
158     #[clippy::version = "pre 1.29.0"]
159     pub MATCH_BOOL,
160     pedantic,
161     "a `match` on a boolean expression instead of an `if..else` block"
162 }
163
164 declare_clippy_lint! {
165     /// ### What it does
166     /// Checks for overlapping match arms.
167     ///
168     /// ### Why is this bad?
169     /// It is likely to be an error and if not, makes the code
170     /// less obvious.
171     ///
172     /// ### Example
173     /// ```rust
174     /// let x = 5;
175     /// match x {
176     ///     1..=10 => println!("1 ... 10"),
177     ///     5..=15 => println!("5 ... 15"),
178     ///     _ => (),
179     /// }
180     /// ```
181     #[clippy::version = "pre 1.29.0"]
182     pub MATCH_OVERLAPPING_ARM,
183     style,
184     "a `match` with overlapping arms"
185 }
186
187 declare_clippy_lint! {
188     /// ### What it does
189     /// Checks for arm which matches all errors with `Err(_)`
190     /// and take drastic actions like `panic!`.
191     ///
192     /// ### Why is this bad?
193     /// It is generally a bad practice, similar to
194     /// catching all exceptions in java with `catch(Exception)`
195     ///
196     /// ### Example
197     /// ```rust
198     /// let x: Result<i32, &str> = Ok(3);
199     /// match x {
200     ///     Ok(_) => println!("ok"),
201     ///     Err(_) => panic!("err"),
202     /// }
203     /// ```
204     #[clippy::version = "pre 1.29.0"]
205     pub MATCH_WILD_ERR_ARM,
206     pedantic,
207     "a `match` with `Err(_)` arm and take drastic actions"
208 }
209
210 declare_clippy_lint! {
211     /// ### What it does
212     /// Checks for match which is used to add a reference to an
213     /// `Option` value.
214     ///
215     /// ### Why is this bad?
216     /// Using `as_ref()` or `as_mut()` instead is shorter.
217     ///
218     /// ### Example
219     /// ```rust
220     /// let x: Option<()> = None;
221     ///
222     /// // Bad
223     /// let r: Option<&()> = match x {
224     ///     None => None,
225     ///     Some(ref v) => Some(v),
226     /// };
227     ///
228     /// // Good
229     /// let r: Option<&()> = x.as_ref();
230     /// ```
231     #[clippy::version = "pre 1.29.0"]
232     pub MATCH_AS_REF,
233     complexity,
234     "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
235 }
236
237 declare_clippy_lint! {
238     /// ### What it does
239     /// Checks for wildcard enum matches using `_`.
240     ///
241     /// ### Why is this bad?
242     /// New enum variants added by library updates can be missed.
243     ///
244     /// ### Known problems
245     /// Suggested replacements may be incorrect if guards exhaustively cover some
246     /// variants, and also may not use correct path to enum if it's not present in the current scope.
247     ///
248     /// ### Example
249     /// ```rust
250     /// # enum Foo { A(usize), B(usize) }
251     /// # let x = Foo::B(1);
252     /// // Bad
253     /// match x {
254     ///     Foo::A(_) => {},
255     ///     _ => {},
256     /// }
257     ///
258     /// // Good
259     /// match x {
260     ///     Foo::A(_) => {},
261     ///     Foo::B(_) => {},
262     /// }
263     /// ```
264     #[clippy::version = "1.34.0"]
265     pub WILDCARD_ENUM_MATCH_ARM,
266     restriction,
267     "a wildcard enum match arm using `_`"
268 }
269
270 declare_clippy_lint! {
271     /// ### What it does
272     /// Checks for wildcard enum matches for a single variant.
273     ///
274     /// ### Why is this bad?
275     /// New enum variants added by library updates can be missed.
276     ///
277     /// ### Known problems
278     /// Suggested replacements may not use correct path to enum
279     /// if it's not present in the current scope.
280     ///
281     /// ### Example
282     /// ```rust
283     /// # enum Foo { A, B, C }
284     /// # let x = Foo::B;
285     /// // Bad
286     /// match x {
287     ///     Foo::A => {},
288     ///     Foo::B => {},
289     ///     _ => {},
290     /// }
291     ///
292     /// // Good
293     /// match x {
294     ///     Foo::A => {},
295     ///     Foo::B => {},
296     ///     Foo::C => {},
297     /// }
298     /// ```
299     #[clippy::version = "1.45.0"]
300     pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
301     pedantic,
302     "a wildcard enum match for a single variant"
303 }
304
305 declare_clippy_lint! {
306     /// ### What it does
307     /// Checks for wildcard pattern used with others patterns in same match arm.
308     ///
309     /// ### Why is this bad?
310     /// Wildcard pattern already covers any other pattern as it will match anyway.
311     /// It makes the code less readable, especially to spot wildcard pattern use in match arm.
312     ///
313     /// ### Example
314     /// ```rust
315     /// // Bad
316     /// match "foo" {
317     ///     "a" => {},
318     ///     "bar" | _ => {},
319     /// }
320     ///
321     /// // Good
322     /// match "foo" {
323     ///     "a" => {},
324     ///     _ => {},
325     /// }
326     /// ```
327     #[clippy::version = "1.42.0"]
328     pub WILDCARD_IN_OR_PATTERNS,
329     complexity,
330     "a wildcard pattern used with others patterns in same match arm"
331 }
332
333 declare_clippy_lint! {
334     /// ### What it does
335     /// Checks for matches being used to destructure a single-variant enum
336     /// or tuple struct where a `let` will suffice.
337     ///
338     /// ### Why is this bad?
339     /// Just readability – `let` doesn't nest, whereas a `match` does.
340     ///
341     /// ### Example
342     /// ```rust
343     /// enum Wrapper {
344     ///     Data(i32),
345     /// }
346     ///
347     /// let wrapper = Wrapper::Data(42);
348     ///
349     /// let data = match wrapper {
350     ///     Wrapper::Data(i) => i,
351     /// };
352     /// ```
353     ///
354     /// The correct use would be:
355     /// ```rust
356     /// enum Wrapper {
357     ///     Data(i32),
358     /// }
359     ///
360     /// let wrapper = Wrapper::Data(42);
361     /// let Wrapper::Data(data) = wrapper;
362     /// ```
363     #[clippy::version = "pre 1.29.0"]
364     pub INFALLIBLE_DESTRUCTURING_MATCH,
365     style,
366     "a `match` statement with a single infallible arm instead of a `let`"
367 }
368
369 declare_clippy_lint! {
370     /// ### What it does
371     /// Checks for useless match that binds to only one value.
372     ///
373     /// ### Why is this bad?
374     /// Readability and needless complexity.
375     ///
376     /// ### Known problems
377     ///  Suggested replacements may be incorrect when `match`
378     /// is actually binding temporary value, bringing a 'dropped while borrowed' error.
379     ///
380     /// ### Example
381     /// ```rust
382     /// # let a = 1;
383     /// # let b = 2;
384     ///
385     /// // Bad
386     /// match (a, b) {
387     ///     (c, d) => {
388     ///         // useless match
389     ///     }
390     /// }
391     ///
392     /// // Good
393     /// let (c, d) = (a, b);
394     /// ```
395     #[clippy::version = "1.43.0"]
396     pub MATCH_SINGLE_BINDING,
397     complexity,
398     "a match with a single binding instead of using `let` statement"
399 }
400
401 declare_clippy_lint! {
402     /// ### What it does
403     /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched.
404     ///
405     /// ### Why is this bad?
406     /// Correctness and readability. It's like having a wildcard pattern after
407     /// matching all enum variants explicitly.
408     ///
409     /// ### Example
410     /// ```rust
411     /// # struct A { a: i32 }
412     /// let a = A { a: 5 };
413     ///
414     /// // Bad
415     /// match a {
416     ///     A { a: 5, .. } => {},
417     ///     _ => {},
418     /// }
419     ///
420     /// // Good
421     /// match a {
422     ///     A { a: 5 } => {},
423     ///     _ => {},
424     /// }
425     /// ```
426     #[clippy::version = "1.43.0"]
427     pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
428     restriction,
429     "a match on a struct that binds all fields but still uses the wildcard pattern"
430 }
431
432 declare_clippy_lint! {
433     /// ### What it does
434     /// Lint for redundant pattern matching over `Result`, `Option`,
435     /// `std::task::Poll` or `std::net::IpAddr`
436     ///
437     /// ### Why is this bad?
438     /// It's more concise and clear to just use the proper
439     /// utility function
440     ///
441     /// ### Known problems
442     /// This will change the drop order for the matched type. Both `if let` and
443     /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
444     /// value before entering the block. For most types this change will not matter, but for a few
445     /// types this will not be an acceptable change (e.g. locks). See the
446     /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
447     /// drop order.
448     ///
449     /// ### Example
450     /// ```rust
451     /// # use std::task::Poll;
452     /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
453     /// if let Ok(_) = Ok::<i32, i32>(42) {}
454     /// if let Err(_) = Err::<i32, i32>(42) {}
455     /// if let None = None::<()> {}
456     /// if let Some(_) = Some(42) {}
457     /// if let Poll::Pending = Poll::Pending::<()> {}
458     /// if let Poll::Ready(_) = Poll::Ready(42) {}
459     /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
460     /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
461     /// match Ok::<i32, i32>(42) {
462     ///     Ok(_) => true,
463     ///     Err(_) => false,
464     /// };
465     /// ```
466     ///
467     /// The more idiomatic use would be:
468     ///
469     /// ```rust
470     /// # use std::task::Poll;
471     /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
472     /// if Ok::<i32, i32>(42).is_ok() {}
473     /// if Err::<i32, i32>(42).is_err() {}
474     /// if None::<()>.is_none() {}
475     /// if Some(42).is_some() {}
476     /// if Poll::Pending::<()>.is_pending() {}
477     /// if Poll::Ready(42).is_ready() {}
478     /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
479     /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
480     /// Ok::<i32, i32>(42).is_ok();
481     /// ```
482     #[clippy::version = "1.31.0"]
483     pub REDUNDANT_PATTERN_MATCHING,
484     style,
485     "use the proper utility function avoiding an `if let`"
486 }
487
488 declare_clippy_lint! {
489     /// ### What it does
490     /// Checks for `match`  or `if let` expressions producing a
491     /// `bool` that could be written using `matches!`
492     ///
493     /// ### Why is this bad?
494     /// Readability and needless complexity.
495     ///
496     /// ### Known problems
497     /// This lint falsely triggers, if there are arms with
498     /// `cfg` attributes that remove an arm evaluating to `false`.
499     ///
500     /// ### Example
501     /// ```rust
502     /// let x = Some(5);
503     ///
504     /// // Bad
505     /// let a = match x {
506     ///     Some(0) => true,
507     ///     _ => false,
508     /// };
509     ///
510     /// let a = if let Some(0) = x {
511     ///     true
512     /// } else {
513     ///     false
514     /// };
515     ///
516     /// // Good
517     /// let a = matches!(x, Some(0));
518     /// ```
519     #[clippy::version = "1.47.0"]
520     pub MATCH_LIKE_MATCHES_MACRO,
521     style,
522     "a match that could be written with the matches! macro"
523 }
524
525 declare_clippy_lint! {
526     /// ### What it does
527     /// Checks for `match` with identical arm bodies.
528     ///
529     /// ### Why is this bad?
530     /// This is probably a copy & paste error. If arm bodies
531     /// are the same on purpose, you can factor them
532     /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
533     ///
534     /// ### Known problems
535     /// False positive possible with order dependent `match`
536     /// (see issue
537     /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
538     ///
539     /// ### Example
540     /// ```rust,ignore
541     /// match foo {
542     ///     Bar => bar(),
543     ///     Quz => quz(),
544     ///     Baz => bar(), // <= oops
545     /// }
546     /// ```
547     ///
548     /// This should probably be
549     /// ```rust,ignore
550     /// match foo {
551     ///     Bar => bar(),
552     ///     Quz => quz(),
553     ///     Baz => baz(), // <= fixed
554     /// }
555     /// ```
556     ///
557     /// or if the original code was not a typo:
558     /// ```rust,ignore
559     /// match foo {
560     ///     Bar | Baz => bar(), // <= shows the intent better
561     ///     Quz => quz(),
562     /// }
563     /// ```
564     #[clippy::version = "pre 1.29.0"]
565     pub MATCH_SAME_ARMS,
566     pedantic,
567     "`match` with identical arm bodies"
568 }
569
570 #[derive(Default)]
571 pub struct Matches {
572     msrv: Option<RustcVersion>,
573     infallible_destructuring_match_linted: bool,
574 }
575
576 impl Matches {
577     #[must_use]
578     pub fn new(msrv: Option<RustcVersion>) -> Self {
579         Self {
580             msrv,
581             ..Matches::default()
582         }
583     }
584 }
585
586 impl_lint_pass!(Matches => [
587     SINGLE_MATCH,
588     MATCH_REF_PATS,
589     MATCH_BOOL,
590     SINGLE_MATCH_ELSE,
591     MATCH_OVERLAPPING_ARM,
592     MATCH_WILD_ERR_ARM,
593     MATCH_AS_REF,
594     WILDCARD_ENUM_MATCH_ARM,
595     MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
596     WILDCARD_IN_OR_PATTERNS,
597     MATCH_SINGLE_BINDING,
598     INFALLIBLE_DESTRUCTURING_MATCH,
599     REST_PAT_IN_FULLY_BOUND_STRUCTS,
600     REDUNDANT_PATTERN_MATCHING,
601     MATCH_LIKE_MATCHES_MACRO,
602     MATCH_SAME_ARMS,
603 ]);
604
605 impl<'tcx> LateLintPass<'tcx> for Matches {
606     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
607         if expr.span.from_expansion() {
608             return;
609         }
610
611         redundant_pattern_match::check(cx, expr);
612
613         if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) {
614             if !match_like_matches::check(cx, expr) {
615                 match_same_arms::check(cx, expr);
616             }
617         } else {
618             match_same_arms::check(cx, expr);
619         }
620
621         if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind {
622             single_match::check(cx, ex, arms, expr);
623             match_bool::check(cx, ex, arms, expr);
624             overlapping_arms::check(cx, ex, arms);
625             match_wild_err_arm::check(cx, ex, arms);
626             match_wild_enum::check(cx, ex, arms);
627             match_as_ref::check(cx, ex, arms, expr);
628             check_wild_in_or_pats(cx, arms);
629
630             if self.infallible_destructuring_match_linted {
631                 self.infallible_destructuring_match_linted = false;
632             } else {
633                 check_match_single_binding(cx, ex, arms, expr);
634             }
635         }
636         if let ExprKind::Match(ex, arms, _) = expr.kind {
637             check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr);
638         }
639     }
640
641     fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
642         if_chain! {
643             if !local.span.from_expansion();
644             if let Some(expr) = local.init;
645             if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
646             if arms.len() == 1 && arms[0].guard.is_none();
647             if let PatKind::TupleStruct(
648                 QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
649             if args.len() == 1;
650             if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind;
651             let body = peel_blocks(arms[0].body);
652             if path_to_local_id(body, arg);
653
654             then {
655                 let mut applicability = Applicability::MachineApplicable;
656                 self.infallible_destructuring_match_linted = true;
657                 span_lint_and_sugg(
658                     cx,
659                     INFALLIBLE_DESTRUCTURING_MATCH,
660                     local.span,
661                     "you seem to be trying to use `match` to destructure a single infallible pattern. \
662                     Consider using `let`",
663                     "try this",
664                     format!(
665                         "let {}({}) = {};",
666                         snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
667                         snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
668                         snippet_with_applicability(cx, target.span, "..", &mut applicability),
669                     ),
670                     applicability,
671                 );
672             }
673         }
674     }
675
676     fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
677         if_chain! {
678             if !pat.span.from_expansion();
679             if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind;
680             if let Some(def_id) = path.res.opt_def_id();
681             let ty = cx.tcx.type_of(def_id);
682             if let ty::Adt(def, _) = ty.kind();
683             if def.is_struct() || def.is_union();
684             if fields.len() == def.non_enum_variant().fields.len();
685
686             then {
687                 span_lint_and_help(
688                     cx,
689                     REST_PAT_IN_FULLY_BOUND_STRUCTS,
690                     pat.span,
691                     "unnecessary use of `..` pattern in struct binding. All fields were already bound",
692                     None,
693                     "consider removing `..` from this binding",
694                 );
695             }
696         }
697     }
698
699     extract_msrv_attr!(LateContext);
700 }
701
702 fn check_match_ref_pats<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>)
703 where
704     'b: 'a,
705     I: Clone + Iterator<Item = &'a Pat<'b>>,
706 {
707     if !has_multiple_ref_pats(pats.clone()) {
708         return;
709     }
710
711     let (first_sugg, msg, title);
712     let span = ex.span.source_callsite();
713     if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind {
714         first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
715         msg = "try";
716         title = "you don't need to add `&` to both the expression and the patterns";
717     } else {
718         first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string()));
719         msg = "instead of prefixing all patterns with `&`, you can dereference the expression";
720         title = "you don't need to add `&` to all patterns";
721     }
722
723     let remaining_suggs = pats.filter_map(|pat| {
724         if let PatKind::Ref(refp, _) = pat.kind {
725             Some((pat.span, snippet(cx, refp.span, "..").to_string()))
726         } else {
727             None
728         }
729     });
730
731     span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| {
732         if !expr.span.from_expansion() {
733             multispan_sugg(diag, msg, first_sugg.chain(remaining_suggs));
734         }
735     });
736 }
737
738 fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) {
739     for arm in arms {
740         if let PatKind::Or(fields) = arm.pat.kind {
741             // look for multiple fields in this arm that contains at least one Wild pattern
742             if fields.len() > 1 && fields.iter().any(is_wild) {
743                 span_lint_and_help(
744                     cx,
745                     WILDCARD_IN_OR_PATTERNS,
746                     arm.pat.span,
747                     "wildcard pattern covers any other pattern as it will match anyway",
748                     None,
749                     "consider handling `_` separately",
750                 );
751             }
752         }
753     }
754 }
755
756 #[allow(clippy::too_many_lines)]
757 fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) {
758     if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
759         return;
760     }
761
762     // HACK:
763     // This is a hack to deal with arms that are excluded by macros like `#[cfg]`. It is only used here
764     // to prevent false positives as there is currently no better way to detect if code was excluded by
765     // a macro. See PR #6435
766     if_chain! {
767         if let Some(match_snippet) = snippet_opt(cx, expr.span);
768         if let Some(arm_snippet) = snippet_opt(cx, arms[0].span);
769         if let Some(ex_snippet) = snippet_opt(cx, ex.span);
770         let rest_snippet = match_snippet.replace(&arm_snippet, "").replace(&ex_snippet, "");
771         if rest_snippet.contains("=>");
772         then {
773             // The code it self contains another thick arrow "=>"
774             // -> Either another arm or a comment
775             return;
776         }
777     }
778
779     let matched_vars = ex.span;
780     let bind_names = arms[0].pat.span;
781     let match_body = peel_blocks(arms[0].body);
782     let mut snippet_body = if match_body.span.from_expansion() {
783         Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
784     } else {
785         snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
786     };
787
788     // Do we need to add ';' to suggestion ?
789     match match_body.kind {
790         ExprKind::Block(block, _) => {
791             // macro + expr_ty(body) == ()
792             if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
793                 snippet_body.push(';');
794             }
795         },
796         _ => {
797             // expr_ty(body) == ()
798             if cx.typeck_results().expr_ty(match_body).is_unit() {
799                 snippet_body.push(';');
800             }
801         },
802     }
803
804     let mut applicability = Applicability::MaybeIncorrect;
805     match arms[0].pat.kind {
806         PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
807             // If this match is in a local (`let`) stmt
808             let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) {
809                 (
810                     parent_let_node.span,
811                     format!(
812                         "let {} = {};\n{}let {} = {};",
813                         snippet_with_applicability(cx, bind_names, "..", &mut applicability),
814                         snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
815                         " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
816                         snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability),
817                         snippet_body
818                     ),
819                 )
820             } else {
821                 // If we are in closure, we need curly braces around suggestion
822                 let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
823                 let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string());
824                 if let Some(parent_expr) = get_parent_expr(cx, expr) {
825                     if let ExprKind::Closure(..) = parent_expr.kind {
826                         cbrace_end = format!("\n{}}}", indent);
827                         // Fix body indent due to the closure
828                         indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
829                         cbrace_start = format!("{{\n{}", indent);
830                     }
831                 }
832                 // If the parent is already an arm, and the body is another match statement,
833                 // we need curly braces around suggestion
834                 let parent_node_id = cx.tcx.hir().get_parent_node(expr.hir_id);
835                 if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
836                     if let ExprKind::Match(..) = arm.body.kind {
837                         cbrace_end = format!("\n{}}}", indent);
838                         // Fix body indent due to the match
839                         indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
840                         cbrace_start = format!("{{\n{}", indent);
841                     }
842                 }
843                 (
844                     expr.span,
845                     format!(
846                         "{}let {} = {};\n{}{}{}",
847                         cbrace_start,
848                         snippet_with_applicability(cx, bind_names, "..", &mut applicability),
849                         snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
850                         indent,
851                         snippet_body,
852                         cbrace_end
853                     ),
854                 )
855             };
856             span_lint_and_sugg(
857                 cx,
858                 MATCH_SINGLE_BINDING,
859                 target_span,
860                 "this match could be written as a `let` statement",
861                 "consider using `let` statement",
862                 sugg,
863                 applicability,
864             );
865         },
866         PatKind::Wild => {
867             if ex.can_have_side_effects() {
868                 let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0));
869                 let sugg = format!(
870                     "{};\n{}{}",
871                     snippet_with_applicability(cx, ex.span, "..", &mut applicability),
872                     indent,
873                     snippet_body
874                 );
875                 span_lint_and_sugg(
876                     cx,
877                     MATCH_SINGLE_BINDING,
878                     expr.span,
879                     "this match could be replaced by its scrutinee and body",
880                     "consider using the scrutinee and body instead",
881                     sugg,
882                     applicability,
883                 );
884             } else {
885                 span_lint_and_sugg(
886                     cx,
887                     MATCH_SINGLE_BINDING,
888                     expr.span,
889                     "this match could be replaced by its body itself",
890                     "consider using the match body instead",
891                     snippet_body,
892                     Applicability::MachineApplicable,
893                 );
894             }
895         },
896         _ => (),
897     }
898 }
899
900 /// Returns true if the `ex` match expression is in a local (`let`) statement
901 fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> {
902     let map = &cx.tcx.hir();
903     if_chain! {
904         if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id));
905         if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id));
906         then {
907             return Some(parent_let_expr);
908         }
909     }
910     None
911 }
912
913 fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool
914 where
915     'b: 'a,
916     I: Iterator<Item = &'a Pat<'b>>,
917 {
918     let mut ref_count = 0;
919     for opt in pats.map(|pat| match pat.kind {
920         PatKind::Ref(..) => Some(true), // &-patterns
921         PatKind::Wild => Some(false),   // an "anything" wildcard is also fine
922         _ => None,                      // any other pattern is not fine
923     }) {
924         if let Some(inner) = opt {
925             if inner {
926                 ref_count += 1;
927             }
928         } else {
929             return false;
930         }
931     }
932     ref_count > 1
933 }