Skip to content

Commit 81e004a

Browse files
committed
Auto merge of #8549 - J-ZhengLi:issue8542, r=llogiq
fix FP in lint `[needless_match]` fixes: #8542 fixes: #8551 fixes: #8595 fixes: #8599 --- changelog: check for more complex custom type, and ignore type coercion in [`needless_match`]
2 parents 30019d1 + 85b081b commit 81e004a

File tree

5 files changed

+425
-196
lines changed

5 files changed

+425
-196
lines changed

clippy_lints/src/matches/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
667667
overlapping_arms::check(cx, ex, arms);
668668
match_wild_enum::check(cx, ex, arms);
669669
match_as_ref::check(cx, ex, arms, expr);
670-
needless_match::check_match(cx, ex, arms);
670+
needless_match::check_match(cx, ex, arms, expr);
671671

672672
if self.infallible_destructuring_match_linted {
673673
self.infallible_destructuring_match_linted = false;
+86-74
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,25 @@
11
use super::NEEDLESS_MATCH;
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::source::snippet_with_applicability;
4-
use clippy_utils::ty::is_type_diagnostic_item;
5-
use clippy_utils::{eq_expr_value, get_parent_expr, higher, is_else_clause, is_lang_ctor, peel_blocks_with_stmt};
4+
use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
5+
use clippy_utils::{
6+
eq_expr_value, get_parent_expr_for_hir, get_parent_node, higher, is_else_clause, is_lang_ctor, over,
7+
peel_blocks_with_stmt,
8+
};
69
use rustc_errors::Applicability;
710
use rustc_hir::LangItem::OptionNone;
8-
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath, UnOp};
11+
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Node, Pat, PatKind, Path, QPath};
912
use rustc_lint::LateContext;
1013
use rustc_span::sym;
14+
use rustc_typeck::hir_ty_to_ty;
1115

