Auto merge of #8471 - J-ZhengLi:master-issue7040, r=llogiq

new lint that detects useless match expression

fixes #7040

changelog: Add new  lint [`needless_match`] under complexity lint group
This commit is contained in:
bors 2022-03-13 14:49:56 +00:00
commit 75b616e92f
11 changed files with 577 additions and 0 deletions

View File

@ -3331,6 +3331,7 @@ Released 2018-09-13
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark

View File

@ -138,6 +138,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(matches::MATCH_OVERLAPPING_ARM),
LintId::of(matches::MATCH_REF_PATS),
LintId::of(matches::MATCH_SINGLE_BINDING),
LintId::of(matches::NEEDLESS_MATCH),
LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
LintId::of(matches::SINGLE_MATCH),
LintId::of(matches::WILDCARD_IN_OR_PATTERNS),

View File

@ -30,6 +30,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
LintId::of(matches::MATCH_AS_REF),
LintId::of(matches::MATCH_SINGLE_BINDING),
LintId::of(matches::NEEDLESS_MATCH),
LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
LintId::of(methods::BIND_INSTEAD_OF_MAP),
LintId::of(methods::CLONE_ON_COPY),

View File

@ -260,6 +260,7 @@ store.register_lints(&[
matches::MATCH_SINGLE_BINDING,
matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
matches::MATCH_WILD_ERR_ARM,
matches::NEEDLESS_MATCH,
matches::REDUNDANT_PATTERN_MATCHING,
matches::REST_PAT_IN_FULLY_BOUND_STRUCTS,
matches::SINGLE_MATCH,

View File

@ -16,6 +16,7 @@ mod match_same_arms;
mod match_single_binding;
mod match_wild_enum;
mod match_wild_err_arm;
mod needless_match;
mod overlapping_arms;
mod redundant_pattern_match;
mod rest_pat_in_fully_bound_struct;
@ -566,6 +567,49 @@ declare_clippy_lint! {
"`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"
}
#[derive(Default)]
pub struct Matches {
msrv: Option<RustcVersion>,
@ -599,6 +643,7 @@ impl_lint_pass!(Matches => [
REDUNDANT_PATTERN_MATCHING,
MATCH_LIKE_MATCHES_MACRO,
MATCH_SAME_ARMS,
NEEDLESS_MATCH,
]);
impl<'tcx> LateLintPass<'tcx> for Matches {
@ -622,6 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
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);
if self.infallible_destructuring_match_linted {
self.infallible_destructuring_match_linted = false;
@ -640,6 +686,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
match_like_matches::check(cx, expr);
}
redundant_pattern_match::check(cx, expr);
needless_match::check(cx, expr);
}
}

View File

@ -0,0 +1,197 @@
use super::NEEDLESS_MATCH;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{eq_expr_value, get_parent_expr, higher, is_else_clause, is_lang_ctor, peel_blocks_with_stmt};
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath, UnOp};
use rustc_lint::LateContext;
use rustc_span::sym;
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
// This is for avoiding collision with `match_single_binding`.
if arms.len() < 2 {
return;
}
for arm in arms {
if let PatKind::Wild = arm.pat.kind {
let ret_expr = strip_return(arm.body);
if !eq_expr_value(cx, ex, ret_expr) {
return;
}
} else if !pat_same_as_expr(arm.pat, arm.body) {
return;
}
}
if let Some(match_expr) = get_parent_expr(cx, ex) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_MATCH,
match_expr.span,
"this match expression is unnecessary",
"replace it with",
snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
applicability,
);
}
}
/// Check for nop `if let` expression that assembled as unnecessary match
///
/// ```rust,ignore
/// if let Some(a) = option {
/// Some(a)
/// } else {
/// None
/// }
/// ```
/// OR
/// ```rust,ignore
/// if let SomeEnum::A = some_enum {
/// SomeEnum::A
/// } else if let SomeEnum::B = some_enum {
/// SomeEnum::B
/// } else {
/// some_enum
/// }
/// ```
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
if_chain! {
if let Some(ref if_let) = higher::IfLet::hir(cx, ex);
if !is_else_clause(cx.tcx, ex);
if check_if_let(cx, if_let);
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_MATCH,
ex.span,
"this if-let expression is unnecessary",
"replace it with",
snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
applicability,
);
}
}
}
fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
if let Some(if_else) = if_let.if_else {
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
return false;
}
// Recurrsively check for each `else if let` phrase,
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
return check_if_let(cx, nested_if_let);
}
if matches!(if_else.kind, ExprKind::Block(..)) {
let else_expr = peel_blocks_with_stmt(if_else);
let ret = strip_return(else_expr);
let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
if let ExprKind::Path(ref qpath) = ret.kind {
return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
}
} else {
return eq_expr_value(cx, if_let.let_expr, ret);
}
return true;
}
}
false
}
/// Strip `return` keyword if the expression type is `ExprKind::Ret`.
fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
if let ExprKind::Ret(Some(ret)) = expr.kind {
ret
} else {
expr
}
}
fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
let expr = strip_return(expr);
match (&pat.kind, &expr.kind) {
// Example: `Some(val) => Some(val)`
(
PatKind::TupleStruct(QPath::Resolved(_, path), [first_pat, ..], _),
ExprKind::Call(call_expr, [first_param, ..]),
) => {
if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
if has_identical_segments(path.segments, call_path.segments)
&& has_same_non_ref_symbol(first_pat, first_param)
{
return true;
}
}
},
// Example: `val => val`, or `ref val => *val`
(PatKind::Binding(annot, _, pat_ident, _), _) => {
let new_expr = if let (
BindingAnnotation::Ref | BindingAnnotation::RefMut,
ExprKind::Unary(UnOp::Deref, operand_expr),
) = (annot, &expr.kind)
{
operand_expr
} else {
expr
};
if let ExprKind::Path(QPath::Resolved(
_,
Path {
segments: [first_seg, ..],
..
},
)) = new_expr.kind
{
return pat_ident.name == first_seg.ident.name;
}
},
// Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
(PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
return has_identical_segments(p_path.segments, e_path.segments);
},
// Example: `5 => 5`
(PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind {
return pat_spanned.node == expr_spanned.node;
}
},
_ => {},
}
false
}
fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
if left_segs.len() != right_segs.len() {
return false;
}
for i in 0..left_segs.len() {
if left_segs[i].ident.name != right_segs[i].ident.name {
return false;
}
}
true
}
fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
if_chain! {
if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind;
if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind;
then {
return pat_ident.name == first_seg.ident.name;
}
}
false
}

