fix #8551, add test cases, and some code improvement

This commit is contained in:
J-ZhengLi 2022-03-17 23:06:31 +08:00
parent 2909b33a24
commit 4b128624ed
5 changed files with 185 additions and 71 deletions

View file

@ -667,7 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
overlapping_arms::check(cx, ex, arms); overlapping_arms::check(cx, ex, arms);
match_wild_enum::check(cx, ex, arms); match_wild_enum::check(cx, ex, arms);
match_as_ref::check(cx, ex, arms, expr); match_as_ref::check(cx, ex, arms, expr);
needless_match::check_match(cx, ex, arms); needless_match::check_match(cx, ex, arms, expr);
if self.infallible_destructuring_match_linted { if self.infallible_destructuring_match_linted {
self.infallible_destructuring_match_linted = false; self.infallible_destructuring_match_linted = false;

View file

@ -1,37 +1,25 @@
use super::NEEDLESS_MATCH; use super::NEEDLESS_MATCH;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
use clippy_utils::{eq_expr_value, get_parent_expr, higher, is_else_clause, is_lang_ctor, peel_blocks_with_stmt}; use clippy_utils::{
eq_expr_value, get_parent_expr_for_hir, get_parent_node, higher, is_else_clause, is_lang_ctor,
peel_blocks_with_stmt,
};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone; use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath}; use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Node, Pat, PatKind, Path, PathSegment, QPath};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::sym; use rustc_span::sym;
use rustc_typeck::hir_ty_to_ty;
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
// This is for avoiding collision with `match_single_binding`. if arms.len() > 1 && !is_coercion_casting(cx, ex, expr) && check_all_arms(cx, ex, arms) {
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, peel_blocks_with_stmt(arm.body)) {
return;
}
}
if let Some(match_expr) = get_parent_expr(cx, ex) {
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
NEEDLESS_MATCH, NEEDLESS_MATCH,
match_expr.span, expr.span,
"this match expression is unnecessary", "this match expression is unnecessary",
"replace it with", "replace it with",
snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(), snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
@ -60,11 +48,8 @@ pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
/// } /// }
/// ``` /// ```
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) { pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
if_chain! { if let Some(ref if_let) = higher::IfLet::hir(cx, ex) {
if let Some(ref if_let) = higher::IfLet::hir(cx, ex); if !is_else_clause(cx.tcx, ex) && !is_coercion_casting(cx, if_let.let_expr, ex) && check_if_let(cx, if_let) {
if !is_else_clause(cx.tcx, ex);
if check_if_let(cx, if_let);
then {
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
@ -79,6 +64,19 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
} }
} }
fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
for arm in arms {
let arm_expr = peel_blocks_with_stmt(arm.body);
if let PatKind::Wild = arm.pat.kind {
return eq_expr_value(cx, match_expr, strip_return(arm_expr));
} else if !pat_same_as_expr(arm.pat, arm_expr) {
return false;
}
}
true
}
fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool { fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
if let Some(if_else) = if_let.if_else { 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)) { if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
@ -101,12 +99,12 @@ fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
if let ExprKind::Path(ref qpath) = ret.kind { if let ExprKind::Path(ref qpath) = ret.kind {
return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret); return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
} }
} else { return true;
return eq_expr_value(cx, if_let.let_expr, ret);
} }
return true; return eq_expr_value(cx, if_let.let_expr, ret);
} }
} }
false false
} }
@ -119,14 +117,52 @@ fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
} }
} }
/// Manually check for coercion casting by checking if the type of the match operand or let expr
/// differs with the assigned local variable or the funtion return type.
fn is_coercion_casting(cx: &LateContext<'_>, match_expr: &Expr<'_>, expr: &Expr<'_>) -> bool {
if let Some(p_node) = get_parent_node(cx.tcx, expr.hir_id) {
match p_node {
// Compare match_expr ty with local in `let local = match match_expr {..}`
Node::Local(local) => {
let results = cx.typeck_results();
return !same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(match_expr));
},
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
Node::Item(..) => {
if let Some(fn_decl) = p_node.fn_decl() {
if let FnRetTy::Return(ret_ty) = fn_decl.output {
return !same_type_and_consts(
hir_ty_to_ty(cx.tcx, ret_ty),
cx.typeck_results().expr_ty(match_expr),
);
}
}
},
// check the parent expr for this whole block `{ match match_expr {..} }`
Node::Block(block) => {
if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
return is_coercion_casting(cx, match_expr, block_parent_expr);
}
},
// recursively call on `if xxx {..}` etc.
Node::Expr(p_expr) => {
return is_coercion_casting(cx, match_expr, p_expr);
},
_ => {},
}
}
false
}
fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool { fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
let expr = strip_return(expr); let expr = strip_return(expr);
match (&pat.kind, &expr.kind) { match (&pat.kind, &expr.kind) {
// Example: `Some(val) => Some(val)` // Example: `Some(val) => Some(val)`
(PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => { (PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => {
if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind { if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
return has_identical_segments(path.segments, call_path.segments) return same_segments(path.segments, call_path.segments)
&& has_same_non_ref_symbols(tuple_params, call_params); && same_non_ref_symbols(tuple_params, call_params);
} }
}, },
// Example: `val => val` // Example: `val => val`
@ -145,7 +181,7 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
}, },
// Example: `Custom::TypeA => Custom::TypeB`, or `None => None` // Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
(PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => { (PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
return has_identical_segments(p_path.segments, e_path.segments); return same_segments(p_path.segments, e_path.segments);
}, },
// Example: `5 => 5` // Example: `5 => 5`
(PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => { (PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
@ -159,19 +195,21 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
false false
} }
fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool { fn same_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
if left_segs.len() != right_segs.len() { if left_segs.len() != right_segs.len() {
return false; return false;
} }
for i in 0..left_segs.len() { for i in 0..left_segs.len() {
if left_segs[i].ident.name != right_segs[i].ident.name { if left_segs[i].ident.name != right_segs[i].ident.name {
return false; return false;
} }
} }
true true
} }
fn has_same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool { fn same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool {
if pats.len() != exprs.len() { if pats.len() != exprs.len() {
return false; return false;
} }

View file

@ -60,8 +60,26 @@ fn result_match() {
}; };
} }
fn if_let_option() -> Option<i32> { fn if_let_option() {
Some(1) let _ = Some(1);
fn do_something() {}
// Don't trigger
let _ = if let Some(a) = Some(1) {
Some(a)
} else {
do_something();
None
};
// Don't trigger
let _ = if let Some(a) = Some(1) {
do_something();
Some(a)
} else {
None
};
} }
fn if_let_result() { fn if_let_result() {
@ -122,25 +140,45 @@ mod issue8542 {
_ => ce, _ => ce,
}; };
} }
}
fn if_let_test() { /// Lint triggered when type coercions happen.
fn do_something() {} /// Do NOT trigger on any of these.
mod issue8551 {
trait Trait {}
struct Struct;
impl Trait for Struct {}
// Don't trigger fn optmap(s: Option<&Struct>) -> Option<&dyn Trait> {
let _ = if let Some(a) = Some(1) { match s {
Some(a) Some(s) => Some(s),
} else { None => None,
do_something(); }
None }
fn lint_tests() {
let option: Option<&Struct> = None;
let _: Option<&dyn Trait> = match option {
Some(s) => Some(s),
None => None,
}; };
// Don't trigger let _: Option<&dyn Trait> = if true {
let _ = if let Some(a) = Some(1) { match option {
do_something(); Some(s) => Some(s),
Some(a) None => None,
}
} else { } else {
None None
}; };
let result: Result<&Struct, i32> = Err(0);
let _: Result<&dyn Trait, i32> = match result {
Ok(s) => Ok(s),
Err(e) => Err(e),
};
let _: Option<&dyn Trait> = if let Some(s) = option { Some(s) } else { None };
} }
} }