12-
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
13-
// This is for avoiding collision with `match_single_binding`.
14-
if arms.len() < 2 {
15-
return;
16-
}
17-
18-
for arm in arms {
19-
if let PatKind::Wild = arm.pat.kind {
20-
let ret_expr = strip_return(arm.body);
21-
if !eq_expr_value(cx, ex, ret_expr) {
22-
return;
23-
}
24-
} else if !pat_same_as_expr(arm.pat, arm.body) {
25-
return;
26-
}
27-
}
28-
29-
if let Some(match_expr) = get_parent_expr(cx, ex) {
16+
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
17+
if arms.len() > 1 && expr_ty_matches_p_ty(cx, ex, expr) && check_all_arms(cx, ex, arms) {
3018
let mut applicability = Applicability::MachineApplicable;
3119
span_lint_and_sugg(
3220
cx,
3321
NEEDLESS_MATCH,
34-
match_expr.span,
22+
expr.span,
3523
"this match expression is unnecessary",
3624
"replace it with",
3725
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<'_>])
6048
/// }
6149
/// ```
6250
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
63-
if_chain! {
64-
if let Some(ref if_let) = higher::IfLet::hir(cx, ex);
65-
if !is_else_clause(cx.tcx, ex);
66-
if check_if_let(cx, if_let);
67-
then {
51+
if let Some(ref if_let) = higher::IfLet::hir(cx, ex) {
52+
if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let(cx, if_let) {
6853
let mut applicability = Applicability::MachineApplicable;
6954
span_lint_and_sugg(
7055
cx,
@@ -79,6 +64,19 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
7964
}
8065
}
8166

67+
fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
68+
for arm in arms {
69+
let arm_expr = peel_blocks_with_stmt(arm.body);
70+
if let PatKind::Wild = arm.pat.kind {
71+
return eq_expr_value(cx, match_expr, strip_return(arm_expr));
72+
} else if !pat_same_as_expr(arm.pat, arm_expr) {
73+
return false;
74+
}
75+
}
76+
77+
true
78+
}
79+
8280
fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
8381
if let Some(if_else) = if_let.if_else {
8482
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
@@ -92,18 +90,21 @@ fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
9290

9391
if matches!(if_else.kind, ExprKind::Block(..)) {
9492
let else_expr = peel_blocks_with_stmt(if_else);
93+
if matches!(else_expr.kind, ExprKind::Block(..)) {
94+
return false;
95+
}
9596
let ret = strip_return(else_expr);
9697
let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
9798
if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
9899
if let ExprKind::Path(ref qpath) = ret.kind {
99100
return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
100101
}
101-
} else {
102-
return eq_expr_value(cx, if_let.let_expr, ret);
102+
return true;
103103
}
104-
return true;
104+
return eq_expr_value(cx, if_let.let_expr, ret);
105105
}
106106
}
107+
107108
false
108109
}
109110

@@ -116,48 +117,70 @@ fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
116117
}
117118
}
118119

120+
/// Manually check for coercion casting by checking if the type of the match operand or let expr
121+
/// differs with the assigned local variable or the funtion return type.
122+
fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool {
123+
if let Some(p_node) = get_parent_node(cx.tcx, p_expr.hir_id) {
124+
match p_node {
125+
// Compare match_expr ty with local in `let local = match match_expr {..}`
126+
Node::Local(local) => {
127+
let results = cx.typeck_results();
128+
return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
129+
},
130+
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
131+
Node::Item(..) => {
132+
if let Some(fn_decl) = p_node.fn_decl() {
133+
if let FnRetTy::Return(ret_ty) = fn_decl.output {
134+
return same_type_and_consts(hir_ty_to_ty(cx.tcx, ret_ty), cx.typeck_results().expr_ty(expr));
135+
}
136+
}
137+
},
138+
// check the parent expr for this whole block `{ match match_expr {..} }`
139+
Node::Block(block) => {
140+
if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
141+
return expr_ty_matches_p_ty(cx, expr, block_parent_expr);
142+
}
143+
},
144+
// recursively call on `if xxx {..}` etc.
145+
Node::Expr(p_expr) => {
146+
return expr_ty_matches_p_ty(cx, expr, p_expr);
147+
},
148+
_ => {},
149+
}
150+
}
151+
false
152+
}
153+
119154
fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
120155
let expr = strip_return(expr);
121156
match (&pat.kind, &expr.kind) {
122157
// Example: `Some(val) => Some(val)`
123-
(
124-
PatKind::TupleStruct(QPath::Resolved(_, path), [first_pat, ..], _),
125-
ExprKind::Call(call_expr, [first_param, ..]),
126-
) => {
158+
(PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => {
127159
if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
128-
if has_identical_segments(path.segments, call_path.segments)
129-
&& has_same_non_ref_symbol(first_pat, first_param)
130-
{
131-
return true;
132-
}
160+
return over(path.segments, call_path.segments, |pat_seg, call_seg| {
161+
pat_seg.ident.name == call_seg.ident.name
162+
}) && same_non_ref_symbols(tuple_params, call_params);
133163
}
134164
},
135-
// Example: `val => val`, or `ref val => *val`
136-
(PatKind::Binding(annot, _, pat_ident, _), _) => {
137-
let new_expr = if let (
138-
BindingAnnotation::Ref | BindingAnnotation::RefMut,
139-
ExprKind::Unary(UnOp::Deref, operand_expr),
140-
) = (annot, &expr.kind)
141-
{
142-
operand_expr
143-
} else {
144-
expr
145-
};
146-
147-
if let ExprKind::Path(QPath::Resolved(
165+
// Example: `val => val`
166+
(
167+
PatKind::Binding(annot, _, pat_ident, _),
168+
ExprKind::Path(QPath::Resolved(
148169
_,
149170
Path {
150171
segments: [first_seg, ..],
151172
..
152173
},
153-
)) = new_expr.kind
154-
{
155-
return pat_ident.name == first_seg.ident.name;
156-
}
174+
)),
175+
) => {
176+
return !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut)
177+
&& pat_ident.name == first_seg.ident.name;
157178
},
158179
// Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
159180
(PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
160-
return has_identical_segments(p_path.segments, e_path.segments);
181+
return over(p_path.segments, e_path.segments, |p_seg, e_seg| {
182+
p_seg.ident.name == e_seg.ident.name
183+
});
161184
},
162185
// Example: `5 => 5`
163186
(PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
@@ -171,27 +194,16 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
171194
false
172195
}
173196

174-
fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
175-
if left_segs.len() != right_segs.len() {
197+
fn same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool {
198+
if pats.len() != exprs.len() {
176199
return false;
177200
}
178-
for i in 0..left_segs.len() {
179-
if left_segs[i].ident.name != right_segs[i].ident.name {
180-
return false;
181-
}
182-
}
183-
true
184-
}
185201

186-
fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
187-
if_chain! {
188-
if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind;
189-
if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
190-
if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind;
191-
then {
192-
return pat_ident.name == first_seg.ident.name;
202+
for i in 0..pats.len() {
203+
if !pat_same_as_expr(&pats[i], &exprs[i]) {
204+
return false;
193205
}
194206
}
195207

196-
false
208+
true
197209
}

0 commit comments

Comments
 (0)