View File

@ -148,6 +148,7 @@ fn main() {
// #7077
let s = &String::new();
#[allow(clippy::needless_match)]
let _: Option<&str> = match Some(s) {
Some(s) => Some(s),
None => None,

View File

@ -214,6 +214,7 @@ fn main() {
// #7077
let s = &String::new();
#[allow(clippy::needless_match)]
let _: Option<&str> = match Some(s) {
Some(s) => Some(s),
None => None,

View File

@ -0,0 +1,83 @@
// run-rustfix
#![warn(clippy::needless_match)]
#![allow(clippy::manual_map)]
#![allow(dead_code)]
#[derive(Clone, Copy)]
enum Choice {
A,
B,
C,
D,
}
#[allow(unused_mut)]
fn useless_match() {
let mut i = 10;
let _: i32 = i;
let _: i32 = i;
let mut _i_mut = i;
let s = "test";
let _: &str = s;
}
fn custom_type_match(se: Choice) {
let _: Choice = se;
// Don't trigger
let _: Choice = match se {
Choice::A => Choice::A,
Choice::B => Choice::B,
_ => Choice::C,
};
// Mingled, don't trigger
let _: Choice = match se {
Choice::A => Choice::B,
Choice::B => Choice::C,
Choice::C => Choice::D,
Choice::D => Choice::A,
};
}
fn option_match(x: Option<i32>) {
let _: Option<i32> = x;
// Don't trigger, this is the case for manual_map_option
let _: Option<i32> = match x {
Some(a) => Some(-a),
None => None,
};
}
fn func_ret_err<T>(err: T) -> Result<i32, T> {
Err(err)
}
fn result_match() {
let _: Result<i32, i32> = Ok(1);
let _: Result<i32, i32> = func_ret_err(0_i32);
}
fn if_let_option() -> Option<i32> {
Some(1)
}
fn if_let_result(x: Result<(), i32>) {
let _: Result<(), i32> = x;
let _: Result<(), i32> = x;
// Input type mismatch, don't trigger
let _: Result<(), i32> = if let Err(e) = Ok(1) { Err(e) } else { x };
}
fn if_let_custom_enum(x: Choice) {
let _: Choice = x;
// Don't trigger
let _: Choice = if let Choice::A = x {
Choice::A
} else if true {
Choice::B
} else {
x
};
}
fn main() {}

122
tests/ui/needless_match.rs Normal file
View File

@ -0,0 +1,122 @@
// run-rustfix
#![warn(clippy::needless_match)]
#![allow(clippy::manual_map)]
#![allow(dead_code)]
#[derive(Clone, Copy)]
enum Choice {
A,
B,
C,
D,
}
#[allow(unused_mut)]
fn useless_match() {
let mut i = 10;
let _: i32 = match i {
0 => 0,
1 => 1,
2 => 2,
_ => i,
};
let _: i32 = match i {
0 => 0,
1 => 1,
ref i => *i,
};
let mut _i_mut = match i {
0 => 0,
1 => 1,
ref mut i => *i,
};
let s = "test";
let _: &str = match s {
"a" => "a",
"b" => "b",
s => s,
};
}
fn custom_type_match(se: Choice) {
let _: Choice = match se {
Choice::A => Choice::A,
Choice::B => Choice::B,
Choice::C => Choice::C,
Choice::D => Choice::D,
};
// Don't trigger
let _: Choice = match se {
Choice::A => Choice::A,
Choice::B => Choice::B,
_ => Choice::C,
};
// Mingled, don't trigger
let _: Choice = match se {
Choice::A => Choice::B,
Choice::B => Choice::C,
Choice::C => Choice::D,
Choice::D => Choice::A,
};
}
fn option_match(x: Option<i32>) {
let _: Option<i32> = match x {
Some(a) => Some(a),
None => None,
};
// Don't trigger, this is the case for manual_map_option
let _: Option<i32> = match x {
Some(a) => Some(-a),
None => None,
};
}
fn func_ret_err<T>(err: T) -> Result<i32, T> {
Err(err)
}
fn result_match() {
let _: Result<i32, i32> = match Ok(1) {
Ok(a) => Ok(a),
Err(err) => Err(err),
};
let _: Result<i32, i32> = match func_ret_err(0_i32) {
Err(err) => Err(err),
Ok(a) => Ok(a),
};
}
fn if_let_option() -> Option<i32> {
if let Some(a) = Some(1) { Some(a) } else { None }
}
fn if_let_result(x: Result<(), i32>) {
let _: Result<(), i32> = if let Err(e) = x { Err(e) } else { x };
let _: Result<(), i32> = if let Ok(val) = x { Ok(val) } else { x };
// Input type mismatch, don't trigger
let _: Result<(), i32> = if let Err(e) = Ok(1) { Err(e) } else { x };
}
fn if_let_custom_enum(x: Choice) {
let _: Choice = if let Choice::A = x {
Choice::A
} else if let Choice::B = x {
Choice::B
} else if let Choice::C = x {
Choice::C
} else {
x
};
// Don't trigger
let _: Choice = if let Choice::A = x {
Choice::A
} else if true {
Choice::B
} else {
x
};
}
fn main() {}

View File

@ -0,0 +1,122 @@
error: this match expression is unnecessary
--> $DIR/needless_match.rs:17:18
|
LL | let _: i32 = match i {
| __________________^
LL | | 0 => 0,
LL | | 1 => 1,
LL | | 2 => 2,
LL | | _ => i,
LL | | };
| |_____^ help: replace it with: `i`
|
= note: `-D clippy::needless-match` implied by `-D warnings`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:23:18
|
LL | let _: i32 = match i {
| __________________^
LL | | 0 => 0,
LL | | 1 => 1,
LL | | ref i => *i,
LL | | };
| |_____^ help: replace it with: `i`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:28:22
|
LL | let mut _i_mut = match i {
| ______________________^
LL | | 0 => 0,
LL | | 1 => 1,
LL | | ref mut i => *i,
LL | | };
| |_____^ help: replace it with: `i`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:35:19
|
LL | let _: &str = match s {
| ___________________^
LL | | "a" => "a",
LL | | "b" => "b",
LL | | s => s,
LL | | };
| |_____^ help: replace it with: `s`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:43:21
|
LL | let _: Choice = match se {
| _____________________^
LL | | Choice::A => Choice::A,
LL | | Choice::B => Choice::B,
LL | | Choice::C => Choice::C,
LL | | Choice::D => Choice::D,
LL | | };
| |_____^ help: replace it with: `se`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:65:26
|
LL | let _: Option<i32> = match x {
| __________________________^
LL | | Some(a) => Some(a),
LL | | None => None,
LL | | };
| |_____^ help: replace it with: `x`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:81:31
|
LL | let _: Result<i32, i32> = match Ok(1) {
| _______________________________^
LL | | Ok(a) => Ok(a),
LL | | Err(err) => Err(err),
LL | | };
| |_____^ help: replace it with: `Ok(1)`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:85:31
|
LL | let _: Result<i32, i32> = match func_ret_err(0_i32) {
| _______________________________^
LL | | Err(err) => Err(err),
LL | | Ok(a) => Ok(a),
LL | | };
| |_____^ help: replace it with: `func_ret_err(0_i32)`
error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:92:5
|
LL | if let Some(a) = Some(1) { Some(a) } else { None }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `Some(1)`
error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:96:30
|
LL | let _: Result<(), i32> = if let Err(e) = x { Err(e) } else { x };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x`
error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:97:30
|
LL | let _: Result<(), i32> = if let Ok(val) = x { Ok(val) } else { x };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x`
error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:103:21
|
LL | let _: Choice = if let Choice::A = x {
| _____________________^
LL | | Choice::A
LL | | } else if let Choice::B = x {
LL | | Choice::B
... |
LL | | x
LL | | };
| |_____^ help: replace it with: `x`
error: aborting due to 12 previous errors