]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches/mod.rs
Move `MatchStrCaseMismatch` into `Matches` lint pass
[rust.git] / clippy_lints / src / matches / mod.rs
1 use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context};
2 use clippy_utils::{higher, in_constant, meets_msrv, msrvs};
3 use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
4 use rustc_lexer::{tokenize, TokenKind};
5 use rustc_lint::{LateContext, LateLintPass, LintContext};
6 use rustc_middle::lint::in_external_macro;
7 use rustc_semver::RustcVersion;
8 use rustc_session::{declare_tool_lint, impl_lint_pass};
9 use rustc_span::{Span, SpanData, SyntaxContext};
10
11 mod collapsible_match;
12 mod infallible_destructuring_match;
13 mod manual_unwrap_or;
14 mod match_as_ref;
15 mod match_bool;
16 mod match_like_matches;
17 mod match_on_vec_items;
18 mod match_ref_pats;
19 mod match_same_arms;
20 mod match_single_binding;
21 mod match_str_case_mismatch;
22 mod match_wild_enum;
23 mod match_wild_err_arm;
24 mod needless_match;
25 mod overlapping_arms;
26 mod redundant_pattern_match;
27 mod rest_pat_in_fully_bound_struct;
28 mod single_match;
29 mod wild_in_or_pats;
30
31 declare_clippy_lint! {
32     /// ### What it does
33     /// Checks for matches with a single arm where an `if let`
34     /// will usually suffice.
35     ///
36     /// ### Why is this bad?
37     /// Just readability – `if let` nests less than a `match`.
38     ///
39     /// ### Example
40     /// ```rust
41     /// # fn bar(stool: &str) {}
42     /// # let x = Some("abc");
43     /// // Bad
44     /// match x {
45     ///     Some(ref foo) => bar(foo),
46     ///     _ => (),
47     /// }
48     ///
49     /// // Good
50     /// if let Some(ref foo) = x {
51     ///     bar(foo);
52     /// }
53     /// ```
54     #[clippy::version = "pre 1.29.0"]
55     pub SINGLE_MATCH,
56     style,
57     "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
58 }
59
60 declare_clippy_lint! {
61     /// ### What it does
62     /// Checks for matches with two arms where an `if let else` will
63     /// usually suffice.
64     ///
65     /// ### Why is this bad?
66     /// Just readability – `if let` nests less than a `match`.
67     ///
68     /// ### Known problems
69     /// Personal style preferences may differ.
70     ///
71     /// ### Example
72     /// Using `match`:
73     ///
74     /// ```rust
75     /// # fn bar(foo: &usize) {}
76     /// # let other_ref: usize = 1;
77     /// # let x: Option<&usize> = Some(&1);
78     /// match x {
79     ///     Some(ref foo) => bar(foo),
80     ///     _ => bar(&other_ref),
81     /// }
82     /// ```
83     ///
84     /// Using `if let` with `else`:
85     ///
86     /// ```rust
87     /// # fn bar(foo: &usize) {}
88     /// # let other_ref: usize = 1;
89     /// # let x: Option<&usize> = Some(&1);
90     /// if let Some(ref foo) = x {
91     ///     bar(foo);
92     /// } else {
93     ///     bar(&other_ref);
94     /// }
95     /// ```
96     #[clippy::version = "pre 1.29.0"]
97     pub SINGLE_MATCH_ELSE,
98     pedantic,
99     "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
100 }
101
102 declare_clippy_lint! {
103     /// ### What it does
104     /// Checks for matches where all arms match a reference,
105     /// suggesting to remove the reference and deref the matched expression
106     /// instead. It also checks for `if let &foo = bar` blocks.
107     ///
108     /// ### Why is this bad?
109     /// It just makes the code less readable. That reference
110     /// destructuring adds nothing to the code.
111     ///
112     /// ### Example
113     /// ```rust,ignore
114     /// // Bad
115     /// match x {
116     ///     &A(ref y) => foo(y),
117     ///     &B => bar(),
118     ///     _ => frob(&x),
119     /// }
120     ///
121     /// // Good
122     /// match *x {
123     ///     A(ref y) => foo(y),
124     ///     B => bar(),
125     ///     _ => frob(x),
126     /// }
127     /// ```
128     #[clippy::version = "pre 1.29.0"]
129     pub MATCH_REF_PATS,
130     style,
131     "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
132 }
133
134 declare_clippy_lint! {
135     /// ### What it does
136     /// Checks for matches where match expression is a `bool`. It
137     /// suggests to replace the expression with an `if...else` block.
138     ///
139     /// ### Why is this bad?
140     /// It makes the code less readable.
141     ///
142     /// ### Example
143     /// ```rust
144     /// # fn foo() {}
145     /// # fn bar() {}
146     /// let condition: bool = true;
147     /// match condition {
148     ///     true => foo(),
149     ///     false => bar(),
150     /// }
151     /// ```
152     /// Use if/else instead:
153     /// ```rust
154     /// # fn foo() {}
155     /// # fn bar() {}
156     /// let condition: bool = true;
157     /// if condition {
158     ///     foo();
159     /// } else {
160     ///     bar();
161     /// }
162     /// ```
163     #[clippy::version = "pre 1.29.0"]
164     pub MATCH_BOOL,
165     pedantic,
166     "a `match` on a boolean expression instead of an `if..else` block"
167 }
168
169 declare_clippy_lint! {
170     /// ### What it does
171     /// Checks for overlapping match arms.
172     ///
173     /// ### Why is this bad?
174     /// It is likely to be an error and if not, makes the code
175     /// less obvious.
176     ///
177     /// ### Example
178     /// ```rust
179     /// let x = 5;
180     /// match x {
181     ///     1..=10 => println!("1 ... 10"),
182     ///     5..=15 => println!("5 ... 15"),
183     ///     _ => (),
184     /// }
185     /// ```
186     #[clippy::version = "pre 1.29.0"]
187     pub MATCH_OVERLAPPING_ARM,
188     style,
189     "a `match` with overlapping arms"
190 }
191
192 declare_clippy_lint! {
193     /// ### What it does
194     /// Checks for arm which matches all errors with `Err(_)`
195     /// and take drastic actions like `panic!`.
196     ///
197     /// ### Why is this bad?
198     /// It is generally a bad practice, similar to
199     /// catching all exceptions in java with `catch(Exception)`
200     ///
201     /// ### Example
202     /// ```rust
203     /// let x: Result<i32, &str> = Ok(3);
204     /// match x {
205     ///     Ok(_) => println!("ok"),
206     ///     Err(_) => panic!("err"),
207     /// }
208     /// ```
209     #[clippy::version = "pre 1.29.0"]
210     pub MATCH_WILD_ERR_ARM,
211     pedantic,
212     "a `match` with `Err(_)` arm and take drastic actions"
213 }
214
215 declare_clippy_lint! {
216     /// ### What it does
217     /// Checks for match which is used to add a reference to an
218     /// `Option` value.
219     ///
220     /// ### Why is this bad?
221     /// Using `as_ref()` or `as_mut()` instead is shorter.
222     ///
223     /// ### Example
224     /// ```rust
225     /// let x: Option<()> = None;
226     ///
227     /// // Bad
228     /// let r: Option<&()> = match x {
229     ///     None => None,
230     ///     Some(ref v) => Some(v),
231     /// };
232     ///
233     /// // Good
234     /// let r: Option<&()> = x.as_ref();
235     /// ```
236     #[clippy::version = "pre 1.29.0"]
237     pub MATCH_AS_REF,
238     complexity,
239     "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
240 }
241
242 declare_clippy_lint! {
243     /// ### What it does
244     /// Checks for wildcard enum matches using `_`.
245     ///
246     /// ### Why is this bad?
247     /// New enum variants added by library updates can be missed.
248     ///
249     /// ### Known problems
250     /// Suggested replacements may be incorrect if guards exhaustively cover some
251     /// variants, and also may not use correct path to enum if it's not present in the current scope.
252     ///
253     /// ### Example
254     /// ```rust
255     /// # enum Foo { A(usize), B(usize) }
256     /// # let x = Foo::B(1);
257     /// // Bad
258     /// match x {
259     ///     Foo::A(_) => {},
260     ///     _ => {},
261     /// }
262     ///
263     /// // Good
264     /// match x {
265     ///     Foo::A(_) => {},
266     ///     Foo::B(_) => {},
267     /// }
268     /// ```
269     #[clippy::version = "1.34.0"]
270     pub WILDCARD_ENUM_MATCH_ARM,
271     restriction,
272     "a wildcard enum match arm using `_`"
273 }
274
275 declare_clippy_lint! {
276     /// ### What it does
277     /// Checks for wildcard enum matches for a single variant.
278     ///
279     /// ### Why is this bad?
280     /// New enum variants added by library updates can be missed.
281     ///
282     /// ### Known problems
283     /// Suggested replacements may not use correct path to enum
284     /// if it's not present in the current scope.
285     ///
286     /// ### Example
287     /// ```rust
288     /// # enum Foo { A, B, C }
289     /// # let x = Foo::B;
290     /// // Bad
291     /// match x {
292     ///     Foo::A => {},
293     ///     Foo::B => {},
294     ///     _ => {},
295     /// }
296     ///
297     /// // Good
298     /// match x {
299     ///     Foo::A => {},
300     ///     Foo::B => {},
301     ///     Foo::C => {},
302     /// }
303     /// ```
304     #[clippy::version = "1.45.0"]
305     pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
306     pedantic,
307     "a wildcard enum match for a single variant"
308 }
309
310 declare_clippy_lint! {
311     /// ### What it does
312     /// Checks for wildcard pattern used with others patterns in same match arm.
313     ///
314     /// ### Why is this bad?
315     /// Wildcard pattern already covers any other pattern as it will match anyway.
316     /// It makes the code less readable, especially to spot wildcard pattern use in match arm.
317     ///
318     /// ### Example
319     /// ```rust
320     /// // Bad
321     /// match "foo" {
322     ///     "a" => {},
323     ///     "bar" | _ => {},
324     /// }
325     ///
326     /// // Good
327     /// match "foo" {
328     ///     "a" => {},
329     ///     _ => {},
330     /// }
331     /// ```
332     #[clippy::version = "1.42.0"]
333     pub WILDCARD_IN_OR_PATTERNS,
334     complexity,
335     "a wildcard pattern used with others patterns in same match arm"
336 }
337
338 declare_clippy_lint! {
339     /// ### What it does
340     /// Checks for matches being used to destructure a single-variant enum
341     /// or tuple struct where a `let` will suffice.
342     ///
343     /// ### Why is this bad?
344     /// Just readability – `let` doesn't nest, whereas a `match` does.
345     ///
346     /// ### Example
347     /// ```rust
348     /// enum Wrapper {
349     ///     Data(i32),
350     /// }
351     ///
352     /// let wrapper = Wrapper::Data(42);
353     ///
354     /// let data = match wrapper {
355     ///     Wrapper::Data(i) => i,
356     /// };
357     /// ```
358     ///
359     /// The correct use would be:
360     /// ```rust
361     /// enum Wrapper {
362     ///     Data(i32),
363     /// }
364     ///
365     /// let wrapper = Wrapper::Data(42);
366     /// let Wrapper::Data(data) = wrapper;
367     /// ```
368     #[clippy::version = "pre 1.29.0"]
369     pub INFALLIBLE_DESTRUCTURING_MATCH,
370     style,
371     "a `match` statement with a single infallible arm instead of a `let`"
372 }
373
374 declare_clippy_lint! {
375     /// ### What it does
376     /// Checks for useless match that binds to only one value.
377     ///
378     /// ### Why is this bad?
379     /// Readability and needless complexity.
380     ///
381     /// ### Known problems
382     ///  Suggested replacements may be incorrect when `match`
383     /// is actually binding temporary value, bringing a 'dropped while borrowed' error.
384     ///
385     /// ### Example
386     /// ```rust
387     /// # let a = 1;
388     /// # let b = 2;
389     ///
390     /// // Bad
391     /// match (a, b) {
392     ///     (c, d) => {
393     ///         // useless match
394     ///     }
395     /// }
396     ///
397     /// // Good
398     /// let (c, d) = (a, b);
399     /// ```
400     #[clippy::version = "1.43.0"]
401     pub MATCH_SINGLE_BINDING,
402     complexity,
403     "a match with a single binding instead of using `let` statement"
404 }
405
406 declare_clippy_lint! {
407     /// ### What it does
408     /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched.
409     ///
410     /// ### Why is this bad?
411     /// Correctness and readability. It's like having a wildcard pattern after
412     /// matching all enum variants explicitly.
413     ///
414     /// ### Example
415     /// ```rust
416     /// # struct A { a: i32 }
417     /// let a = A { a: 5 };
418     ///
419     /// // Bad
420     /// match a {
421     ///     A { a: 5, .. } => {},
422     ///     _ => {},
423     /// }
424     ///
425     /// // Good
426     /// match a {
427     ///     A { a: 5 } => {},
428     ///     _ => {},
429     /// }
430     /// ```
431     #[clippy::version = "1.43.0"]
432     pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
433     restriction,
434     "a match on a struct that binds all fields but still uses the wildcard pattern"
435 }
436
437 declare_clippy_lint! {
438     /// ### What it does
439     /// Lint for redundant pattern matching over `Result`, `Option`,
440     /// `std::task::Poll` or `std::net::IpAddr`
441     ///
442     /// ### Why is this bad?
443     /// It's more concise and clear to just use the proper
444     /// utility function
445     ///
446     /// ### Known problems
447     /// This will change the drop order for the matched type. Both `if let` and
448     /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
449     /// value before entering the block. For most types this change will not matter, but for a few
450     /// types this will not be an acceptable change (e.g. locks). See the
451     /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
452     /// drop order.
453     ///
454     /// ### Example
455     /// ```rust
456     /// # use std::task::Poll;
457     /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
458     /// if let Ok(_) = Ok::<i32, i32>(42) {}
459     /// if let Err(_) = Err::<i32, i32>(42) {}
460     /// if let None = None::<()> {}
461     /// if let Some(_) = Some(42) {}
462     /// if let Poll::Pending = Poll::Pending::<()> {}
463     /// if let Poll::Ready(_) = Poll::Ready(42) {}
464     /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
465     /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
466     /// match Ok::<i32, i32>(42) {
467     ///     Ok(_) => true,
468     ///     Err(_) => false,
469     /// };
470     /// ```
471     ///
472     /// The more idiomatic use would be:
473     ///
474     /// ```rust
475     /// # use std::task::Poll;
476     /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
477     /// if Ok::<i32, i32>(42).is_ok() {}
478     /// if Err::<i32, i32>(42).is_err() {}
479     /// if None::<()>.is_none() {}
480     /// if Some(42).is_some() {}
481     /// if Poll::Pending::<()>.is_pending() {}
482     /// if Poll::Ready(42).is_ready() {}
483     /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
484     /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
485     /// Ok::<i32, i32>(42).is_ok();
486     /// ```
487     #[clippy::version = "1.31.0"]
488     pub REDUNDANT_PATTERN_MATCHING,
489     style,
490     "use the proper utility function avoiding an `if let`"
491 }
492
493 declare_clippy_lint! {
494     /// ### What it does
495     /// Checks for `match`  or `if let` expressions producing a
496     /// `bool` that could be written using `matches!`
497     ///
498     /// ### Why is this bad?
499     /// Readability and needless complexity.
500     ///
501     /// ### Known problems
502     /// This lint falsely triggers, if there are arms with
503     /// `cfg` attributes that remove an arm evaluating to `false`.
504     ///
505     /// ### Example
506     /// ```rust
507     /// let x = Some(5);
508     ///
509     /// // Bad
510     /// let a = match x {
511     ///     Some(0) => true,
512     ///     _ => false,
513     /// };
514     ///
515     /// let a = if let Some(0) = x {
516     ///     true
517     /// } else {
518     ///     false
519     /// };
520     ///
521     /// // Good
522     /// let a = matches!(x, Some(0));
523     /// ```
524     #[clippy::version = "1.47.0"]
525     pub MATCH_LIKE_MATCHES_MACRO,
526     style,
527     "a match that could be written with the matches! macro"
528 }
529
530 declare_clippy_lint! {
531     /// ### What it does
532     /// Checks for `match` with identical arm bodies.
533     ///
534     /// ### Why is this bad?
535     /// This is probably a copy & paste error. If arm bodies
536     /// are the same on purpose, you can factor them
537     /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
538     ///
539     /// ### Known problems
540     /// False positive possible with order dependent `match`
541     /// (see issue
542     /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
543     ///
544     /// ### Example
545     /// ```rust,ignore
546     /// match foo {
547     ///     Bar => bar(),
548     ///     Quz => quz(),
549     ///     Baz => bar(), // <= oops
550     /// }
551     /// ```
552     ///
553     /// This should probably be
554     /// ```rust,ignore
555     /// match foo {
556     ///     Bar => bar(),
557     ///     Quz => quz(),
558     ///     Baz => baz(), // <= fixed
559     /// }
560     /// ```
561     ///
562     /// or if the original code was not a typo:
563     /// ```rust,ignore
564     /// match foo {
565     ///     Bar | Baz => bar(), // <= shows the intent better
566     ///     Quz => quz(),
567     /// }
568     /// ```
569     #[clippy::version = "pre 1.29.0"]
570     pub MATCH_SAME_ARMS,
571     pedantic,
572     "`match` with identical arm bodies"
573 }
574
575 declare_clippy_lint! {
576     /// ### What it does
577     /// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result`
578     /// when function signatures are the same.
579     ///
580     /// ### Why is this bad?
581     /// This `match` block does nothing and might not be what the coder intended.
582     ///
583     /// ### Example
584     /// ```rust,ignore
585     /// fn foo() -> Result<(), i32> {
586     ///     match result {
587     ///         Ok(val) => Ok(val),
588     ///         Err(err) => Err(err),
589     ///     }
590     /// }
591     ///
592     /// fn bar() -> Option<i32> {
593     ///     if let Some(val) = option {
594     ///         Some(val)
595     ///     } else {
596     ///         None
597     ///     }
598     /// }
599     /// ```
600     ///
601     /// Could be replaced as
602     ///
603     /// ```rust,ignore
604     /// fn foo() -> Result<(), i32> {
605     ///     result
606     /// }
607     ///
608     /// fn bar() -> Option<i32> {
609     ///     option
610     /// }
611     /// ```
612     #[clippy::version = "1.61.0"]
613     pub NEEDLESS_MATCH,
614     complexity,
615     "`match` or match-like `if let` that are unnecessary"
616 }
617
618 declare_clippy_lint! {
619     /// ### What it does
620     /// Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
621     /// without adding any branches.
622     ///
623     /// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
624     /// cases where merging would most likely make the code more readable.
625     ///
626     /// ### Why is this bad?
627     /// It is unnecessarily verbose and complex.
628     ///
629     /// ### Example
630     /// ```rust
631     /// fn func(opt: Option<Result<u64, String>>) {
632     ///     let n = match opt {
633     ///         Some(n) => match n {
634     ///             Ok(n) => n,
635     ///             _ => return,
636     ///         }
637     ///         None => return,
638     ///     };
639     /// }
640     /// ```
641     /// Use instead:
642     /// ```rust
643     /// fn func(opt: Option<Result<u64, String>>) {
644     ///     let n = match opt {
645     ///         Some(Ok(n)) => n,
646     ///         _ => return,
647     ///     };
648     /// }
649     /// ```
650     #[clippy::version = "1.50.0"]
651     pub COLLAPSIBLE_MATCH,
652     style,
653     "Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
654 }
655
656 declare_clippy_lint! {
657     /// ### What it does
658     /// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`.
659     ///
660     /// ### Why is this bad?
661     /// Concise code helps focusing on behavior instead of boilerplate.
662     ///
663     /// ### Example
664     /// ```rust
665     /// let foo: Option<i32> = None;
666     /// match foo {
667     ///     Some(v) => v,
668     ///     None => 1,
669     /// };
670     /// ```
671     ///
672     /// Use instead:
673     /// ```rust
674     /// let foo: Option<i32> = None;
675     /// foo.unwrap_or(1);
676     /// ```
677     #[clippy::version = "1.49.0"]
678     pub MANUAL_UNWRAP_OR,
679     complexity,
680     "finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`"
681 }
682
683 declare_clippy_lint! {
684     /// ### What it does
685     /// Checks for `match vec[idx]` or `match vec[n..m]`.
686     ///
687     /// ### Why is this bad?
688     /// This can panic at runtime.
689     ///
690     /// ### Example
691     /// ```rust, no_run
692     /// let arr = vec![0, 1, 2, 3];
693     /// let idx = 1;
694     ///
695     /// // Bad
696     /// match arr[idx] {
697     ///     0 => println!("{}", 0),
698     ///     1 => println!("{}", 3),
699     ///     _ => {},
700     /// }
701     /// ```
702     /// Use instead:
703     /// ```rust, no_run
704     /// let arr = vec![0, 1, 2, 3];
705     /// let idx = 1;
706     ///
707     /// // Good
708     /// match arr.get(idx) {
709     ///     Some(0) => println!("{}", 0),
710     ///     Some(1) => println!("{}", 3),
711     ///     _ => {},
712     /// }
713     /// ```
714     #[clippy::version = "1.45.0"]
715     pub MATCH_ON_VEC_ITEMS,
716     pedantic,
717     "matching on vector elements can panic"
718 }
719
720 declare_clippy_lint! {
721     /// ### What it does
722     /// Checks for `match` expressions modifying the case of a string with non-compliant arms
723     ///
724     /// ### Why is this bad?
725     /// The arm is unreachable, which is likely a mistake
726     ///
727     /// ### Example
728     /// ```rust
729     /// # let text = "Foo";
730     /// match &*text.to_ascii_lowercase() {
731     ///     "foo" => {},
732     ///     "Bar" => {},
733     ///     _ => {},
734     /// }
735     /// ```
736     /// Use instead:
737     /// ```rust
738     /// # let text = "Foo";
739     /// match &*text.to_ascii_lowercase() {
740     ///     "foo" => {},
741     ///     "bar" => {},
742     ///     _ => {},
743     /// }
744     /// ```
745     #[clippy::version = "1.58.0"]
746     pub MATCH_STR_CASE_MISMATCH,
747     correctness,
748     "creation of a case altering match expression with non-compliant arms"
749 }
750
751 #[derive(Default)]
752 pub struct Matches {
753     msrv: Option<RustcVersion>,
754     infallible_destructuring_match_linted: bool,
755 }
756
757 impl Matches {
758     #[must_use]
759     pub fn new(msrv: Option<RustcVersion>) -> Self {
760         Self {
761             msrv,
762             ..Matches::default()
763         }
764     }
765 }
766
767 impl_lint_pass!(Matches => [
768     SINGLE_MATCH,
769     MATCH_REF_PATS,
770     MATCH_BOOL,
771     SINGLE_MATCH_ELSE,
772     MATCH_OVERLAPPING_ARM,
773     MATCH_WILD_ERR_ARM,
774     MATCH_AS_REF,
775     WILDCARD_ENUM_MATCH_ARM,
776     MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
777     WILDCARD_IN_OR_PATTERNS,
778     MATCH_SINGLE_BINDING,
779     INFALLIBLE_DESTRUCTURING_MATCH,
780     REST_PAT_IN_FULLY_BOUND_STRUCTS,
781     REDUNDANT_PATTERN_MATCHING,
782     MATCH_LIKE_MATCHES_MACRO,
783     MATCH_SAME_ARMS,
784     NEEDLESS_MATCH,
785     COLLAPSIBLE_MATCH,
786     MANUAL_UNWRAP_OR,
787     MATCH_ON_VEC_ITEMS,
788     MATCH_STR_CASE_MISMATCH,
789 ]);
790
791 impl<'tcx> LateLintPass<'tcx> for Matches {
792     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
793         if in_external_macro(cx.sess(), expr.span) {
794             return;
795         }
796         let from_expansion = expr.span.from_expansion();
797
798         if let ExprKind::Match(ex, arms, source) = expr.kind {
799             if !span_starts_with(cx, expr.span, "match") {
800                 return;
801             }
802
803             collapsible_match::check_match(cx, arms);
804             if !from_expansion {
805                 // These don't depend on a relationship between multiple arms
806                 match_wild_err_arm::check(cx, ex, arms);
807                 wild_in_or_pats::check(cx, arms);
808             }
809
810             if !from_expansion && !contains_cfg_arm(cx, expr, ex, arms) {
811                 if source == MatchSource::Normal {
812                     if !(meets_msrv(self.msrv, msrvs::MATCHES_MACRO)
813                         && match_like_matches::check_match(cx, expr, ex, arms))
814                     {
815                         match_same_arms::check(cx, arms);
816                     }
817
818                     redundant_pattern_match::check_match(cx, expr, ex, arms);
819                     single_match::check(cx, ex, arms, expr);
820                     match_bool::check(cx, ex, arms, expr);
821                     overlapping_arms::check(cx, ex, arms);
822                     match_wild_enum::check(cx, ex, arms);
823                     match_as_ref::check(cx, ex, arms, expr);
824                     needless_match::check_match(cx, ex, arms, expr);
825                     match_on_vec_items::check(cx, ex);
826                     match_str_case_mismatch::check(cx, ex, arms);
827
828                     if !in_constant(cx, expr.hir_id) {
829                         manual_unwrap_or::check(cx, expr, ex, arms);
830                     }
831
832                     if self.infallible_destructuring_match_linted {
833                         self.infallible_destructuring_match_linted = false;
834                     } else {
835                         match_single_binding::check(cx, ex, arms, expr);
836                     }
837                 }
838                 match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
839             }
840         } else if let Some(if_let) = higher::IfLet::hir(cx, expr) {
841             collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else);
842             if !from_expansion {
843                 if let Some(else_expr) = if_let.if_else {
844                     if meets_msrv(self.msrv, msrvs::MATCHES_MACRO) {
845                         match_like_matches::check_if_let(
846                             cx,
847                             expr,
848                             if_let.let_pat,
849                             if_let.let_expr,
850                             if_let.if_then,
851                             else_expr,
852                         );
853                     }
854                 }
855                 redundant_pattern_match::check_if_let(
856                     cx,
857                     expr,
858                     if_let.let_pat,
859                     if_let.let_expr,
860                     if_let.if_else.is_some(),
861                 );
862                 needless_match::check_if_let(cx, expr, &if_let);
863             }
864         } else if !from_expansion {
865             redundant_pattern_match::check(cx, expr);
866         }
867     }
868
869     fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
870         self.infallible_destructuring_match_linted |= infallible_destructuring_match::check(cx, local);
871     }
872
873     fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
874         rest_pat_in_fully_bound_struct::check(cx, pat);
875     }
876
877     extract_msrv_attr!(LateContext);
878 }
879
880 /// Checks if there are any arms with a `#[cfg(..)]` attribute.
881 fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
882     let Some(scrutinee_span) = walk_span_to_context(scrutinee.span, SyntaxContext::root()) else {
883         // Shouldn't happen, but treat this as though a `cfg` attribute were found
884         return true;
885     };
886
887     let start = scrutinee_span.hi();
888     let mut arm_spans = arms.iter().map(|arm| {
889         let data = arm.span.data();
890         (data.ctxt == SyntaxContext::root()).then(|| (data.lo, data.hi))
891     });
892     let end = e.span.hi();
893
894     // Walk through all the non-code space before each match arm. The space trailing the final arm is
895     // handled after the `try_fold` e.g.
896     //
897     // match foo {
898     // _________^-                      everything between the scrutinee and arm1
899     //|    arm1 => (),
900     //|---^___________^                 everything before arm2
901     //|    #[cfg(feature = "enabled")]
902     //|    arm2 => some_code(),
903     //|---^____________________^        everything before arm3
904     //|    // some comment about arm3
905     //|    arm3 => some_code(),
906     //|---^____________________^        everything after arm3
907     //|    #[cfg(feature = "disabled")]
908     //|    arm4 = some_code(),
909     //|};
910     //|^
911     let found = arm_spans.try_fold(start, |start, range| {
912         let Some((end, next_start)) = range else {
913             // Shouldn't happen as macros can't expand to match arms, but treat this as though a `cfg` attribute were
914             // found.
915             return Err(());
916         };
917         let span = SpanData {
918             lo: start,
919             hi: end,
920             ctxt: SyntaxContext::root(),
921             parent: None,
922         }
923         .span();
924         (!span_contains_cfg(cx, span)).then(|| next_start).ok_or(())
925     });
926     match found {
927         Ok(start) => {
928             let span = SpanData {
929                 lo: start,
930                 hi: end,
931                 ctxt: SyntaxContext::root(),
932                 parent: None,
933             }
934             .span();
935             span_contains_cfg(cx, span)
936         },
937         Err(()) => true,
938     }
939 }
940
941 /// Checks if the given span contains a `#[cfg(..)]` attribute
942 fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
943     let Some(snip) = snippet_opt(cx, s) else {
944         // Assume true. This would require either an invalid span, or one which crosses file boundaries.
945         return true;
946     };
947     let mut pos = 0usize;
948     let mut iter = tokenize(&snip).map(|t| {
949         let start = pos;
950         pos += t.len;
951         (t.kind, start..pos)
952     });
953
954     // Search for the token sequence [`#`, `[`, `cfg`]
955     while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) {
956         let mut iter = iter.by_ref().skip_while(|(t, _)| {
957             matches!(
958                 t,
959                 TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. }
960             )
961         });
962         if matches!(iter.next(), Some((TokenKind::OpenBracket, _)))
963             && matches!(iter.next(), Some((TokenKind::Ident, range)) if &snip[range.clone()] == "cfg")
964         {
965             return true;
966         }
967     }
968     false
969 }