2022-05-05 15:12:52 +01:00
|
|
|
|
use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context};
|
2022-02-10 18:40:06 +01:00
|
|
|
|
use clippy_utils::{meets_msrv, msrvs};
|
2022-02-26 14:26:21 +01:00
|
|
|
|
use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
|
|
|
|
|
use rustc_lexer::{tokenize, TokenKind};
|
2022-02-10 18:40:06 +01:00
|
|
|
|
use rustc_lint::{LateContext, LateLintPass};
|
|
|
|
|
use rustc_semver::RustcVersion;
|
|
|
|
|
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
2022-02-26 14:26:21 +01:00
|
|
|
|
use rustc_span::{Span, SpanData, SyntaxContext};
|
2022-02-10 18:40:06 +01:00
|
|
|
|
|
2022-05-05 15:12:52 +01:00
|
|
|
|
mod infallible_destructuring_match;
|
2022-02-10 18:40:06 +01:00
|
|
|
|
mod match_as_ref;
|
|
|
|
|
mod match_bool;
|
|
|
|
|
mod match_like_matches;
|
|
|
|
|
mod match_ref_pats;
|
|
|
|
|
mod match_same_arms;
|
|
|
|
|
mod match_single_binding;
|
|
|
|
|
mod match_wild_enum;
|
|
|
|
|
mod match_wild_err_arm;
|
2022-03-14 12:02:53 +01:00
|
|
|
|
mod needless_match;
|
2022-02-10 18:40:06 +01:00
|
|
|
|
mod overlapping_arms;
|
|
|
|
|
mod redundant_pattern_match;
|
|
|
|
|
mod rest_pat_in_fully_bound_struct;
|
|
|
|
|
mod single_match;
|
|
|
|
|
mod wild_in_or_pats;
|
|
|
|
|
|
|
|
|
|
declare_clippy_lint! {
|
|
|
|
|
/// ### What it does
|
|
|
|
|
/// Checks for matches with a single arm where an `if let`
|
|
|
|
|
/// will usually suffice.
|
|
|
|
|
///
|
|
|
|
|
/// ### Why is this bad?
|
|
|
|
|
/// Just readability – `if let` nests less than a `match`.
|
|
|
|
|
///
|
|
|
|
|
/// ### Example
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # fn bar(stool: &str) {}
|
|
|
|
|
/// # let x = Some("abc");
|
|
|
|
|
/// // Bad
|
|
|
|
|
/// match x {
|
|
|
|
|
/// Some(ref foo) => bar(foo),
|
|
|
|
|
/// _ => (),
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// // Good
|
|
|
|
|
/// 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`:
|
|
|
|
|
///
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # 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`:
|
|
|
|
|
///
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # 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
|
|
|
|
|
/// // Bad
|
|
|
|
|
/// match x {
|
|
|
|
|
/// &A(ref y) => foo(y),
|
|
|
|
|
/// &B => bar(),
|
|
|
|
|
/// _ => frob(&x),
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// // Good
|
|
|
|
|
/// 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # fn foo() {}
|
|
|
|
|
/// # fn bar() {}
|
|
|
|
|
/// let condition: bool = true;
|
|
|
|
|
/// match condition {
|
|
|
|
|
/// true => foo(),
|
|
|
|
|
/// false => bar(),
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
/// Use if/else instead:
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// let x: Option<()> = None;
|
|
|
|
|
///
|
|
|
|
|
/// // Bad
|
|
|
|
|
/// let r: Option<&()> = match x {
|
|
|
|
|
/// None => None,
|
|
|
|
|
/// Some(ref v) => Some(v),
|
|
|
|
|
/// };
|
|
|
|
|
///
|
|
|
|
|
/// // Good
|
|
|
|
|
/// 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # enum Foo { A(usize), B(usize) }
|
|
|
|
|
/// # let x = Foo::B(1);
|
|
|
|
|
/// // Bad
|
|
|
|
|
/// match x {
|
|
|
|
|
/// Foo::A(_) => {},
|
|
|
|
|
/// _ => {},
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// // Good
|
|
|
|
|
/// 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # enum Foo { A, B, C }
|
|
|
|
|
/// # let x = Foo::B;
|
|
|
|
|
/// // Bad
|
|
|
|
|
/// match x {
|
|
|
|
|
/// Foo::A => {},
|
|
|
|
|
/// Foo::B => {},
|
|
|
|
|
/// _ => {},
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// // Good
|
|
|
|
|
/// 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// // Bad
|
|
|
|
|
/// match "foo" {
|
|
|
|
|
/// "a" => {},
|
|
|
|
|
/// "bar" | _ => {},
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// // Good
|
|
|
|
|
/// match "foo" {
|
|
|
|
|
/// "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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// enum Wrapper {
|
|
|
|
|
/// Data(i32),
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// let wrapper = Wrapper::Data(42);
|
|
|
|
|
///
|
|
|
|
|
/// let data = match wrapper {
|
|
|
|
|
/// Wrapper::Data(i) => i,
|
|
|
|
|
/// };
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// The correct use would be:
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # let a = 1;
|
|
|
|
|
/// # let b = 2;
|
|
|
|
|
///
|
|
|
|
|
/// // Bad
|
|
|
|
|
/// match (a, b) {
|
|
|
|
|
/// (c, d) => {
|
|
|
|
|
/// // useless match
|
|
|
|
|
/// }
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// // Good
|
|
|
|
|
/// 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # struct A { a: i32 }
|
|
|
|
|
/// let a = A { a: 5 };
|
|
|
|
|
///
|
|
|
|
|
/// // Bad
|
|
|
|
|
/// match a {
|
|
|
|
|
/// A { a: 5, .. } => {},
|
|
|
|
|
/// _ => {},
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// // Good
|
|
|
|
|
/// 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # 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:
|
|
|
|
|
///
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// # 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
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// let x = Some(5);
|
|
|
|
|
///
|
|
|
|
|
/// // Bad
|
|
|
|
|
/// let a = match x {
|
|
|
|
|
/// Some(0) => true,
|
|
|
|
|
/// _ => false,
|
|
|
|
|
/// };
|
|
|
|
|
///
|
|
|
|
|
/// let a = if let Some(0) = x {
|
|
|
|
|
/// true
|
|
|
|
|
/// } else {
|
|
|
|
|
/// false
|
|
|
|
|
/// };
|
|
|
|
|
///
|
|
|
|
|
/// // Good
|
|
|
|
|
/// 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.
|
|
|
|
|
///
|
|
|
|
|
/// ### 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"
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-14 12:02:53 +01:00
|
|
|
|
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"
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-10 18:40:06 +01:00
|
|
|
|
#[derive(Default)]
|
|
|
|
|
pub struct Matches {
|
|
|
|
|
msrv: Option<RustcVersion>,
|
|
|
|
|
infallible_destructuring_match_linted: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Matches {
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn new(msrv: Option<RustcVersion>) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
msrv,
|
|
|
|
|
..Matches::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
2022-03-14 12:02:53 +01:00
|
|
|
|
NEEDLESS_MATCH,
|
2022-02-10 18:40:06 +01:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
impl<'tcx> LateLintPass<'tcx> for Matches {
|
|
|
|
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
|
|
|
|
if expr.span.from_expansion() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-26 14:26:21 +01:00
|
|
|
|
if let ExprKind::Match(ex, arms, source) = expr.kind {
|
2022-05-05 15:12:52 +01:00
|
|
|
|
if !span_starts_with(cx, expr.span, "match") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-02-26 14:26:21 +01:00
|
|
|
|
if !contains_cfg_arm(cx, expr, ex, arms) {
|
|
|
|
|
if source == MatchSource::Normal {
|
2022-05-21 13:24:00 +02:00
|
|
|
|
if !(meets_msrv(self.msrv, msrvs::MATCHES_MACRO)
|
2022-02-26 14:26:21 +01:00
|
|
|
|
&& match_like_matches::check_match(cx, expr, ex, arms))
|
|
|
|
|
{
|
|
|
|
|
match_same_arms::check(cx, arms);
|
|
|
|
|
}
|
2022-02-10 18:40:06 +01:00
|
|
|
|
|
2022-02-26 14:26:21 +01:00
|
|
|
|
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);
|
2022-04-07 18:39:59 +01:00
|
|
|
|
needless_match::check_match(cx, ex, arms, expr);
|
2022-02-26 14:26:21 +01:00
|
|
|
|
|
|
|
|
|
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);
|
2022-02-10 18:40:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-26 14:26:21 +01:00
|
|
|
|
// These don't depend on a relationship between multiple arms
|
2022-02-10 18:40:06 +01:00
|
|
|
|
match_wild_err_arm::check(cx, ex, arms);
|
|
|
|
|
wild_in_or_pats::check(cx, arms);
|
2022-02-26 14:26:21 +01:00
|
|
|
|
} else {
|
2022-05-21 13:24:00 +02:00
|
|
|
|
if meets_msrv(self.msrv, msrvs::MATCHES_MACRO) {
|
2022-02-26 14:26:21 +01:00
|
|
|
|
match_like_matches::check(cx, expr);
|
2022-02-10 18:40:06 +01:00
|
|
|
|
}
|
2022-02-26 14:26:21 +01:00
|
|
|
|
redundant_pattern_match::check(cx, expr);
|
2022-03-14 12:02:53 +01:00
|
|
|
|
needless_match::check(cx, expr);
|
2022-02-10 18:40:06 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
|
2022-05-05 15:12:52 +01:00
|
|
|
|
self.infallible_destructuring_match_linted |= infallible_destructuring_match::check(cx, local);
|
2022-02-10 18:40:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
|
|
|
|
|
rest_pat_in_fully_bound_struct::check(cx, pat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extract_msrv_attr!(LateContext);
|
|
|
|
|
}
|
2022-02-26 14:26:21 +01:00
|
|
|
|
|
|
|
|
|
/// 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(|| (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(|| 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 pos = 0usize;
|
|
|
|
|
let mut iter = tokenize(&snip).map(|t| {
|
|
|
|
|
let start = pos;
|
|
|
|
|
pos += t.len;
|
|
|
|
|
(t.kind, start..pos)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 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, range)) if &snip[range.clone()] == "cfg")
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
}
|