1 use clippy_utils::source::{snippet_opt, walk_span_to_context};
2 use clippy_utils::{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};
6 use rustc_semver::RustcVersion;
7 use rustc_session::{declare_tool_lint, impl_lint_pass};
8 use rustc_span::{Span, SpanData, SyntaxContext};
10 mod infalliable_detructuring_match;
13 mod match_like_matches;
16 mod match_single_binding;
18 mod match_wild_err_arm;
21 mod redundant_pattern_match;
22 mod rest_pat_in_fully_bound_struct;
26 declare_clippy_lint! {
28 /// Checks for matches with a single arm where an `if let`
29 /// will usually suffice.
31 /// ### Why is this bad?
32 /// Just readability – `if let` nests less than a `match`.
36 /// # fn bar(stool: &str) {}
37 /// # let x = Some("abc");
40 /// Some(ref foo) => bar(foo),
45 /// if let Some(ref foo) = x {
49 #[clippy::version = "pre 1.29.0"]
52 "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
55 declare_clippy_lint! {
57 /// Checks for matches with two arms where an `if let else` will
60 /// ### Why is this bad?
61 /// Just readability – `if let` nests less than a `match`.
63 /// ### Known problems
64 /// Personal style preferences may differ.
70 /// # fn bar(foo: &usize) {}
71 /// # let other_ref: usize = 1;
72 /// # let x: Option<&usize> = Some(&1);
74 /// Some(ref foo) => bar(foo),
75 /// _ => bar(&other_ref),
79 /// Using `if let` with `else`:
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 {
91 #[clippy::version = "pre 1.29.0"]
92 pub SINGLE_MATCH_ELSE,
94 "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
97 declare_clippy_lint! {
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.
103 /// ### Why is this bad?
104 /// It just makes the code less readable. That reference
105 /// destructuring adds nothing to the code.
111 /// &A(ref y) => foo(y),
118 /// A(ref y) => foo(y),
123 #[clippy::version = "pre 1.29.0"]
126 "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
129 declare_clippy_lint! {
131 /// Checks for matches where match expression is a `bool`. It
132 /// suggests to replace the expression with an `if...else` block.
134 /// ### Why is this bad?
135 /// It makes the code less readable.
141 /// let condition: bool = true;
142 /// match condition {
147 /// Use if/else instead:
151 /// let condition: bool = true;
158 #[clippy::version = "pre 1.29.0"]
161 "a `match` on a boolean expression instead of an `if..else` block"
164 declare_clippy_lint! {
166 /// Checks for overlapping match arms.
168 /// ### Why is this bad?
169 /// It is likely to be an error and if not, makes the code
176 /// 1..=10 => println!("1 ... 10"),
177 /// 5..=15 => println!("5 ... 15"),
181 #[clippy::version = "pre 1.29.0"]
182 pub MATCH_OVERLAPPING_ARM,
184 "a `match` with overlapping arms"
187 declare_clippy_lint! {
189 /// Checks for arm which matches all errors with `Err(_)`
190 /// and take drastic actions like `panic!`.
192 /// ### Why is this bad?
193 /// It is generally a bad practice, similar to
194 /// catching all exceptions in java with `catch(Exception)`
198 /// let x: Result<i32, &str> = Ok(3);
200 /// Ok(_) => println!("ok"),
201 /// Err(_) => panic!("err"),
204 #[clippy::version = "pre 1.29.0"]
205 pub MATCH_WILD_ERR_ARM,
207 "a `match` with `Err(_)` arm and take drastic actions"
210 declare_clippy_lint! {
212 /// Checks for match which is used to add a reference to an
215 /// ### Why is this bad?
216 /// Using `as_ref()` or `as_mut()` instead is shorter.
220 /// let x: Option<()> = None;
223 /// let r: Option<&()> = match x {
225 /// Some(ref v) => Some(v),
229 /// let r: Option<&()> = x.as_ref();
231 #[clippy::version = "pre 1.29.0"]
234 "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
237 declare_clippy_lint! {
239 /// Checks for wildcard enum matches using `_`.
241 /// ### Why is this bad?
242 /// New enum variants added by library updates can be missed.
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.
250 /// # enum Foo { A(usize), B(usize) }
251 /// # let x = Foo::B(1);
264 #[clippy::version = "1.34.0"]
265 pub WILDCARD_ENUM_MATCH_ARM,
267 "a wildcard enum match arm using `_`"
270 declare_clippy_lint! {
272 /// Checks for wildcard enum matches for a single variant.
274 /// ### Why is this bad?
275 /// New enum variants added by library updates can be missed.
277 /// ### Known problems
278 /// Suggested replacements may not use correct path to enum
279 /// if it's not present in the current scope.
283 /// # enum Foo { A, B, C }
284 /// # let x = Foo::B;
299 #[clippy::version = "1.45.0"]
300 pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
302 "a wildcard enum match for a single variant"
305 declare_clippy_lint! {
307 /// Checks for wildcard pattern used with others patterns in same match arm.
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.
327 #[clippy::version = "1.42.0"]
328 pub WILDCARD_IN_OR_PATTERNS,
330 "a wildcard pattern used with others patterns in same match arm"
333 declare_clippy_lint! {
335 /// Checks for matches being used to destructure a single-variant enum
336 /// or tuple struct where a `let` will suffice.
338 /// ### Why is this bad?
339 /// Just readability – `let` doesn't nest, whereas a `match` does.
347 /// let wrapper = Wrapper::Data(42);
349 /// let data = match wrapper {
350 /// Wrapper::Data(i) => i,
354 /// The correct use would be:
360 /// let wrapper = Wrapper::Data(42);
361 /// let Wrapper::Data(data) = wrapper;
363 #[clippy::version = "pre 1.29.0"]
364 pub INFALLIBLE_DESTRUCTURING_MATCH,
366 "a `match` statement with a single infallible arm instead of a `let`"
369 declare_clippy_lint! {
371 /// Checks for useless match that binds to only one value.
373 /// ### Why is this bad?
374 /// Readability and needless complexity.
376 /// ### Known problems
377 /// Suggested replacements may be incorrect when `match`
378 /// is actually binding temporary value, bringing a 'dropped while borrowed' error.
393 /// let (c, d) = (a, b);
395 #[clippy::version = "1.43.0"]
396 pub MATCH_SINGLE_BINDING,
398 "a match with a single binding instead of using `let` statement"
401 declare_clippy_lint! {
403 /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched.
405 /// ### Why is this bad?
406 /// Correctness and readability. It's like having a wildcard pattern after
407 /// matching all enum variants explicitly.
411 /// # struct A { a: i32 }
412 /// let a = A { a: 5 };
416 /// A { a: 5, .. } => {},
422 /// A { a: 5 } => {},
426 #[clippy::version = "1.43.0"]
427 pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
429 "a match on a struct that binds all fields but still uses the wildcard pattern"
432 declare_clippy_lint! {
434 /// Lint for redundant pattern matching over `Result`, `Option`,
435 /// `std::task::Poll` or `std::net::IpAddr`
437 /// ### Why is this bad?
438 /// It's more concise and clear to just use the proper
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
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) {
467 /// The more idiomatic use would be:
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();
482 #[clippy::version = "1.31.0"]
483 pub REDUNDANT_PATTERN_MATCHING,
485 "use the proper utility function avoiding an `if let`"
488 declare_clippy_lint! {
490 /// Checks for `match` or `if let` expressions producing a
491 /// `bool` that could be written using `matches!`
493 /// ### Why is this bad?
494 /// Readability and needless complexity.
496 /// ### Known problems
497 /// This lint falsely triggers, if there are arms with
498 /// `cfg` attributes that remove an arm evaluating to `false`.
505 /// let a = match x {
510 /// let a = if let Some(0) = x {
517 /// let a = matches!(x, Some(0));
519 #[clippy::version = "1.47.0"]
520 pub MATCH_LIKE_MATCHES_MACRO,
522 "a match that could be written with the matches! macro"
525 declare_clippy_lint! {
527 /// Checks for `match` with identical arm bodies.
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).
534 /// ### Known problems
535 /// False positive possible with order dependent `match`
537 /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
544 /// Baz => bar(), // <= oops
548 /// This should probably be
553 /// Baz => baz(), // <= fixed
557 /// or if the original code was not a typo:
560 /// Bar | Baz => bar(), // <= shows the intent better
564 #[clippy::version = "pre 1.29.0"]
567 "`match` with identical arm bodies"
570 declare_clippy_lint! {
572 /// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result`
573 /// when function signatures are the same.
575 /// ### Why is this bad?
576 /// This `match` block does nothing and might not be what the coder intended.
580 /// fn foo() -> Result<(), i32> {
582 /// Ok(val) => Ok(val),
583 /// Err(err) => Err(err),
587 /// fn bar() -> Option<i32> {
588 /// if let Some(val) = option {
596 /// Could be replaced as
599 /// fn foo() -> Result<(), i32> {
603 /// fn bar() -> Option<i32> {
607 #[clippy::version = "1.61.0"]
610 "`match` or match-like `if let` that are unnecessary"
615 msrv: Option<RustcVersion>,
616 infallible_destructuring_match_linted: bool,
621 pub fn new(msrv: Option<RustcVersion>) -> Self {
629 impl_lint_pass!(Matches => [
634 MATCH_OVERLAPPING_ARM,
637 WILDCARD_ENUM_MATCH_ARM,
638 MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
639 WILDCARD_IN_OR_PATTERNS,
640 MATCH_SINGLE_BINDING,
641 INFALLIBLE_DESTRUCTURING_MATCH,
642 REST_PAT_IN_FULLY_BOUND_STRUCTS,
643 REDUNDANT_PATTERN_MATCHING,
644 MATCH_LIKE_MATCHES_MACRO,
649 impl<'tcx> LateLintPass<'tcx> for Matches {
650 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
651 if expr.span.from_expansion() {
655 if let ExprKind::Match(ex, arms, source) = expr.kind {
656 if !contains_cfg_arm(cx, expr, ex, arms) {
657 if source == MatchSource::Normal {
658 if !(meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO)
659 && match_like_matches::check_match(cx, expr, ex, arms))
661 match_same_arms::check(cx, arms);
664 redundant_pattern_match::check_match(cx, expr, ex, arms);
665 single_match::check(cx, ex, arms, expr);
666 match_bool::check(cx, ex, arms, expr);
667 overlapping_arms::check(cx, ex, arms);
668 match_wild_enum::check(cx, ex, arms);
669 match_as_ref::check(cx, ex, arms, expr);
670 needless_match::check_match(cx, ex, arms);
672 if self.infallible_destructuring_match_linted {
673 self.infallible_destructuring_match_linted = false;
675 match_single_binding::check(cx, ex, arms, expr);
678 match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
681 // These don't depend on a relationship between multiple arms
682 match_wild_err_arm::check(cx, ex, arms);
683 wild_in_or_pats::check(cx, arms);
685 if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) {
686 match_like_matches::check(cx, expr);
688 redundant_pattern_match::check(cx, expr);
689 needless_match::check(cx, expr);
693 fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
694 self.infallible_destructuring_match_linted |= infalliable_detructuring_match::check(cx, local);
697 fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
698 rest_pat_in_fully_bound_struct::check(cx, pat);
701 extract_msrv_attr!(LateContext);
704 /// Checks if there are any arms with a `#[cfg(..)]` attribute.
705 fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
706 let Some(scrutinee_span) = walk_span_to_context(scrutinee.span, SyntaxContext::root()) else {
707 // Shouldn't happen, but treat this as though a `cfg` attribute were found
711 let start = scrutinee_span.hi();
712 let mut arm_spans = arms.iter().map(|arm| {
713 let data = arm.span.data();
714 (data.ctxt == SyntaxContext::root()).then(|| (data.lo, data.hi))
716 let end = e.span.hi();
718 // Walk through all the non-code space before each match arm. The space trailing the final arm is
719 // handled after the `try_fold` e.g.
722 // _________^- everything between the scrutinee and arm1
724 //|---^___________^ everything before arm2
725 //| #[cfg(feature = "enabled")]
726 //| arm2 => some_code(),
727 //|---^____________________^ everything before arm3
728 //| // some comment about arm3
729 //| arm3 => some_code(),
730 //|---^____________________^ everything after arm3
731 //| #[cfg(feature = "disabled")]
732 //| arm4 = some_code(),
735 let found = arm_spans.try_fold(start, |start, range| {
736 let Some((end, next_start)) = range else {
737 // Shouldn't happen as macros can't expand to match arms, but treat this as though a `cfg` attribute were
741 let span = SpanData {
744 ctxt: SyntaxContext::root(),
748 (!span_contains_cfg(cx, span)).then(|| next_start).ok_or(())
752 let span = SpanData {
755 ctxt: SyntaxContext::root(),
759 span_contains_cfg(cx, span)
765 /// Checks if the given span contains a `#[cfg(..)]` attribute
766 fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
767 let Some(snip) = snippet_opt(cx, s) else {
768 // Assume true. This would require either an invalid span, or one which crosses file boundaries.
771 let mut pos = 0usize;
772 let mut iter = tokenize(&snip).map(|t| {
778 // Search for the token sequence [`#`, `[`, `cfg`]
779 while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) {
780 let mut iter = iter.by_ref().skip_while(|(t, _)| {
783 TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. }
786 if matches!(iter.next(), Some((TokenKind::OpenBracket, _)))
787 && matches!(iter.next(), Some((TokenKind::Ident, range)) if &snip[range.clone()] == "cfg")