1 use clippy_utils::diagnostics::span_lint_and_help;
2 use clippy_utils::{is_wild, meets_msrv, msrvs};
3 use if_chain::if_chain;
4 use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat, PatKind, QPath};
5 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_semver::RustcVersion;
8 use rustc_session::{declare_tool_lint, impl_lint_pass};
10 mod infalliable_detructuring_match;
13 mod match_like_matches;
16 mod match_single_binding;
18 mod match_wild_err_arm;
20 mod redundant_pattern_match;
23 declare_clippy_lint! {
25 /// Checks for matches with a single arm where an `if let`
26 /// will usually suffice.
28 /// ### Why is this bad?
29 /// Just readability – `if let` nests less than a `match`.
33 /// # fn bar(stool: &str) {}
34 /// # let x = Some("abc");
37 /// Some(ref foo) => bar(foo),
42 /// if let Some(ref foo) = x {
46 #[clippy::version = "pre 1.29.0"]
49 "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
52 declare_clippy_lint! {
54 /// Checks for matches with two arms where an `if let else` will
57 /// ### Why is this bad?
58 /// Just readability – `if let` nests less than a `match`.
60 /// ### Known problems
61 /// Personal style preferences may differ.
67 /// # fn bar(foo: &usize) {}
68 /// # let other_ref: usize = 1;
69 /// # let x: Option<&usize> = Some(&1);
71 /// Some(ref foo) => bar(foo),
72 /// _ => bar(&other_ref),
76 /// Using `if let` with `else`:
79 /// # fn bar(foo: &usize) {}
80 /// # let other_ref: usize = 1;
81 /// # let x: Option<&usize> = Some(&1);
82 /// if let Some(ref foo) = x {
88 #[clippy::version = "pre 1.29.0"]
89 pub SINGLE_MATCH_ELSE,
91 "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
94 declare_clippy_lint! {
96 /// Checks for matches where all arms match a reference,
97 /// suggesting to remove the reference and deref the matched expression
98 /// instead. It also checks for `if let &foo = bar` blocks.
100 /// ### Why is this bad?
101 /// It just makes the code less readable. That reference
102 /// destructuring adds nothing to the code.
108 /// &A(ref y) => foo(y),
115 /// A(ref y) => foo(y),
120 #[clippy::version = "pre 1.29.0"]
123 "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
126 declare_clippy_lint! {
128 /// Checks for matches where match expression is a `bool`. It
129 /// suggests to replace the expression with an `if...else` block.
131 /// ### Why is this bad?
132 /// It makes the code less readable.
138 /// let condition: bool = true;
139 /// match condition {
144 /// Use if/else instead:
148 /// let condition: bool = true;
155 #[clippy::version = "pre 1.29.0"]
158 "a `match` on a boolean expression instead of an `if..else` block"
161 declare_clippy_lint! {
163 /// Checks for overlapping match arms.
165 /// ### Why is this bad?
166 /// It is likely to be an error and if not, makes the code
173 /// 1..=10 => println!("1 ... 10"),
174 /// 5..=15 => println!("5 ... 15"),
178 #[clippy::version = "pre 1.29.0"]
179 pub MATCH_OVERLAPPING_ARM,
181 "a `match` with overlapping arms"
184 declare_clippy_lint! {
186 /// Checks for arm which matches all errors with `Err(_)`
187 /// and take drastic actions like `panic!`.
189 /// ### Why is this bad?
190 /// It is generally a bad practice, similar to
191 /// catching all exceptions in java with `catch(Exception)`
195 /// let x: Result<i32, &str> = Ok(3);
197 /// Ok(_) => println!("ok"),
198 /// Err(_) => panic!("err"),
201 #[clippy::version = "pre 1.29.0"]
202 pub MATCH_WILD_ERR_ARM,
204 "a `match` with `Err(_)` arm and take drastic actions"
207 declare_clippy_lint! {
209 /// Checks for match which is used to add a reference to an
212 /// ### Why is this bad?
213 /// Using `as_ref()` or `as_mut()` instead is shorter.
217 /// let x: Option<()> = None;
220 /// let r: Option<&()> = match x {
222 /// Some(ref v) => Some(v),
226 /// let r: Option<&()> = x.as_ref();
228 #[clippy::version = "pre 1.29.0"]
231 "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
234 declare_clippy_lint! {
236 /// Checks for wildcard enum matches using `_`.
238 /// ### Why is this bad?
239 /// New enum variants added by library updates can be missed.
241 /// ### Known problems
242 /// Suggested replacements may be incorrect if guards exhaustively cover some
243 /// variants, and also may not use correct path to enum if it's not present in the current scope.
247 /// # enum Foo { A(usize), B(usize) }
248 /// # let x = Foo::B(1);
261 #[clippy::version = "1.34.0"]
262 pub WILDCARD_ENUM_MATCH_ARM,
264 "a wildcard enum match arm using `_`"
267 declare_clippy_lint! {
269 /// Checks for wildcard enum matches for a single variant.
271 /// ### Why is this bad?
272 /// New enum variants added by library updates can be missed.
274 /// ### Known problems
275 /// Suggested replacements may not use correct path to enum
276 /// if it's not present in the current scope.
280 /// # enum Foo { A, B, C }
281 /// # let x = Foo::B;
296 #[clippy::version = "1.45.0"]
297 pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
299 "a wildcard enum match for a single variant"
302 declare_clippy_lint! {
304 /// Checks for wildcard pattern used with others patterns in same match arm.
306 /// ### Why is this bad?
307 /// Wildcard pattern already covers any other pattern as it will match anyway.
308 /// It makes the code less readable, especially to spot wildcard pattern use in match arm.
324 #[clippy::version = "1.42.0"]
325 pub WILDCARD_IN_OR_PATTERNS,
327 "a wildcard pattern used with others patterns in same match arm"
330 declare_clippy_lint! {
332 /// Checks for matches being used to destructure a single-variant enum
333 /// or tuple struct where a `let` will suffice.
335 /// ### Why is this bad?
336 /// Just readability – `let` doesn't nest, whereas a `match` does.
344 /// let wrapper = Wrapper::Data(42);
346 /// let data = match wrapper {
347 /// Wrapper::Data(i) => i,
351 /// The correct use would be:
357 /// let wrapper = Wrapper::Data(42);
358 /// let Wrapper::Data(data) = wrapper;
360 #[clippy::version = "pre 1.29.0"]
361 pub INFALLIBLE_DESTRUCTURING_MATCH,
363 "a `match` statement with a single infallible arm instead of a `let`"
366 declare_clippy_lint! {
368 /// Checks for useless match that binds to only one value.
370 /// ### Why is this bad?
371 /// Readability and needless complexity.
373 /// ### Known problems
374 /// Suggested replacements may be incorrect when `match`
375 /// is actually binding temporary value, bringing a 'dropped while borrowed' error.
390 /// let (c, d) = (a, b);
392 #[clippy::version = "1.43.0"]
393 pub MATCH_SINGLE_BINDING,
395 "a match with a single binding instead of using `let` statement"
398 declare_clippy_lint! {
400 /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched.
402 /// ### Why is this bad?
403 /// Correctness and readability. It's like having a wildcard pattern after
404 /// matching all enum variants explicitly.
408 /// # struct A { a: i32 }
409 /// let a = A { a: 5 };
413 /// A { a: 5, .. } => {},
419 /// A { a: 5 } => {},
423 #[clippy::version = "1.43.0"]
424 pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
426 "a match on a struct that binds all fields but still uses the wildcard pattern"
429 declare_clippy_lint! {
431 /// Lint for redundant pattern matching over `Result`, `Option`,
432 /// `std::task::Poll` or `std::net::IpAddr`
434 /// ### Why is this bad?
435 /// It's more concise and clear to just use the proper
438 /// ### Known problems
439 /// This will change the drop order for the matched type. Both `if let` and
440 /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
441 /// value before entering the block. For most types this change will not matter, but for a few
442 /// types this will not be an acceptable change (e.g. locks). See the
443 /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
448 /// # use std::task::Poll;
449 /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
450 /// if let Ok(_) = Ok::<i32, i32>(42) {}
451 /// if let Err(_) = Err::<i32, i32>(42) {}
452 /// if let None = None::<()> {}
453 /// if let Some(_) = Some(42) {}
454 /// if let Poll::Pending = Poll::Pending::<()> {}
455 /// if let Poll::Ready(_) = Poll::Ready(42) {}
456 /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
457 /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
458 /// match Ok::<i32, i32>(42) {
464 /// The more idiomatic use would be:
467 /// # use std::task::Poll;
468 /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
469 /// if Ok::<i32, i32>(42).is_ok() {}
470 /// if Err::<i32, i32>(42).is_err() {}
471 /// if None::<()>.is_none() {}
472 /// if Some(42).is_some() {}
473 /// if Poll::Pending::<()>.is_pending() {}
474 /// if Poll::Ready(42).is_ready() {}
475 /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
476 /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
477 /// Ok::<i32, i32>(42).is_ok();
479 #[clippy::version = "1.31.0"]
480 pub REDUNDANT_PATTERN_MATCHING,
482 "use the proper utility function avoiding an `if let`"
485 declare_clippy_lint! {
487 /// Checks for `match` or `if let` expressions producing a
488 /// `bool` that could be written using `matches!`
490 /// ### Why is this bad?
491 /// Readability and needless complexity.
493 /// ### Known problems
494 /// This lint falsely triggers, if there are arms with
495 /// `cfg` attributes that remove an arm evaluating to `false`.
502 /// let a = match x {
507 /// let a = if let Some(0) = x {
514 /// let a = matches!(x, Some(0));
516 #[clippy::version = "1.47.0"]
517 pub MATCH_LIKE_MATCHES_MACRO,
519 "a match that could be written with the matches! macro"
522 declare_clippy_lint! {
524 /// Checks for `match` with identical arm bodies.
526 /// ### Why is this bad?
527 /// This is probably a copy & paste error. If arm bodies
528 /// are the same on purpose, you can factor them
529 /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
531 /// ### Known problems
532 /// False positive possible with order dependent `match`
534 /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
541 /// Baz => bar(), // <= oops
545 /// This should probably be
550 /// Baz => baz(), // <= fixed
554 /// or if the original code was not a typo:
557 /// Bar | Baz => bar(), // <= shows the intent better
561 #[clippy::version = "pre 1.29.0"]
564 "`match` with identical arm bodies"
569 msrv: Option<RustcVersion>,
570 infallible_destructuring_match_linted: bool,
575 pub fn new(msrv: Option<RustcVersion>) -> Self {
583 impl_lint_pass!(Matches => [
588 MATCH_OVERLAPPING_ARM,
591 WILDCARD_ENUM_MATCH_ARM,
592 MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
593 WILDCARD_IN_OR_PATTERNS,
594 MATCH_SINGLE_BINDING,
595 INFALLIBLE_DESTRUCTURING_MATCH,
596 REST_PAT_IN_FULLY_BOUND_STRUCTS,
597 REDUNDANT_PATTERN_MATCHING,
598 MATCH_LIKE_MATCHES_MACRO,
602 impl<'tcx> LateLintPass<'tcx> for Matches {
603 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
604 if expr.span.from_expansion() {
608 redundant_pattern_match::check(cx, expr);
610 if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) {
611 if !match_like_matches::check(cx, expr) {
612 match_same_arms::check(cx, expr);
615 match_same_arms::check(cx, expr);
618 if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind {
619 single_match::check(cx, ex, arms, expr);
620 match_bool::check(cx, ex, arms, expr);
621 overlapping_arms::check(cx, ex, arms);
622 match_wild_err_arm::check(cx, ex, arms);
623 match_wild_enum::check(cx, ex, arms);
624 match_as_ref::check(cx, ex, arms, expr);
625 check_wild_in_or_pats(cx, arms);
627 if self.infallible_destructuring_match_linted {
628 self.infallible_destructuring_match_linted = false;
630 match_single_binding::check(cx, ex, arms, expr);
633 if let ExprKind::Match(ex, arms, _) = expr.kind {
634 match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
638 fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
639 self.infallible_destructuring_match_linted |= infalliable_detructuring_match::check(cx, local);
642 fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
644 if !pat.span.from_expansion();
645 if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind;
646 if let Some(def_id) = path.res.opt_def_id();
647 let ty = cx.tcx.type_of(def_id);
648 if let ty::Adt(def, _) = ty.kind();
649 if def.is_struct() || def.is_union();
650 if fields.len() == def.non_enum_variant().fields.len();
655 REST_PAT_IN_FULLY_BOUND_STRUCTS,
657 "unnecessary use of `..` pattern in struct binding. All fields were already bound",
659 "consider removing `..` from this binding",
665 extract_msrv_attr!(LateContext);
668 fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) {
670 if let PatKind::Or(fields) = arm.pat.kind {
671 // look for multiple fields in this arm that contains at least one Wild pattern
672 if fields.len() > 1 && fields.iter().any(is_wild) {
675 WILDCARD_IN_OR_PATTERNS,
677 "wildcard pattern covers any other pattern as it will match anyway",
679 "consider handling `_` separately",