blob: dea46d4d36073e2a1c7131490cf96a6a848c40e5 [file] [log] [blame]
mod collapsible_match;
mod infallible_destructuring_match;
mod manual_filter;
mod manual_map;
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::msrvs::{self, Msrv};
use clippy_utils::source::{snippet_opt, walk_span_to_context};
use clippy_utils::{higher, in_constant, is_direct_expn_of, is_span_match, tokenize_with_text};
use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
use rustc_lexer::TokenKind;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{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<i32, &str> = 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 is this bad?
/// 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 is this bad?
/// 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` or `std::net::IpAddr`
///
/// ### Why is this bad?
/// It's more concise and clear to just use the proper
/// utility function
///
/// ### Known problems
/// 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::<i32, i32>(42) {}
/// if let Err(_) = Err::<i32, i32>(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::<i32, i32>(42) {
/// Ok(_) => true,
/// Err(_) => false,
/// };
/// ```
///
/// The more idiomatic use would be:
///
/// ```no_run
/// # use std::task::Poll;
/// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
/// if Ok::<i32, i32>(42).is_ok() {}
/// if Err::<i32, i32>(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::<i32, i32>(42).is_ok();
/// ```
#[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).
///
/// ### Known problems
/// False positive possible with order dependent `match`
/// (see issue
/// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
///
/// ### 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<i32> {
/// if let Some(val) = option {
/// Some(val)
/// } else {
/// None
/// }
/// }
/// ```
///
/// Could be replaced as
///
/// ```rust,ignore
/// fn foo() -> Result<(), i32> {
/// result
/// }
///
/// fn bar() -> Option<i32> {
/// 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<Result<u64, String>>) {
/// let n = match opt {
/// Some(n) => match n {
/// Ok(n) => n,
/// _ => return,
/// }
/// None => return,
/// };
/// }
/// ```
/// Use instead:
/// ```no_run
/// fn func(opt: Option<Result<u64, String>>) {
/// 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<i32> = None;
/// match foo {
/// Some(v) => v,
/// None => 1,
/// };
/// ```
///
/// Use instead:
/// ```no_run
/// let foo: Option<i32> = 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 is this bad?
/// 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<i32, String> {
/// if fail {
/// Err("failed")?;
/// }
/// Ok(0)
/// }
/// ```
/// Could be written:
///
/// ```no_run
/// fn foo(fail: bool) -> Result<i32, String> {
/// 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"
}
pub struct Matches {
msrv: Msrv,
infallible_destructuring_match_linted: bool,
}
impl Matches {
#[must_use]
pub fn new(msrv: Msrv) -> Self {
Self {
msrv,
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,
]);
impl<'tcx> LateLintPass<'tcx> for Matches {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if is_direct_expn_of(expr.span, "matches").is_none() && in_external_macro(cx.sess(), expr.span) {
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() {
redundant_pattern_match::check_match(cx, expr, ex, arms);
}
if source == MatchSource::Normal && !is_span_match(cx, expr.span) {
return;
}
if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) {
significant_drop_in_scrutinee::check(cx, expr, ex, arms, source);
}
collapsible_match::check_match(cx, arms);
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, 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);
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);
if !in_constant(cx, expr.hir_id) {
manual_unwrap_or::check(cx, expr, ex, arms);
manual_map::check_match(cx, expr, ex, arms);
manual_filter::check_match(cx, ex, arms, expr);
}
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);
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 !in_constant(cx, expr.hir_id) {
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,
);
}
}
redundant_pattern_match::check_if_let(
cx,
expr,
if_let.let_pat,
if_let.let_expr,
if_let.if_else.is_some(),
);
needless_match::check_if_let(cx, expr, &if_let);
}
} else if !from_expansion {
redundant_pattern_match::check(cx, expr);
}
}
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
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 the given span contains a `#[cfg(..)]` attribute
fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
let Some(snip) = snippet_opt(cx, s) else {
// Assume true. This would require either an invalid span, or one which crosses file boundaries.
return true;
};
let mut iter = tokenize_with_text(&snip);
// Search for the token sequence [`#`, `[`, `cfg`]
while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) {
let mut iter = iter.by_ref().skip_while(|(t, _)| {
matches!(
t,
TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. }
)
});
if matches!(iter.next(), Some((TokenKind::OpenBracket, _)))
&& matches!(iter.next(), Some((TokenKind::Ident, "cfg")))
{
return true;
}
}
false
}