1 use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
2 use clippy_utils::source::{snippet, snippet_with_applicability};
3 use clippy_utils::sugg::Sugg;
4 use clippy_utils::{is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, strip_pat_refs};
6 use if_chain::if_chain;
7 use rustc_errors::Applicability;
8 use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Pat, PatKind, QPath};
9 use rustc_lint::{LateContext, LateLintPass};
11 use rustc_semver::RustcVersion;
12 use rustc_session::{declare_tool_lint, impl_lint_pass};
16 mod match_like_matches;
18 mod match_single_binding;
20 mod match_wild_err_arm;
22 mod redundant_pattern_match;
25 declare_clippy_lint! {
27 /// Checks for matches with a single arm where an `if let`
28 /// will usually suffice.
30 /// ### Why is this bad?
31 /// Just readability – `if let` nests less than a `match`.
35 /// # fn bar(stool: &str) {}
36 /// # let x = Some("abc");
39 /// Some(ref foo) => bar(foo),
44 /// if let Some(ref foo) = x {
48 #[clippy::version = "pre 1.29.0"]
51 "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
54 declare_clippy_lint! {
56 /// Checks for matches with two arms where an `if let else` will
59 /// ### Why is this bad?
60 /// Just readability – `if let` nests less than a `match`.
62 /// ### Known problems
63 /// Personal style preferences may differ.
69 /// # fn bar(foo: &usize) {}
70 /// # let other_ref: usize = 1;
71 /// # let x: Option<&usize> = Some(&1);
73 /// Some(ref foo) => bar(foo),
74 /// _ => bar(&other_ref),
78 /// Using `if let` with `else`:
81 /// # fn bar(foo: &usize) {}
82 /// # let other_ref: usize = 1;
83 /// # let x: Option<&usize> = Some(&1);
84 /// if let Some(ref foo) = x {
90 #[clippy::version = "pre 1.29.0"]
91 pub SINGLE_MATCH_ELSE,
93 "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
96 declare_clippy_lint! {
98 /// Checks for matches where all arms match a reference,
99 /// suggesting to remove the reference and deref the matched expression
100 /// instead. It also checks for `if let &foo = bar` blocks.
102 /// ### Why is this bad?
103 /// It just makes the code less readable. That reference
104 /// destructuring adds nothing to the code.
110 /// &A(ref y) => foo(y),
117 /// A(ref y) => foo(y),
122 #[clippy::version = "pre 1.29.0"]
125 "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
128 declare_clippy_lint! {
130 /// Checks for matches where match expression is a `bool`. It
131 /// suggests to replace the expression with an `if...else` block.
133 /// ### Why is this bad?
134 /// It makes the code less readable.
140 /// let condition: bool = true;
141 /// match condition {
146 /// Use if/else instead:
150 /// let condition: bool = true;
157 #[clippy::version = "pre 1.29.0"]
160 "a `match` on a boolean expression instead of an `if..else` block"
163 declare_clippy_lint! {
165 /// Checks for overlapping match arms.
167 /// ### Why is this bad?
168 /// It is likely to be an error and if not, makes the code
175 /// 1..=10 => println!("1 ... 10"),
176 /// 5..=15 => println!("5 ... 15"),
180 #[clippy::version = "pre 1.29.0"]
181 pub MATCH_OVERLAPPING_ARM,
183 "a `match` with overlapping arms"
186 declare_clippy_lint! {
188 /// Checks for arm which matches all errors with `Err(_)`
189 /// and take drastic actions like `panic!`.
191 /// ### Why is this bad?
192 /// It is generally a bad practice, similar to
193 /// catching all exceptions in java with `catch(Exception)`
197 /// let x: Result<i32, &str> = Ok(3);
199 /// Ok(_) => println!("ok"),
200 /// Err(_) => panic!("err"),
203 #[clippy::version = "pre 1.29.0"]
204 pub MATCH_WILD_ERR_ARM,
206 "a `match` with `Err(_)` arm and take drastic actions"
209 declare_clippy_lint! {
211 /// Checks for match which is used to add a reference to an
214 /// ### Why is this bad?
215 /// Using `as_ref()` or `as_mut()` instead is shorter.
219 /// let x: Option<()> = None;
222 /// let r: Option<&()> = match x {
224 /// Some(ref v) => Some(v),
228 /// let r: Option<&()> = x.as_ref();
230 #[clippy::version = "pre 1.29.0"]
233 "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
236 declare_clippy_lint! {
238 /// Checks for wildcard enum matches using `_`.
240 /// ### Why is this bad?
241 /// New enum variants added by library updates can be missed.
243 /// ### Known problems
244 /// Suggested replacements may be incorrect if guards exhaustively cover some
245 /// variants, and also may not use correct path to enum if it's not present in the current scope.
249 /// # enum Foo { A(usize), B(usize) }
250 /// # let x = Foo::B(1);
263 #[clippy::version = "1.34.0"]
264 pub WILDCARD_ENUM_MATCH_ARM,
266 "a wildcard enum match arm using `_`"
269 declare_clippy_lint! {
271 /// Checks for wildcard enum matches for a single variant.
273 /// ### Why is this bad?
274 /// New enum variants added by library updates can be missed.
276 /// ### Known problems
277 /// Suggested replacements may not use correct path to enum
278 /// if it's not present in the current scope.
282 /// # enum Foo { A, B, C }
283 /// # let x = Foo::B;
298 #[clippy::version = "1.45.0"]
299 pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
301 "a wildcard enum match for a single variant"
304 declare_clippy_lint! {
306 /// Checks for wildcard pattern used with others patterns in same match arm.
308 /// ### Why is this bad?
309 /// Wildcard pattern already covers any other pattern as it will match anyway.
310 /// It makes the code less readable, especially to spot wildcard pattern use in match arm.
326 #[clippy::version = "1.42.0"]
327 pub WILDCARD_IN_OR_PATTERNS,
329 "a wildcard pattern used with others patterns in same match arm"
332 declare_clippy_lint! {
334 /// Checks for matches being used to destructure a single-variant enum
335 /// or tuple struct where a `let` will suffice.
337 /// ### Why is this bad?
338 /// Just readability – `let` doesn't nest, whereas a `match` does.
346 /// let wrapper = Wrapper::Data(42);
348 /// let data = match wrapper {
349 /// Wrapper::Data(i) => i,
353 /// The correct use would be:
359 /// let wrapper = Wrapper::Data(42);
360 /// let Wrapper::Data(data) = wrapper;
362 #[clippy::version = "pre 1.29.0"]
363 pub INFALLIBLE_DESTRUCTURING_MATCH,
365 "a `match` statement with a single infallible arm instead of a `let`"
368 declare_clippy_lint! {
370 /// Checks for useless match that binds to only one value.
372 /// ### Why is this bad?
373 /// Readability and needless complexity.
375 /// ### Known problems
376 /// Suggested replacements may be incorrect when `match`
377 /// is actually binding temporary value, bringing a 'dropped while borrowed' error.
392 /// let (c, d) = (a, b);
394 #[clippy::version = "1.43.0"]
395 pub MATCH_SINGLE_BINDING,
397 "a match with a single binding instead of using `let` statement"
400 declare_clippy_lint! {
402 /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched.
404 /// ### Why is this bad?
405 /// Correctness and readability. It's like having a wildcard pattern after
406 /// matching all enum variants explicitly.
410 /// # struct A { a: i32 }
411 /// let a = A { a: 5 };
415 /// A { a: 5, .. } => {},
421 /// A { a: 5 } => {},
425 #[clippy::version = "1.43.0"]
426 pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
428 "a match on a struct that binds all fields but still uses the wildcard pattern"
431 declare_clippy_lint! {
433 /// Lint for redundant pattern matching over `Result`, `Option`,
434 /// `std::task::Poll` or `std::net::IpAddr`
436 /// ### Why is this bad?
437 /// It's more concise and clear to just use the proper
440 /// ### Known problems
441 /// This will change the drop order for the matched type. Both `if let` and
442 /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
443 /// value before entering the block. For most types this change will not matter, but for a few
444 /// types this will not be an acceptable change (e.g. locks). See the
445 /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
450 /// # use std::task::Poll;
451 /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
452 /// if let Ok(_) = Ok::<i32, i32>(42) {}
453 /// if let Err(_) = Err::<i32, i32>(42) {}
454 /// if let None = None::<()> {}
455 /// if let Some(_) = Some(42) {}
456 /// if let Poll::Pending = Poll::Pending::<()> {}
457 /// if let Poll::Ready(_) = Poll::Ready(42) {}
458 /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
459 /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
460 /// match Ok::<i32, i32>(42) {
466 /// The more idiomatic use would be:
469 /// # use std::task::Poll;
470 /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
471 /// if Ok::<i32, i32>(42).is_ok() {}
472 /// if Err::<i32, i32>(42).is_err() {}
473 /// if None::<()>.is_none() {}
474 /// if Some(42).is_some() {}
475 /// if Poll::Pending::<()>.is_pending() {}
476 /// if Poll::Ready(42).is_ready() {}
477 /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
478 /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
479 /// Ok::<i32, i32>(42).is_ok();
481 #[clippy::version = "1.31.0"]
482 pub REDUNDANT_PATTERN_MATCHING,
484 "use the proper utility function avoiding an `if let`"
487 declare_clippy_lint! {
489 /// Checks for `match` or `if let` expressions producing a
490 /// `bool` that could be written using `matches!`
492 /// ### Why is this bad?
493 /// Readability and needless complexity.
495 /// ### Known problems
496 /// This lint falsely triggers, if there are arms with
497 /// `cfg` attributes that remove an arm evaluating to `false`.
504 /// let a = match x {
509 /// let a = if let Some(0) = x {
516 /// let a = matches!(x, Some(0));
518 #[clippy::version = "1.47.0"]
519 pub MATCH_LIKE_MATCHES_MACRO,
521 "a match that could be written with the matches! macro"
524 declare_clippy_lint! {
526 /// Checks for `match` with identical arm bodies.
528 /// ### Why is this bad?
529 /// This is probably a copy & paste error. If arm bodies
530 /// are the same on purpose, you can factor them
531 /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
533 /// ### Known problems
534 /// False positive possible with order dependent `match`
536 /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
543 /// Baz => bar(), // <= oops
547 /// This should probably be
552 /// Baz => baz(), // <= fixed
556 /// or if the original code was not a typo:
559 /// Bar | Baz => bar(), // <= shows the intent better
563 #[clippy::version = "pre 1.29.0"]
566 "`match` with identical arm bodies"
571 msrv: Option<RustcVersion>,
572 infallible_destructuring_match_linted: bool,
577 pub fn new(msrv: Option<RustcVersion>) -> Self {
585 impl_lint_pass!(Matches => [
590 MATCH_OVERLAPPING_ARM,
593 WILDCARD_ENUM_MATCH_ARM,
594 MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
595 WILDCARD_IN_OR_PATTERNS,
596 MATCH_SINGLE_BINDING,
597 INFALLIBLE_DESTRUCTURING_MATCH,
598 REST_PAT_IN_FULLY_BOUND_STRUCTS,
599 REDUNDANT_PATTERN_MATCHING,
600 MATCH_LIKE_MATCHES_MACRO,
604 impl<'tcx> LateLintPass<'tcx> for Matches {
605 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
606 if expr.span.from_expansion() {
610 redundant_pattern_match::check(cx, expr);
612 if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) {
613 if !match_like_matches::check(cx, expr) {
614 match_same_arms::check(cx, expr);
617 match_same_arms::check(cx, expr);
620 if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind {
621 single_match::check(cx, ex, arms, expr);
622 match_bool::check(cx, ex, arms, expr);
623 overlapping_arms::check(cx, ex, arms);
624 match_wild_err_arm::check(cx, ex, arms);
625 match_wild_enum::check(cx, ex, arms);
626 match_as_ref::check(cx, ex, arms, expr);
627 check_wild_in_or_pats(cx, arms);
629 if self.infallible_destructuring_match_linted {
630 self.infallible_destructuring_match_linted = false;
632 match_single_binding::check(cx, ex, arms, expr);
635 if let ExprKind::Match(ex, arms, _) = expr.kind {
636 check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr);
640 fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
642 if !local.span.from_expansion();
643 if let Some(expr) = local.init;
644 if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
645 if arms.len() == 1 && arms[0].guard.is_none();
646 if let PatKind::TupleStruct(
647 QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
649 if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind;
650 let body = peel_blocks(arms[0].body);
651 if path_to_local_id(body, arg);
654 let mut applicability = Applicability::MachineApplicable;
655 self.infallible_destructuring_match_linted = true;
658 INFALLIBLE_DESTRUCTURING_MATCH,
660 "you seem to be trying to use `match` to destructure a single infallible pattern. \
661 Consider using `let`",
665 snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
666 snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
667 snippet_with_applicability(cx, target.span, "..", &mut applicability),
675 fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
677 if !pat.span.from_expansion();
678 if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind;
679 if let Some(def_id) = path.res.opt_def_id();
680 let ty = cx.tcx.type_of(def_id);
681 if let ty::Adt(def, _) = ty.kind();
682 if def.is_struct() || def.is_union();
683 if fields.len() == def.non_enum_variant().fields.len();
688 REST_PAT_IN_FULLY_BOUND_STRUCTS,
690 "unnecessary use of `..` pattern in struct binding. All fields were already bound",
692 "consider removing `..` from this binding",
698 extract_msrv_attr!(LateContext);
701 fn check_match_ref_pats<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>)
704 I: Clone + Iterator<Item = &'a Pat<'b>>,
706 if !has_multiple_ref_pats(pats.clone()) {
710 let (first_sugg, msg, title);
711 let span = ex.span.source_callsite();
712 if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind {
713 first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
715 title = "you don't need to add `&` to both the expression and the patterns";
717 first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string()));
718 msg = "instead of prefixing all patterns with `&`, you can dereference the expression";
719 title = "you don't need to add `&` to all patterns";
722 let remaining_suggs = pats.filter_map(|pat| {
723 if let PatKind::Ref(refp, _) = pat.kind {
724 Some((pat.span, snippet(cx, refp.span, "..").to_string()))
730 span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| {
731 if !expr.span.from_expansion() {
732 multispan_sugg(diag, msg, first_sugg.chain(remaining_suggs));
737 fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) {
739 if let PatKind::Or(fields) = arm.pat.kind {
740 // look for multiple fields in this arm that contains at least one Wild pattern
741 if fields.len() > 1 && fields.iter().any(is_wild) {
744 WILDCARD_IN_OR_PATTERNS,
746 "wildcard pattern covers any other pattern as it will match anyway",
748 "consider handling `_` separately",
755 fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool
758 I: Iterator<Item = &'a Pat<'b>>,
760 let mut ref_count = 0;
761 for opt in pats.map(|pat| match pat.kind {
762 PatKind::Ref(..) => Some(true), // &-patterns
763 PatKind::Wild => Some(false), // an "anything" wildcard is also fine
764 _ => None, // any other pattern is not fine
766 if let Some(inner) = opt {