mod collapsible_match; mod infallible_destructuring_match; mod manual_filter; mod manual_map; mod manual_ok_err; mod manual_unwrap_or; mod manual_utils; mod match_as_ref; mod match_bool; mod match_like_matches; mod match_on_vec_items; mod match_ref_pats; mod match_same_arms; mod match_single_binding; mod match_str_case_mismatch; mod match_wild_enum; mod match_wild_err_arm; mod needless_match; mod overlapping_arms; mod redundant_guards; mod redundant_pattern_match; mod rest_pat_in_fully_bound_struct; mod significant_drop_in_scrutinee; mod single_match; mod try_err; mod wild_in_or_pats; use clippy_config::Conf; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::walk_span_to_context; use clippy_utils::{ higher, is_direct_expn_of, is_in_const_context, is_span_match, span_contains_cfg, span_extract_comments, }; use rustc_hir::{Arm, Expr, ExprKind, LetStmt, MatchSource, Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::{SpanData, SyntaxContext}; declare_clippy_lint! { /// ### What it does /// Checks for matches with a single arm where an `if let` /// will usually suffice. /// /// This intentionally does not lint if there are comments /// inside of the other arm, so as to allow the user to document /// why having another explicit pattern with an empty body is necessary, /// or because the comments need to be preserved for other reasons. /// /// ### Why is this bad? /// Just readability – `if let` nests less than a `match`. /// /// ### Example /// ```no_run /// # fn bar(stool: &str) {} /// # let x = Some("abc"); /// match x { /// Some(ref foo) => bar(foo), /// _ => (), /// } /// ``` /// /// Use instead: /// ```no_run /// # fn bar(stool: &str) {} /// # let x = Some("abc"); /// if let Some(ref foo) = x { /// bar(foo); /// } /// ``` #[clippy::version = "pre 1.29.0"] pub SINGLE_MATCH, style, "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`" } declare_clippy_lint! { /// ### What it does /// Checks for matches with two arms where an `if let else` will /// usually suffice. /// /// ### Why is this bad? /// Just readability – `if let` nests less than a `match`. /// /// ### Known problems /// Personal style preferences may differ. /// /// ### Example /// Using `match`: /// /// ```no_run /// # fn bar(foo: &usize) {} /// # let other_ref: usize = 1; /// # let x: Option<&usize> = Some(&1); /// match x { /// Some(ref foo) => bar(foo), /// _ => bar(&other_ref), /// } /// ``` /// /// Using `if let` with `else`: /// /// ```no_run /// # fn bar(foo: &usize) {} /// # let other_ref: usize = 1; /// # let x: Option<&usize> = Some(&1); /// if let Some(ref foo) = x { /// bar(foo); /// } else { /// bar(&other_ref); /// } /// ``` #[clippy::version = "pre 1.29.0"] pub SINGLE_MATCH_ELSE, pedantic, "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern" } declare_clippy_lint! { /// ### What it does /// Checks for matches where all arms match a reference, /// suggesting to remove the reference and deref the matched expression /// instead. It also checks for `if let &foo = bar` blocks. /// /// ### Why is this bad? /// It just makes the code less readable. That reference /// destructuring adds nothing to the code. /// /// ### Example /// ```rust,ignore /// match x { /// &A(ref y) => foo(y), /// &B => bar(), /// _ => frob(&x), /// } /// ``` /// /// Use instead: /// ```rust,ignore /// match *x { /// A(ref y) => foo(y), /// B => bar(), /// _ => frob(x), /// } /// ``` #[clippy::version = "pre 1.29.0"] pub MATCH_REF_PATS, style, "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression" } declare_clippy_lint! { /// ### What it does /// Checks for matches where match expression is a `bool`. It /// suggests to replace the expression with an `if...else` block. /// /// ### Why is this bad? /// It makes the code less readable. /// /// ### Example /// ```no_run /// # fn foo() {} /// # fn bar() {} /// let condition: bool = true; /// match condition { /// true => foo(), /// false => bar(), /// } /// ``` /// Use if/else instead: /// ```no_run /// # fn foo() {} /// # fn bar() {} /// let condition: bool = true; /// if condition { /// foo(); /// } else { /// bar(); /// } /// ``` #[clippy::version = "pre 1.29.0"] pub MATCH_BOOL, pedantic, "a `match` on a boolean expression instead of an `if..else` block" } declare_clippy_lint! { /// ### What it does /// Checks for overlapping match arms. /// /// ### Why is this bad? /// It is likely to be an error and if not, makes the code /// less obvious. /// /// ### Example /// ```no_run /// let x = 5; /// match x { /// 1..=10 => println!("1 ... 10"), /// 5..=15 => println!("5 ... 15"), /// _ => (), /// } /// ``` #[clippy::version = "pre 1.29.0"] pub MATCH_OVERLAPPING_ARM, style, "a `match` with overlapping arms" } declare_clippy_lint! { /// ### What it does /// Checks for arm which matches all errors with `Err(_)` /// and take drastic actions like `panic!`. /// /// ### Why is this bad? /// It is generally a bad practice, similar to /// catching all exceptions in java with `catch(Exception)` /// /// ### Example /// ```no_run /// let x: Result = Ok(3); /// match x { /// Ok(_) => println!("ok"), /// Err(_) => panic!("err"), /// } /// ``` #[clippy::version = "pre 1.29.0"] pub MATCH_WILD_ERR_ARM, pedantic, "a `match` with `Err(_)` arm and take drastic actions" } declare_clippy_lint! { /// ### What it does /// Checks for match which is used to add a reference to an /// `Option` value. /// /// ### Why is this bad? /// Using `as_ref()` or `as_mut()` instead is shorter. /// /// ### Example /// ```no_run /// let x: Option<()> = None; /// /// let r: Option<&()> = match x { /// None => None, /// Some(ref v) => Some(v), /// }; /// ``` /// /// Use instead: /// ```no_run /// let x: Option<()> = None; /// /// let r: Option<&()> = x.as_ref(); /// ``` #[clippy::version = "pre 1.29.0"] pub MATCH_AS_REF, complexity, "a `match` on an Option value instead of using `as_ref()` or `as_mut`" } declare_clippy_lint! { /// ### What it does /// Checks for wildcard enum matches using `_`. /// /// ### Why restrict this? /// New enum variants added by library updates can be missed. /// /// ### Known problems /// Suggested replacements may be incorrect if guards exhaustively cover some /// variants, and also may not use correct path to enum if it's not present in the current scope. /// /// ### Example /// ```no_run /// # enum Foo { A(usize), B(usize) } /// # let x = Foo::B(1); /// match x { /// Foo::A(_) => {}, /// _ => {}, /// } /// ``` /// /// Use instead: /// ```no_run /// # enum Foo { A(usize), B(usize) } /// # let x = Foo::B(1); /// match x { /// Foo::A(_) => {}, /// Foo::B(_) => {}, /// } /// ``` #[clippy::version = "1.34.0"] pub WILDCARD_ENUM_MATCH_ARM, restriction, "a wildcard enum match arm using `_`" } declare_clippy_lint! { /// ### What it does /// Checks for wildcard enum matches for a single variant. /// /// ### Why is this bad? /// New enum variants added by library updates can be missed. /// /// ### Known problems /// Suggested replacements may not use correct path to enum /// if it's not present in the current scope. /// /// ### Example /// ```no_run /// # enum Foo { A, B, C } /// # let x = Foo::B; /// match x { /// Foo::A => {}, /// Foo::B => {}, /// _ => {}, /// } /// ``` /// /// Use instead: /// ```no_run /// # enum Foo { A, B, C } /// # let x = Foo::B; /// match x { /// Foo::A => {}, /// Foo::B => {}, /// Foo::C => {}, /// } /// ``` #[clippy::version = "1.45.0"] pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS, pedantic, "a wildcard enum match for a single variant" } declare_clippy_lint! { /// ### What it does /// Checks for wildcard pattern used with others patterns in same match arm. /// /// ### Why is this bad? /// Wildcard pattern already covers any other pattern as it will match anyway. /// It makes the code less readable, especially to spot wildcard pattern use in match arm. /// /// ### Example /// ```no_run /// # let s = "foo"; /// match s { /// "a" => {}, /// "bar" | _ => {}, /// } /// ``` /// /// Use instead: /// ```no_run /// # let s = "foo"; /// match s { /// "a" => {}, /// _ => {}, /// } /// ``` #[clippy::version = "1.42.0"] pub WILDCARD_IN_OR_PATTERNS, complexity, "a wildcard pattern used with others patterns in same match arm" } declare_clippy_lint! { /// ### What it does /// Checks for matches being used to destructure a single-variant enum /// or tuple struct where a `let` will suffice. /// /// ### Why is this bad? /// Just readability – `let` doesn't nest, whereas a `match` does. /// /// ### Example /// ```no_run /// enum Wrapper { /// Data(i32), /// } /// /// let wrapper = Wrapper::Data(42); /// /// let data = match wrapper { /// Wrapper::Data(i) => i, /// }; /// ``` /// /// The correct use would be: /// ```no_run /// enum Wrapper { /// Data(i32), /// } /// /// let wrapper = Wrapper::Data(42); /// let Wrapper::Data(data) = wrapper; /// ``` #[clippy::version = "pre 1.29.0"] pub INFALLIBLE_DESTRUCTURING_MATCH, style, "a `match` statement with a single infallible arm instead of a `let`" } declare_clippy_lint! { /// ### What it does /// Checks for useless match that binds to only one value. /// /// ### Why is this bad? /// Readability and needless complexity. /// /// ### Known problems /// Suggested replacements may be incorrect when `match` /// is actually binding temporary value, bringing a 'dropped while borrowed' error. /// /// ### Example /// ```no_run /// # let a = 1; /// # let b = 2; /// match (a, b) { /// (c, d) => { /// // useless match /// } /// } /// ``` /// /// Use instead: /// ```no_run /// # let a = 1; /// # let b = 2; /// let (c, d) = (a, b); /// ``` #[clippy::version = "1.43.0"] pub MATCH_SINGLE_BINDING, complexity, "a match with a single binding instead of using `let` statement" } declare_clippy_lint! { /// ### What it does /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched. /// /// ### Why restrict this? /// Correctness and readability. It's like having a wildcard pattern after /// matching all enum variants explicitly. /// /// ### Example /// ```no_run /// # struct A { a: i32 } /// let a = A { a: 5 }; /// /// match a { /// A { a: 5, .. } => {}, /// _ => {}, /// } /// ``` /// /// Use instead: /// ```no_run /// # struct A { a: i32 } /// # let a = A { a: 5 }; /// match a { /// A { a: 5 } => {}, /// _ => {}, /// } /// ``` #[clippy::version = "1.43.0"] pub REST_PAT_IN_FULLY_BOUND_STRUCTS, restriction, "a match on a struct that binds all fields but still uses the wildcard pattern" } declare_clippy_lint! { /// ### What it does /// Lint for redundant pattern matching over `Result`, `Option`, /// `std::task::Poll`, `std::net::IpAddr` or `bool`s /// /// ### Why is this bad? /// It's more concise and clear to just use the proper /// utility function or using the condition directly /// /// ### Known problems /// For suggestions involving bindings in patterns, this will change the drop order for the matched type. /// Both `if let` and `while let` will drop the value at the end of the block, both `if` and `while` will drop the /// value before entering the block. For most types this change will not matter, but for a few /// types this will not be an acceptable change (e.g. locks). See the /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about /// drop order. /// /// ### Example /// ```no_run /// # use std::task::Poll; /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; /// if let Ok(_) = Ok::(42) {} /// if let Err(_) = Err::(42) {} /// if let None = None::<()> {} /// if let Some(_) = Some(42) {} /// if let Poll::Pending = Poll::Pending::<()> {} /// if let Poll::Ready(_) = Poll::Ready(42) {} /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {} /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {} /// match Ok::(42) { /// Ok(_) => true, /// Err(_) => false, /// }; /// /// let cond = true; /// if let true = cond {} /// matches!(cond, true); /// ``` /// /// The more idiomatic use would be: /// /// ```no_run /// # use std::task::Poll; /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; /// if Ok::(42).is_ok() {} /// if Err::(42).is_err() {} /// if None::<()>.is_none() {} /// if Some(42).is_some() {} /// if Poll::Pending::<()>.is_pending() {} /// if Poll::Ready(42).is_ready() {} /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {} /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {} /// Ok::(42).is_ok(); /// /// let cond = true; /// if cond {} /// cond; /// ``` #[clippy::version = "1.31.0"] pub REDUNDANT_PATTERN_MATCHING, style, "use the proper utility function avoiding an `if let`" } declare_clippy_lint! { /// ### What it does /// Checks for `match` or `if let` expressions producing a /// `bool` that could be written using `matches!` /// /// ### Why is this bad? /// Readability and needless complexity. /// /// ### Known problems /// This lint falsely triggers, if there are arms with /// `cfg` attributes that remove an arm evaluating to `false`. /// /// ### Example /// ```no_run /// let x = Some(5); /// /// let a = match x { /// Some(0) => true, /// _ => false, /// }; /// /// let a = if let Some(0) = x { /// true /// } else { /// false /// }; /// ``` /// /// Use instead: /// ```no_run /// let x = Some(5); /// let a = matches!(x, Some(0)); /// ``` #[clippy::version = "1.47.0"] pub MATCH_LIKE_MATCHES_MACRO, style, "a match that could be written with the matches! macro" } declare_clippy_lint! { /// ### What it does /// Checks for `match` with identical arm bodies. /// /// Note: Does not lint on wildcards if the `non_exhaustive_omitted_patterns_lint` feature is /// enabled and disallowed. /// /// ### Why is this bad? /// This is probably a copy & paste error. If arm bodies /// are the same on purpose, you can factor them /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns). /// /// ### Example /// ```rust,ignore /// match foo { /// Bar => bar(), /// Quz => quz(), /// Baz => bar(), // <= oops /// } /// ``` /// /// This should probably be /// ```rust,ignore /// match foo { /// Bar => bar(), /// Quz => quz(), /// Baz => baz(), // <= fixed /// } /// ``` /// /// or if the original code was not a typo: /// ```rust,ignore /// match foo { /// Bar | Baz => bar(), // <= shows the intent better /// Quz => quz(), /// } /// ``` #[clippy::version = "pre 1.29.0"] pub MATCH_SAME_ARMS, pedantic, "`match` with identical arm bodies" } declare_clippy_lint! { /// ### What it does /// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result` /// when function signatures are the same. /// /// ### Why is this bad? /// This `match` block does nothing and might not be what the coder intended. /// /// ### Example /// ```rust,ignore /// fn foo() -> Result<(), i32> { /// match result { /// Ok(val) => Ok(val), /// Err(err) => Err(err), /// } /// } /// /// fn bar() -> Option { /// if let Some(val) = option { /// Some(val) /// } else { /// None /// } /// } /// ``` /// /// Could be replaced as /// /// ```rust,ignore /// fn foo() -> Result<(), i32> { /// result /// } /// /// fn bar() -> Option { /// option /// } /// ``` #[clippy::version = "1.61.0"] pub NEEDLESS_MATCH, complexity, "`match` or match-like `if let` that are unnecessary" } declare_clippy_lint! { /// ### What it does /// Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together /// without adding any branches. /// /// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only /// cases where merging would most likely make the code more readable. /// /// ### Why is this bad? /// It is unnecessarily verbose and complex. /// /// ### Example /// ```no_run /// fn func(opt: Option>) { /// let n = match opt { /// Some(n) => match n { /// Ok(n) => n, /// _ => return, /// } /// None => return, /// }; /// } /// ``` /// Use instead: /// ```no_run /// fn func(opt: Option>) { /// let n = match opt { /// Some(Ok(n)) => n, /// _ => return, /// }; /// } /// ``` #[clippy::version = "1.50.0"] pub COLLAPSIBLE_MATCH, style, "Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together." } declare_clippy_lint! { /// ### What it does /// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`. /// /// ### Why is this bad? /// Concise code helps focusing on behavior instead of boilerplate. /// /// ### Example /// ```no_run /// let foo: Option = None; /// match foo { /// Some(v) => v, /// None => 1, /// }; /// ``` /// /// Use instead: /// ```no_run /// let foo: Option = None; /// foo.unwrap_or(1); /// ``` #[clippy::version = "1.49.0"] pub MANUAL_UNWRAP_OR, complexity, "finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`" } declare_clippy_lint! { /// ### What it does /// Checks for `match vec[idx]` or `match vec[n..m]`. /// /// ### Why is this bad? /// This can panic at runtime. /// /// ### Example /// ```rust, no_run /// let arr = vec![0, 1, 2, 3]; /// let idx = 1; /// /// match arr[idx] { /// 0 => println!("{}", 0), /// 1 => println!("{}", 3), /// _ => {}, /// } /// ``` /// /// Use instead: /// ```rust, no_run /// let arr = vec![0, 1, 2, 3]; /// let idx = 1; /// /// match arr.get(idx) { /// Some(0) => println!("{}", 0), /// Some(1) => println!("{}", 3), /// _ => {}, /// } /// ``` #[clippy::version = "1.45.0"] pub MATCH_ON_VEC_ITEMS, pedantic, "matching on vector elements can panic" } declare_clippy_lint! { /// ### What it does /// Checks for `match` expressions modifying the case of a string with non-compliant arms /// /// ### Why is this bad? /// The arm is unreachable, which is likely a mistake /// /// ### Example /// ```no_run /// # let text = "Foo"; /// match &*text.to_ascii_lowercase() { /// "foo" => {}, /// "Bar" => {}, /// _ => {}, /// } /// ``` /// Use instead: /// ```no_run /// # let text = "Foo"; /// match &*text.to_ascii_lowercase() { /// "foo" => {}, /// "bar" => {}, /// _ => {}, /// } /// ``` #[clippy::version = "1.58.0"] pub MATCH_STR_CASE_MISMATCH, correctness, "creation of a case altering match expression with non-compliant arms" } declare_clippy_lint! { /// ### What it does /// Checks for temporaries returned from function calls in a match scrutinee that have the /// `clippy::has_significant_drop` attribute. /// /// ### Why is this bad? /// The `clippy::has_significant_drop` attribute can be added to types whose Drop impls have /// an important side-effect, such as unlocking a mutex, making it important for users to be /// able to accurately understand their lifetimes. When a temporary is returned in a function /// call in a match scrutinee, its lifetime lasts until the end of the match block, which may /// be surprising. /// /// For `Mutex`es this can lead to a deadlock. This happens when the match scrutinee uses a /// function call that returns a `MutexGuard` and then tries to lock again in one of the match /// arms. In that case the `MutexGuard` in the scrutinee will not be dropped until the end of /// the match block and thus will not unlock. /// /// ### Example /// ```rust,ignore /// # use std::sync::Mutex; /// # struct State {} /// # impl State { /// # fn foo(&self) -> bool { /// # true /// # } /// # fn bar(&self) {} /// # } /// let mutex = Mutex::new(State {}); /// /// match mutex.lock().unwrap().foo() { /// true => { /// mutex.lock().unwrap().bar(); // Deadlock! /// } /// false => {} /// }; /// /// println!("All done!"); /// ``` /// Use instead: /// ```no_run /// # use std::sync::Mutex; /// # struct State {} /// # impl State { /// # fn foo(&self) -> bool { /// # true /// # } /// # fn bar(&self) {} /// # } /// let mutex = Mutex::new(State {}); /// /// let is_foo = mutex.lock().unwrap().foo(); /// match is_foo { /// true => { /// mutex.lock().unwrap().bar(); /// } /// false => {} /// }; /// /// println!("All done!"); /// ``` #[clippy::version = "1.60.0"] pub SIGNIFICANT_DROP_IN_SCRUTINEE, nursery, "warns when a temporary of a type with a drop with a significant side-effect might have a surprising lifetime" } declare_clippy_lint! { /// ### What it does /// Checks for usage of `Err(x)?`. /// /// ### Why restrict this? /// The `?` operator is designed to allow calls that /// can fail to be easily chained. For example, `foo()?.bar()` or /// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will /// always return), it is more clear to write `return Err(x)`. /// /// ### Example /// ```no_run /// fn foo(fail: bool) -> Result { /// if fail { /// Err("failed")?; /// } /// Ok(0) /// } /// ``` /// Could be written: /// /// ```no_run /// fn foo(fail: bool) -> Result { /// if fail { /// return Err("failed".into()); /// } /// Ok(0) /// } /// ``` #[clippy::version = "1.38.0"] pub TRY_ERR, restriction, "return errors explicitly rather than hiding them behind a `?`" } declare_clippy_lint! { /// ### What it does /// Checks for usage of `match` which could be implemented using `map` /// /// ### Why is this bad? /// Using the `map` method is clearer and more concise. /// /// ### Example /// ```no_run /// match Some(0) { /// Some(x) => Some(x + 1), /// None => None, /// }; /// ``` /// Use instead: /// ```no_run /// Some(0).map(|x| x + 1); /// ``` #[clippy::version = "1.52.0"] pub MANUAL_MAP, style, "reimplementation of `map`" } declare_clippy_lint! { /// ### What it does /// Checks for usage of `match` which could be implemented using `filter` /// /// ### Why is this bad? /// Using the `filter` method is clearer and more concise. /// /// ### Example /// ```no_run /// match Some(0) { /// Some(x) => if x % 2 == 0 { /// Some(x) /// } else { /// None /// }, /// None => None, /// }; /// ``` /// Use instead: /// ```no_run /// Some(0).filter(|&x| x % 2 == 0); /// ``` #[clippy::version = "1.66.0"] pub MANUAL_FILTER, complexity, "reimplementation of `filter`" } declare_clippy_lint! { /// ### What it does /// Checks for unnecessary guards in match expressions. /// /// ### Why is this bad? /// It's more complex and much less readable. Making it part of the pattern can improve /// exhaustiveness checking as well. /// /// ### Example /// ```rust,ignore /// match x { /// Some(x) if matches!(x, Some(1)) => .., /// Some(x) if x == Some(2) => .., /// _ => todo!(), /// } /// ``` /// Use instead: /// ```rust,ignore /// match x { /// Some(Some(1)) => .., /// Some(Some(2)) => .., /// _ => todo!(), /// } /// ``` #[clippy::version = "1.73.0"] pub REDUNDANT_GUARDS, complexity, "checks for unnecessary guards in match expressions" } declare_clippy_lint! { /// ### What it does /// Checks for manual implementation of `.ok()` or `.err()` /// on `Result` values. /// /// ### Why is this bad? /// Using `.ok()` or `.err()` rather than a `match` or /// `if let` is less complex and more readable. /// /// ### Example /// ```no_run /// # fn func() -> Result { Ok(0) } /// let a = match func() { /// Ok(v) => Some(v), /// Err(_) => None, /// }; /// let b = if let Err(v) = func() { /// Some(v) /// } else { /// None /// }; /// ``` /// Use instead: /// ```no_run /// # fn func() -> Result { Ok(0) } /// let a = func().ok(); /// let b = func().err(); /// ``` #[clippy::version = "1.86.0"] pub MANUAL_OK_ERR, complexity, "find manual implementations of `.ok()` or `.err()` on `Result`" } pub struct Matches { msrv: Msrv, infallible_destructuring_match_linted: bool, } impl Matches { pub fn new(conf: &'static Conf) -> Self { Self { msrv: conf.msrv.clone(), infallible_destructuring_match_linted: false, } } } impl_lint_pass!(Matches => [ SINGLE_MATCH, MATCH_REF_PATS, MATCH_BOOL, SINGLE_MATCH_ELSE, MATCH_OVERLAPPING_ARM, MATCH_WILD_ERR_ARM, MATCH_AS_REF, WILDCARD_ENUM_MATCH_ARM, MATCH_WILDCARD_FOR_SINGLE_VARIANTS, WILDCARD_IN_OR_PATTERNS, MATCH_SINGLE_BINDING, INFALLIBLE_DESTRUCTURING_MATCH, REST_PAT_IN_FULLY_BOUND_STRUCTS, REDUNDANT_PATTERN_MATCHING, MATCH_LIKE_MATCHES_MACRO, MATCH_SAME_ARMS, NEEDLESS_MATCH, COLLAPSIBLE_MATCH, MANUAL_UNWRAP_OR, MATCH_ON_VEC_ITEMS, MATCH_STR_CASE_MISMATCH, SIGNIFICANT_DROP_IN_SCRUTINEE, TRY_ERR, MANUAL_MAP, MANUAL_FILTER, REDUNDANT_GUARDS, MANUAL_OK_ERR, ]); impl<'tcx> LateLintPass<'tcx> for Matches { #[expect(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if is_direct_expn_of(expr.span, "matches").is_none() && expr.span.in_external_macro(cx.sess().source_map()) { return; } let from_expansion = expr.span.from_expansion(); if let ExprKind::Match(ex, arms, source) = expr.kind { if is_direct_expn_of(expr.span, "matches").is_some() && let [arm, _] = arms { redundant_pattern_match::check_match(cx, expr, ex, arms); redundant_pattern_match::check_matches_true(cx, expr, arm, ex); } if source == MatchSource::Normal && !is_span_match(cx, expr.span) { return; } if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) { significant_drop_in_scrutinee::check_match(cx, expr, ex, arms, source); } collapsible_match::check_match(cx, arms, &self.msrv); if !from_expansion { // These don't depend on a relationship between multiple arms match_wild_err_arm::check(cx, ex, arms); wild_in_or_pats::check(cx, ex, arms); } if let MatchSource::TryDesugar(_) = source { try_err::check(cx, expr, ex); } if !from_expansion && !contains_cfg_arm(cx, expr, ex, arms) { if source == MatchSource::Normal { if !(self.msrv.meets(msrvs::MATCHES_MACRO) && match_like_matches::check_match(cx, expr, ex, arms)) { match_same_arms::check(cx, arms); } redundant_pattern_match::check_match(cx, expr, ex, arms); let source_map = cx.tcx.sess.source_map(); let mut match_comments = span_extract_comments(source_map, expr.span); // We remove comments from inside arms block. if !match_comments.is_empty() { for arm in arms { for comment in span_extract_comments(source_map, arm.body.span) { if let Some(index) = match_comments .iter() .enumerate() .find(|(_, cm)| **cm == comment) .map(|(index, _)| index) { match_comments.remove(index); } } } } // If there are still comments, it means they are outside of the arms, therefore // we should not lint. if match_comments.is_empty() { single_match::check(cx, ex, arms, expr); } match_bool::check(cx, ex, arms, expr); overlapping_arms::check(cx, ex, arms); match_wild_enum::check(cx, ex, arms); match_as_ref::check(cx, ex, arms, expr); needless_match::check_match(cx, ex, arms, expr); match_on_vec_items::check(cx, ex); match_str_case_mismatch::check(cx, ex, arms); redundant_guards::check(cx, arms, &self.msrv); if !is_in_const_context(cx) { manual_unwrap_or::check_match(cx, expr, ex, arms); manual_map::check_match(cx, expr, ex, arms); manual_filter::check_match(cx, ex, arms, expr); manual_ok_err::check_match(cx, expr, ex, arms); } if self.infallible_destructuring_match_linted { self.infallible_destructuring_match_linted = false; } else { match_single_binding::check(cx, ex, arms, expr); } } match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr); } } else if let Some(if_let) = higher::IfLet::hir(cx, expr) { collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else, &self.msrv); significant_drop_in_scrutinee::check_if_let(cx, expr, if_let.let_expr, if_let.if_then, if_let.if_else); if !from_expansion { if let Some(else_expr) = if_let.if_else { if self.msrv.meets(msrvs::MATCHES_MACRO) { match_like_matches::check_if_let( cx, expr, if_let.let_pat, if_let.let_expr, if_let.if_then, else_expr, ); } if !is_in_const_context(cx) { manual_unwrap_or::check_if_let( cx, expr, if_let.let_pat, if_let.let_expr, if_let.if_then, else_expr, ); manual_map::check_if_let(cx, expr, if_let.let_pat, if_let.let_expr, if_let.if_then, else_expr); manual_filter::check_if_let( cx, expr, if_let.let_pat, if_let.let_expr, if_let.if_then, else_expr, ); manual_ok_err::check_if_let( cx, expr, if_let.let_pat, if_let.let_expr, if_let.if_then, else_expr, ); } } redundant_pattern_match::check_if_let( cx, expr, if_let.let_pat, if_let.let_expr, if_let.if_else.is_some(), if_let.let_span, ); needless_match::check_if_let(cx, expr, &if_let); } } else { if let Some(while_let) = higher::WhileLet::hir(expr) { significant_drop_in_scrutinee::check_while_let(cx, expr, while_let.let_expr, while_let.if_then); } if !from_expansion { redundant_pattern_match::check(cx, expr); } } } fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) { self.infallible_destructuring_match_linted |= local.els.is_none() && infallible_destructuring_match::check(cx, local); } fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { rest_pat_in_fully_bound_struct::check(cx, pat); } extract_msrv_attr!(LateContext); } /// Checks if there are any arms with a `#[cfg(..)]` attribute. fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>]) -> bool { let Some(scrutinee_span) = walk_span_to_context(scrutinee.span, SyntaxContext::root()) else { // Shouldn't happen, but treat this as though a `cfg` attribute were found return true; }; let start = scrutinee_span.hi(); let mut arm_spans = arms.iter().map(|arm| { let data = arm.span.data(); (data.ctxt == SyntaxContext::root()).then_some((data.lo, data.hi)) }); let end = e.span.hi(); // Walk through all the non-code space before each match arm. The space trailing the final arm is // handled after the `try_fold` e.g. // // match foo { // _________^- everything between the scrutinee and arm1 //| arm1 => (), //|---^___________^ everything before arm2 //| #[cfg(feature = "enabled")] //| arm2 => some_code(), //|---^____________________^ everything before arm3 //| // some comment about arm3 //| arm3 => some_code(), //|---^____________________^ everything after arm3 //| #[cfg(feature = "disabled")] //| arm4 = some_code(), //|}; //|^ let found = arm_spans.try_fold(start, |start, range| { let Some((end, next_start)) = range else { // Shouldn't happen as macros can't expand to match arms, but treat this as though a `cfg` attribute // were found. return Err(()); }; let span = SpanData { lo: start, hi: end, ctxt: SyntaxContext::root(), parent: None, } .span(); (!span_contains_cfg(cx, span)).then_some(next_start).ok_or(()) }); match found { Ok(start) => { let span = SpanData { lo: start, hi: end, ctxt: SyntaxContext::root(), parent: None, } .span(); span_contains_cfg(cx, span) }, Err(()) => true, } } /// Checks if `pat` contains OR patterns that cannot be nested due to a too low MSRV. fn pat_contains_disallowed_or(pat: &Pat<'_>, msrv: &Msrv) -> bool { if msrv.meets(msrvs::OR_PATTERNS) { return false; } let mut result = false; pat.walk(|p| { let is_or = matches!(p.kind, PatKind::Or(_)); result |= is_or; !is_or }); result }