View file

@ -83,8 +83,26 @@ fn result_match() {
}; };
} }
fn if_let_option() -> Option<i32> { fn if_let_option() {
if let Some(a) = Some(1) { Some(a) } else { None } let _ = if let Some(a) = Some(1) { Some(a) } else { None };
fn do_something() {}
// Don't trigger
let _ = if let Some(a) = Some(1) {
Some(a)
} else {
do_something();
None
};
// Don't trigger
let _ = if let Some(a) = Some(1) {
do_something();
Some(a)
} else {
None
};
} }
fn if_let_result() { fn if_let_result() {
@ -159,25 +177,45 @@ mod issue8542 {
_ => ce, _ => ce,
}; };
} }
}
fn if_let_test() { /// Lint triggered when type coercions happen.
fn do_something() {} /// Do NOT trigger on any of these.
mod issue8551 {
trait Trait {}
struct Struct;
impl Trait for Struct {}
// Don't trigger fn optmap(s: Option<&Struct>) -> Option<&dyn Trait> {
let _ = if let Some(a) = Some(1) { match s {
Some(a) Some(s) => Some(s),
} else { None => None,
do_something(); }
None }
fn lint_tests() {
let option: Option<&Struct> = None;
let _: Option<&dyn Trait> = match option {
Some(s) => Some(s),
None => None,
}; };
// Don't trigger let _: Option<&dyn Trait> = if true {
let _ = if let Some(a) = Some(1) { match option {
do_something(); Some(s) => Some(s),
Some(a) None => None,
}
} else { } else {
None None
}; };
let result: Result<&Struct, i32> = Err(0);
let _: Result<&dyn Trait, i32> = match result {
Ok(s) => Ok(s),
Err(e) => Err(e),
};
let _: Option<&dyn Trait> = if let Some(s) = option { Some(s) } else { None };
} }
} }

View file

@ -66,25 +66,25 @@ LL | | };
| |_____^ help: replace it with: `func_ret_err(0_i32)` | |_____^ help: replace it with: `func_ret_err(0_i32)`
error: this if-let expression is unnecessary error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:87:5 --> $DIR/needless_match.rs:87:13
| |
LL | if let Some(a) = Some(1) { Some(a) } else { None } LL | let _ = if let Some(a) = Some(1) { Some(a) } else { None };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `Some(1)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `Some(1)`
error: this if-let expression is unnecessary error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:92:31 --> $DIR/needless_match.rs:110:31
| |
LL | let _: Result<i32, i32> = if let Err(e) = x { Err(e) } else { x }; LL | let _: Result<i32, i32> = if let Err(e) = x { Err(e) } else { x };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x`
error: this if-let expression is unnecessary error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:93:31 --> $DIR/needless_match.rs:111:31
| |
LL | let _: Result<i32, i32> = if let Ok(val) = x { Ok(val) } else { x }; LL | let _: Result<i32, i32> = if let Ok(val) = x { Ok(val) } else { x };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x`
error: this if-let expression is unnecessary error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:99:21 --> $DIR/needless_match.rs:117:21
| |
LL | let _: Simple = if let Simple::A = x { LL | let _: Simple = if let Simple::A = x {
| _____________________^ | _____________________^
@ -97,7 +97,7 @@ LL | | };
| |_____^ help: replace it with: `x` | |_____^ help: replace it with: `x`
error: this match expression is unnecessary error: this match expression is unnecessary
--> $DIR/needless_match.rs:138:26 --> $DIR/needless_match.rs:156:26
| |
LL | let _: Complex = match ce { LL | let _: Complex = match ce {
| __________________________^ | __________________________^