Skip to content

Commit

Permalink
add basic code to check nop match blocks
Browse files Browse the repository at this point in the history
modify `manual_map_option` uitest because one test case has confliction.
  • Loading branch information
J-ZhengLi committed Mar 7, 2022
1 parent 30fb822 commit db3fcf8
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 73 deletions.
10 changes: 5 additions & 5 deletions clippy_lints/src/matches/mod.rs
Expand Up @@ -16,12 +16,12 @@ mod match_same_arms;
mod match_single_binding;
mod match_wild_enum;
mod match_wild_err_arm;
mod nop_match;
mod overlapping_arms;
mod redundant_pattern_match;
mod rest_pat_in_fully_bound_struct;
mod single_match;
mod wild_in_or_pats;
mod nop_match;

declare_clippy_lint! {
/// ### What it does
Expand Down Expand Up @@ -569,7 +569,7 @@ declare_clippy_lint! {

declare_clippy_lint! {
/// ### What it does
/// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result`
/// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result`
/// when function signatures are the same.
///
/// ### Why is this bad?
Expand All @@ -583,7 +583,7 @@ declare_clippy_lint! {
/// Err(err) => Err(err),
/// }
/// }
///
///
/// fn bar() -> Option<i32> {
/// if let Some(val) = option {
/// Some(val)
Expand All @@ -594,12 +594,12 @@ declare_clippy_lint! {
/// ```
///
/// Could be replaced as
///
///
/// ```rust,ignore
/// fn foo() -> Result<(), i32> {
/// result
/// }
///
///
/// fn bar() -> Option<i32> {
/// option
/// }
Expand Down
98 changes: 90 additions & 8 deletions clippy_lints/src/matches/nop_match.rs
@@ -1,9 +1,11 @@
#![allow(unused_variables)]
use super::NOP_MATCH;
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_lint::LateContext;
use rustc_hir::{Arm, Expr};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{eq_expr_value, get_parent_expr};
use rustc_errors::Applicability;
use super::NOP_MATCH;
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, PathSegment, QPath};
use rustc_lint::LateContext;

pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
if false {
Expand All @@ -20,15 +22,95 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
}

pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
if false {
// 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,
NOP_MATCH,
ex.span,
match_expr.span,
"this match expression is unnecessary",
"replace it with",
"".to_string(),
Applicability::MachineApplicable,
snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
applicability,
);
}
}
}

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) {
(
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 is_identical_segments(path.segments, call_path.segments)
&& has_same_non_ref_symbol(first_pat, first_param)
{
return true;
}
}
},
(PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
return is_identical_segments(p_path.segments, e_path.segments);
},
(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 is_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)) = expr.kind;
if let Some(first_seg) = path.segments.first();
then {
return pat_ident.name == first_seg.ident.name;
}
}

false
}
1 change: 1 addition & 0 deletions tests/ui/manual_map_option.fixed
Expand Up @@ -148,6 +148,7 @@ fn main() {

// #7077
let s = &String::new();
#[allow(clippy::nop_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
Expand Up @@ -214,6 +214,7 @@ fn main() {

// #7077
let s = &String::new();
#[allow(clippy::nop_match)]
let _: Option<&str> = match Some(s) {
Some(s) => Some(s),
None => None,
Expand Down
71 changes: 38 additions & 33 deletions tests/ui/nop_match.fixed
Expand Up @@ -4,26 +4,47 @@
#![allow(clippy::question_mark)]
#![allow(dead_code)]

fn option_match() -> Option<i32> {
match Some(1) {
Some(a) => Some(a),
None => None
fn func_ret_err<T>(err: T) -> Result<(), T> {
Err(err)
}

enum SampleEnum {
A,
B,
C,
}

fn useless_prim_type_match(x: i32) -> i32 {
x
}

fn useless_custom_type_match(se: SampleEnum) -> SampleEnum {
se
}

// Don't trigger
fn mingled_custom_type(se: SampleEnum) -> SampleEnum {
match se {
SampleEnum::A => SampleEnum::B,
SampleEnum::B => SampleEnum::C,
SampleEnum::C => SampleEnum::A,
}
}

fn option_match() -> Option<i32> {
Some(1)
}

fn result_match() -> Result<i32, i32> {
match Ok(1) {
Ok(a) => Ok(a),
Err(err) => Err(err)
}
Ok(1)
}

fn result_match_func_call() {
let _ = func_ret_err(0_i32);
}

fn option_check() -> Option<i32> {
if let Some(a) = Some(1) {
Some(a)
} else {
None
}
if let Some(a) = Some(1) { Some(a) } else { None }
}

fn option_check_no_else() -> Option<i32> {
Expand All @@ -33,10 +54,6 @@ fn option_check_no_else() -> Option<i32> {
None
}

fn func_ret_err<T>(err: T) -> Result<(), T> {
Err(err)
}

fn result_check_no_else() -> Result<(), i32> {
if let Err(e) = func_ret_err(0_i32) {
return Err(e);
Expand All @@ -54,30 +71,18 @@ fn result_check_a() -> Result<(), i32> {

// Don't trigger
fn result_check_b() -> Result<(), i32> {
if let Err(e) = Ok(1) {
Err(e)
} else {
Ok(())
}
if let Err(e) = Ok(1) { Err(e) } else { Ok(()) }
}

fn result_check_c() -> Result<(), i32> {
let example = Ok(());
if let Err(e) = example {
Err(e)
} else {
example
}
if let Err(e) = example { Err(e) } else { example }
}

// Don't trigger
fn result_check_d() -> Result<(), i32> {
let example = Ok(1);
if let Err(e) = example {
Err(e)
} else {
Ok(())
}
if let Err(e) = example { Err(e) } else { Ok(()) }
}

fn main() { }
fn main() {}

0 comments on commit db3fcf8

Please sign in to comment.