Skip to content

Commit

Permalink
Auto merge of #8471 - J-ZhengLi:master-issue7040, r=llogiq
Browse files Browse the repository at this point in the history
new lint that detects useless match expression

fixes #7040

changelog: Add new  lint [`needless_match`] under complexity lint group
  • Loading branch information
bors committed Mar 13, 2022
2 parents 8ae74da + 086b045 commit 75b616e
Show file tree
Hide file tree
Showing 11 changed files with 577 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_complexity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
47 changes: 47 additions & 0 deletions clippy_lints/src/matches/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -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);
}
}

Expand Down
197 changes: 197 additions & 0 deletions clippy_lints/src/matches/needless_match.rs
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions tests/ui/manual_map_option.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions tests/ui/manual_map_option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 75b616e

Please sign in to comment.