1 use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
2 use clippy_utils::source::snippet_with_applicability;
3 use clippy_utils::{is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, strip_pat_refs};
4 use if_chain::if_chain;
5 use rustc_errors::Applicability;
6 use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat, PatKind, QPath};
7 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_semver::RustcVersion;
10 use rustc_session::{declare_tool_lint, impl_lint_pass};
14 mod match_like_matches;
17 mod match_single_binding;
19 mod match_wild_err_arm;
21 mod redundant_pattern_match;
24 declare_clippy_lint! {
26 /// Checks for matches with a single arm where an `if let`
27 /// will usually suffice.
29 /// ### Why is this bad?
30 /// Just readability – `if let` nests less than a `match`.
34 /// # fn bar(stool: &str) {}
35 /// # let x = Some("abc");
38 /// Some(ref foo) => bar(foo),
43 /// if let Some(ref foo) = x {
47 #[clippy::version = "pre 1.29.0"]
50 "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
53 declare_clippy_lint! {
55 /// Checks for matches with two arms where an `if let else` will
58 /// ### Why is this bad?
59 /// Just readability – `if let` nests less than a `match`.
61 /// ### Known problems
62 /// Personal style preferences may differ.
68 /// # fn bar(foo: &usize) {}
69 /// # let other_ref: usize = 1;
70 /// # let x: Option<&usize> = Some(&1);
72 /// Some(ref foo) => bar(foo),
73 /// _ => bar(&other_ref),
77 /// Using `if let` with `else`:
80 /// # fn bar(foo: &usize) {}
81 /// # let other_ref: usize = 1;
82 /// # let x: Option<&usize> = Some(&1);
83 /// if let Some(ref foo) = x {
89 #[clippy::version = "pre 1.29.0"]
90 pub SINGLE_MATCH_ELSE,
92 "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
95 declare_clippy_lint! {
97 /// Checks for matches where all arms match a reference,
98 /// suggesting to remove the reference and deref the matched expression
99 /// instead. It also checks for `if let &foo = bar` blocks.
101 /// ### Why is this bad?
102 /// It just makes the code less readable. That reference
103 /// destructuring adds nothing to the code.
109 /// &A(ref y) => foo(y),
116 /// A(ref y) => foo(y),
121 #[clippy::version = "pre 1.29.0"]
124 "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
127 declare_clippy_lint! {
129 /// Checks for matches where match expression is a `bool`. It
130 /// suggests to replace the expression with an `if...else` block.
132 /// ### Why is this bad?
133 /// It makes the code less readable.
139 /// let condition: bool = true;
140 /// match condition {
145 /// Use if/else instead:
149 /// let condition: bool = true;
156 #[clippy::version = "pre 1.29.0"]
159 "a `match` on a boolean expression instead of an `if..else` block"
162 declare_clippy_lint! {
164 /// Checks for overlapping match arms.
166 /// ### Why is this bad?
167 /// It is likely to be an error and if not, makes the code
174 /// 1..=10 => println!("1 ... 10"),
175 /// 5..=15 => println!("5 ... 15"),
179 #[clippy::version = "pre 1.29.0"]
180 pub MATCH_OVERLAPPING_ARM,
182 "a `match` with overlapping arms"
185 declare_clippy_lint! {
187 /// Checks for arm which matches all errors with `Err(_)`
188 /// and take drastic actions like `panic!`.
190 /// ### Why is this bad?
191 /// It is generally a bad practice, similar to
192 /// catching all exceptions in java with `catch(Exception)`
196 /// let x: Result<i32, &str> = Ok(3);
198 /// Ok(_) => println!("ok"),
199 /// Err(_) => panic!("err"),
202 #[clippy::version = "pre 1.29.0"]
203 pub MATCH_WILD_ERR_ARM,
205 "a `match` with `Err(_)` arm and take drastic actions"
208 declare_clippy_lint! {
210 /// Checks for match which is used to add a reference to an
213 /// ### Why is this bad?
214 /// Using `as_ref()` or `as_mut()` instead is shorter.
218 /// let x: Option<()> = None;
221 /// let r: Option<&()> = match x {
223 /// Some(ref v) => Some(v),
227 /// let r: Option<&()> = x.as_ref();
229 #[clippy::version = "pre 1.29.0"]
232 "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
235 declare_clippy_lint! {
237 /// Checks for wildcard enum matches using `_`.
239 /// ### Why is this bad?
240 /// New enum variants added by library updates can be missed.
242 /// ### Known problems
243 /// Suggested replacements may be incorrect if guards exhaustively cover some
244 /// variants, and also may not use correct path to enum if it's not present in the current scope.
248 /// # enum Foo { A(usize), B(usize) }
249 /// # let x = Foo::B(1);
262 #[clippy::version = "1.34.0"]
263 pub WILDCARD_ENUM_MATCH_ARM,
265 "a wildcard enum match arm using `_`"
268 declare_clippy_lint! {
270 /// Checks for wildcard enum matches for a single variant.
272 /// ### Why is this bad?
273 /// New enum variants added by library updates can be missed.
275 /// ### Known problems
276 /// Suggested replacements may not use correct path to enum
277 /// if it's not present in the current scope.
281 /// # enum Foo { A, B, C }
282 /// # let x = Foo::B;
297 #[clippy::version = "1.45.0"]
298 pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
300 "a wildcard enum match for a single variant"
303 declare_clippy_lint! {
305 /// Checks for wildcard pattern used with others patterns in same match arm.
307 /// ### Why is this bad?
308 /// Wildcard pattern already covers any other pattern as it will match anyway.
309 /// It makes the code less readable, especially to spot wildcard pattern use in match arm.
325 #[clippy::version = "1.42.0"]
326 pub WILDCARD_IN_OR_PATTERNS,
328 "a wildcard pattern used with others patterns in same match arm"
331 declare_clippy_lint! {
333 /// Checks for matches being used to destructure a single-variant enum
334 /// or tuple struct where a `let` will suffice.
336 /// ### Why is this bad?
337 /// Just readability – `let` doesn't nest, whereas a `match` does.
345 /// let wrapper = Wrapper::Data(42);
347 /// let data = match wrapper {
348 /// Wrapper::Data(i) => i,
352 /// The correct use would be:
358 /// let wrapper = Wrapper::Data(42);
359 /// let Wrapper::Data(data) = wrapper;
361 #[clippy::version = "pre 1.29.0"]
362 pub INFALLIBLE_DESTRUCTURING_MATCH,
364 "a `match` statement with a single infallible arm instead of a `let`"
367 declare_clippy_lint! {
369 /// Checks for useless match that binds to only one value.
371 /// ### Why is this bad?
372 /// Readability and needless complexity.
374 /// ### Known problems
375 /// Suggested replacements may be incorrect when `match`
376 /// is actually binding temporary value, bringing a 'dropped while borrowed' error.
391 /// let (c, d) = (a, b);
393 #[clippy::version = "1.43.0"]
394 pub MATCH_SINGLE_BINDING,
396 "a match with a single binding instead of using `let` statement"
399 declare_clippy_lint! {
401 /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched.
403 /// ### Why is this bad?
404 /// Correctness and readability. It's like having a wildcard pattern after
405 /// matching all enum variants explicitly.
409 /// # struct A { a: i32 }
410 /// let a = A { a: 5 };
414 /// A { a: 5, .. } => {},
420 /// A { a: 5 } => {},
424 #[clippy::version = "1.43.0"]
425 pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
427 "a match on a struct that binds all fields but still uses the wildcard pattern"
430 declare_clippy_lint! {
432 /// Lint for redundant pattern matching over `Result`, `Option`,
433 /// `std::task::Poll` or `std::net::IpAddr`
435 /// ### Why is this bad?
436 /// It's more concise and clear to just use the proper
439 /// ### Known problems
440 /// This will change the drop order for the matched type. Both `if let` and
441 /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
442 /// value before entering the block. For most types this change will not matter, but for a few
443 /// types this will not be an acceptable change (e.g. locks). See the
444 /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
449 /// # use std::task::Poll;
450 /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
451 /// if let Ok(_) = Ok::<i32, i32>(42) {}
452 /// if let Err(_) = Err::<i32, i32>(42) {}
453 /// if let None = None::<()> {}
454 /// if let Some(_) = Some(42) {}
455 /// if let Poll::Pending = Poll::Pending::<()> {}
456 /// if let Poll::Ready(_) = Poll::Ready(42) {}
457 /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
458 /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
459 /// match Ok::<i32, i32>(42) {
465 /// The more idiomatic use would be:
468 /// # use std::task::Poll;
469 /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
470 /// if Ok::<i32, i32>(42).is_ok() {}
471 /// if Err::<i32, i32>(42).is_err() {}
472 /// if None::<()>.is_none() {}
473 /// if Some(42).is_some() {}
474 /// if Poll::Pending::<()>.is_pending() {}
475 /// if Poll::Ready(42).is_ready() {}
476 /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
477 /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
478 /// Ok::<i32, i32>(42).is_ok();
480 #[clippy::version = "1.31.0"]
481 pub REDUNDANT_PATTERN_MATCHING,
483 "use the proper utility function avoiding an `if let`"
486 declare_clippy_lint! {
488 /// Checks for `match` or `if let` expressions producing a
489 /// `bool` that could be written using `matches!`
491 /// ### Why is this bad?
492 /// Readability and needless complexity.
494 /// ### Known problems
495 /// This lint falsely triggers, if there are arms with
496 /// `cfg` attributes that remove an arm evaluating to `false`.
503 /// let a = match x {
508 /// let a = if let Some(0) = x {
515 /// let a = matches!(x, Some(0));
517 #[clippy::version = "1.47.0"]
518 pub MATCH_LIKE_MATCHES_MACRO,
520 "a match that could be written with the matches! macro"
523 declare_clippy_lint! {
525 /// Checks for `match` with identical arm bodies.
527 /// ### Why is this bad?
528 /// This is probably a copy & paste error. If arm bodies
529 /// are the same on purpose, you can factor them
530 /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
532 /// ### Known problems
533 /// False positive possible with order dependent `match`
535 /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
542 /// Baz => bar(), // <= oops
546 /// This should probably be
551 /// Baz => baz(), // <= fixed
555 /// or if the original code was not a typo:
558 /// Bar | Baz => bar(), // <= shows the intent better
562 #[clippy::version = "pre 1.29.0"]
565 "`match` with identical arm bodies"
570 msrv: Option<RustcVersion>,
571 infallible_destructuring_match_linted: bool,
576 pub fn new(msrv: Option<RustcVersion>) -> Self {
584 impl_lint_pass!(Matches => [
589 MATCH_OVERLAPPING_ARM,
592 WILDCARD_ENUM_MATCH_ARM,
593 MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
594 WILDCARD_IN_OR_PATTERNS,
595 MATCH_SINGLE_BINDING,
596 INFALLIBLE_DESTRUCTURING_MATCH,
597 REST_PAT_IN_FULLY_BOUND_STRUCTS,
598 REDUNDANT_PATTERN_MATCHING,
599 MATCH_LIKE_MATCHES_MACRO,
603 impl<'tcx> LateLintPass<'tcx> for Matches {
604 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
605 if expr.span.from_expansion() {
609 redundant_pattern_match::check(cx, expr);
611 if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) {
612 if !match_like_matches::check(cx, expr) {
613 match_same_arms::check(cx, expr);
616 match_same_arms::check(cx, expr);
619 if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind {
620 single_match::check(cx, ex, arms, expr);
621 match_bool::check(cx, ex, arms, expr);
622 overlapping_arms::check(cx, ex, arms);
623 match_wild_err_arm::check(cx, ex, arms);
624 match_wild_enum::check(cx, ex, arms);
625 match_as_ref::check(cx, ex, arms, expr);
626 check_wild_in_or_pats(cx, arms);
628 if self.infallible_destructuring_match_linted {
629 self.infallible_destructuring_match_linted = false;
631 match_single_binding::check(cx, ex, arms, expr);
634 if let ExprKind::Match(ex, arms, _) = expr.kind {
635 match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
639 fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
641 if !local.span.from_expansion();
642 if let Some(expr) = local.init;
643 if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
644 if arms.len() == 1 && arms[0].guard.is_none();
645 if let PatKind::TupleStruct(
646 QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
648 if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind;
649 let body = peel_blocks(arms[0].body);
650 if path_to_local_id(body, arg);
653 let mut applicability = Applicability::MachineApplicable;
654 self.infallible_destructuring_match_linted = true;
657 INFALLIBLE_DESTRUCTURING_MATCH,
659 "you seem to be trying to use `match` to destructure a single infallible pattern. \
660 Consider using `let`",
664 snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
665 snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
666 snippet_with_applicability(cx, target.span, "..", &mut applicability),
674 fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
676 if !pat.span.from_expansion();
677 if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind;
678 if let Some(def_id) = path.res.opt_def_id();
679 let ty = cx.tcx.type_of(def_id);
680 if let ty::Adt(def, _) = ty.kind();
681 if def.is_struct() || def.is_union();
682 if fields.len() == def.non_enum_variant().fields.len();
687 REST_PAT_IN_FULLY_BOUND_STRUCTS,
689 "unnecessary use of `..` pattern in struct binding. All fields were already bound",
691 "consider removing `..` from this binding",
697 extract_msrv_attr!(LateContext);
700 fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) {
702 if let PatKind::Or(fields) = arm.pat.kind {
703 // look for multiple fields in this arm that contains at least one Wild pattern
704 if fields.len() > 1 && fields.iter().any(is_wild) {
707 WILDCARD_IN_OR_PATTERNS,
709 "wildcard pattern covers any other pattern as it will match anyway",
711 "consider handling `_` separately",