Skip to content

Commit 51b5772

Browse files
committed
new lint redundant_guards
1 parent a44dcf8 commit 51b5772

16 files changed

+681
-83
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5190,6 +5190,7 @@ Released 2018-09-13
51905190
[`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else
51915191
[`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names
51925192
[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
5193+
[`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards
51935194
[`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals
51945195
[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
51955196
[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
308308
crate::matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS_INFO,
309309
crate::matches::MATCH_WILD_ERR_ARM_INFO,
310310
crate::matches::NEEDLESS_MATCH_INFO,
311+
crate::matches::REDUNDANT_GUARDS_INFO,
311312
crate::matches::REDUNDANT_PATTERN_MATCHING_INFO,
312313
crate::matches::REST_PAT_IN_FULLY_BOUND_STRUCTS_INFO,
313314
crate::matches::SIGNIFICANT_DROP_IN_SCRUTINEE_INFO,

clippy_lints/src/loops/utils.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> {
7676
ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => {
7777
*state = IncrementVisitorVarState::DontWarn;
7878
},
79-
ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
79+
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => {
8080
*state = IncrementVisitorVarState::DontWarn;
8181
},
8282
_ => (),
@@ -226,7 +226,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
226226
InitializeVisitorState::DontWarn
227227
}
228228
},
229-
ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
229+
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => {
230230
self.state = InitializeVisitorState::DontWarn;
231231
},
232232
_ => (),

clippy_lints/src/matches/mod.rs

+33
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod match_wild_enum;
1616
mod match_wild_err_arm;
1717
mod needless_match;
1818
mod overlapping_arms;
19+
mod redundant_guards;
1920
mod redundant_pattern_match;
2021
mod rest_pat_in_fully_bound_struct;
2122
mod significant_drop_in_scrutinee;
@@ -936,6 +937,36 @@ declare_clippy_lint! {
936937
"reimplementation of `filter`"
937938
}
938939

940+
declare_clippy_lint! {
941+
/// ### What it does
942+
/// Checks for unnecessary guards in match expressions.
943+
///
944+
/// ### Why is this bad?
945+
/// It's more complex and much less readable. Making it part of the pattern can improve
946+
/// exhaustiveness checking as well.
947+
///
948+
/// ### Example
949+
/// ```rust,ignore
950+
/// match x {
951+
/// Some(x) if matches!(x, Some(1)) => ..,
952+
/// Some(x) if x == Some(2) => ..,
953+
/// _ => todo!(),
954+
/// }
955+
/// ```
956+
/// Use instead:
957+
/// ```rust,ignore
958+
/// match x {
959+
/// Some(Some(1)) => ..,
960+
/// Some(Some(2)) => ..,
961+
/// _ => todo!(),
962+
/// }
963+
/// ```
964+
#[clippy::version = "1.72.0"]
965+
pub REDUNDANT_GUARDS,
966+
complexity,
967+
"checks for unnecessary guards in match expressions"
968+
}
969+
939970
#[derive(Default)]
940971
pub struct Matches {
941972
msrv: Msrv,
@@ -978,6 +1009,7 @@ impl_lint_pass!(Matches => [
9781009
TRY_ERR,
9791010
MANUAL_MAP,
9801011
MANUAL_FILTER,
1012+
REDUNDANT_GUARDS,
9811013
]);
9821014

9831015
impl<'tcx> LateLintPass<'tcx> for Matches {
@@ -1025,6 +1057,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
10251057
needless_match::check_match(cx, ex, arms, expr);
10261058
match_on_vec_items::check(cx, ex);
10271059
match_str_case_mismatch::check(cx, ex, arms);
1060+
redundant_guards::check(cx, arms);
10281061

10291062
if !in_constant(cx, expr.hir_id) {
10301063
manual_unwrap_or::check(cx, expr, ex, arms);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::path_to_local;
3+
use clippy_utils::source::snippet_with_applicability;
4+
use clippy_utils::visitors::{for_each_expr, is_local_used};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::def::{DefKind, Res};
7+
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind};
8+
use rustc_lint::LateContext;
9+
use rustc_span::Span;
10+
use std::ops::ControlFlow;
11+
12+
use super::REDUNDANT_GUARDS;
13+
14+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) {
15+
for outer_arm in arms {
16+
let Some(guard) = outer_arm.guard else {
17+
continue;
18+
};
19+
20+
// `Some(x) if matches!(x, y)`
21+
if let Guard::If(if_expr) = guard
22+
&& let ExprKind::Match(
23+
scrutinee,
24+
[
25+
arm,
26+
Arm {
27+
pat: Pat {
28+
kind: PatKind::Wild,
29+
..
30+
},
31+
..
32+
},
33+
],
34+
MatchSource::Normal,
35+
) = if_expr.kind
36+
{
37+
emit_redundant_guards(
38+
cx,
39+
outer_arm,
40+
if_expr.span,
41+
scrutinee,
42+
arm.pat.span,
43+
arm.guard,
44+
);
45+
}
46+
// `Some(x) if let Some(2) = x`
47+
else if let Guard::IfLet(let_expr) = guard {
48+
emit_redundant_guards(
49+
cx,
50+
outer_arm,
51+
let_expr.span,
52+
let_expr.init,
53+
let_expr.pat.span,
54+
None,
55+
);
56+
}
57+
// `Some(x) if x == Some(2)`
58+
else if let Guard::If(if_expr) = guard
59+
&& let ExprKind::Binary(bin_op, local, pat) = if_expr.kind
60+
&& matches!(bin_op.node, BinOpKind::Eq)
61+
&& expr_can_be_pat(cx, pat)
62+
// Ensure they have the same type. If they don't, we'd need deref coercion which isn't
63+
// possible (currently) in a pattern. In some cases, you can use something like
64+
// `as_deref` or similar but in general, we shouldn't lint this as it'd create an
65+
// extraordinary amount of FPs.
66+
//
67+
// This isn't necessary in the other two checks, as they must be a pattern already.
68+
&& cx.typeck_results().expr_ty(local) == cx.typeck_results().expr_ty(pat)
69+
{
70+
emit_redundant_guards(
71+
cx,
72+
outer_arm,
73+
if_expr.span,
74+
local,
75+
pat.span,
76+
None,
77+
);
78+
}
79+
}
80+
}
81+
82+
fn get_pat_binding<'tcx>(cx: &LateContext<'tcx>, guard_expr: &Expr<'_>, outer_arm: &Arm<'tcx>) -> Option<(Span, bool)> {
83+
if let Some(local) = path_to_local(guard_expr) && !is_local_used(cx, outer_arm.body, local) {
84+
let mut span = None;
85+
let mut multiple_bindings = false;
86+
// `each_binding` gives the `HirId` of the `Pat` itself, not the binding
87+
outer_arm.pat.walk(|pat| {
88+
if let PatKind::Binding(_, hir_id, _, _) = pat.kind
89+
&& hir_id == local
90+
&& span.replace(pat.span).is_some()
91+
{
92+
multiple_bindings = true;
93+
return false;
94+
}
95+
96+
true
97+
});
98+
99+
// Ignore bindings from or patterns, like `First(x) | Second(x, _) | Third(x, _, _)`
100+
if !multiple_bindings {
101+
return span.map(|span| {
102+
(
103+
span,
104+
!matches!(cx.tcx.hir().get_parent(local), Node::PatField(_)),
105+
)
106+
});
107+
}
108+
}
109+
110+
None
111+
}
112+
113+
fn emit_redundant_guards<'tcx>(
114+
cx: &LateContext<'tcx>,
115+
outer_arm: &Arm<'tcx>,
116+
guard_span: Span,
117+
local: &Expr<'_>,
118+
pat_span: Span,
119+
inner_guard: Option<Guard<'_>>,
120+
) {
121+
let mut app = Applicability::MaybeIncorrect;
122+
let Some((pat_binding, can_use_shorthand)) = get_pat_binding(cx, local, outer_arm) else {
123+
return;
124+
};
125+
126+
span_lint_and_then(
127+
cx,
128+
REDUNDANT_GUARDS,
129+
guard_span.source_callsite(),
130+
"redundant guard",
131+
|diag| {
132+
let binding_replacement = snippet_with_applicability(cx, pat_span, "<binding_repl>", &mut app);
133+
diag.multipart_suggestion_verbose(
134+
"try",
135+
vec![
136+
if can_use_shorthand {
137+
(pat_binding, binding_replacement.into_owned())
138+
} else {
139+
(pat_binding.shrink_to_hi(), format!(": {binding_replacement}"))
140+
},
141+
(
142+
guard_span.source_callsite().with_lo(outer_arm.pat.span.hi()),
143+
inner_guard.map_or_else(String::new, |guard| {
144+
let (prefix, span) = match guard {
145+
Guard::If(e) => ("if", e.span),
146+
Guard::IfLet(l) => ("if let", l.span),
147+
};
148+
149+
format!(
150+
" {prefix} {}",
151+
snippet_with_applicability(cx, span, "<guard>", &mut app),
152+
)
153+
}),
154+
),
155+
],
156+
app,
157+
);
158+
},
159+
);
160+
}
161+
162+
/// Checks if the given `Expr` can also be represented as a `Pat`.
163+
fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
164+
for_each_expr(expr, |expr| {
165+
if match expr.kind {
166+
ExprKind::ConstBlock(..) => cx.tcx.features().inline_const_pat,
167+
ExprKind::Call(c, ..) if let ExprKind::Path(qpath) = c.kind => {
168+
// Allow ctors
169+
matches!(cx.qpath_res(&qpath, c.hir_id), Res::Def(DefKind::Ctor(..), ..))
170+
},
171+
ExprKind::Path(qpath) => {
172+
matches!(
173+
cx.qpath_res(&qpath, expr.hir_id),
174+
Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Ctor(..), ..),
175+
)
176+
},
177+
ExprKind::AddrOf(..)
178+
| ExprKind::Array(..)
179+
| ExprKind::Tup(..)
180+
| ExprKind::Struct(..)
181+
| ExprKind::Lit(..) => true,
182+
_ => false,
183+
} {
184+
return ControlFlow::Continue(());
185+
}
186+
187+
ControlFlow::Break(())
188+
})
189+
.is_none()
190+
}

tests/ui/match_expr_like_matches_macro.fixed

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
unreachable_patterns,
66
dead_code,
77
clippy::equatable_if_let,
8-
clippy::needless_borrowed_reference
8+
clippy::needless_borrowed_reference,
9+
clippy::redundant_guards
910
)]
1011

1112
fn main() {

tests/ui/match_expr_like_matches_macro.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
unreachable_patterns,
66
dead_code,
77
clippy::equatable_if_let,
8-
clippy::needless_borrowed_reference
8+
clippy::needless_borrowed_reference,
9+
clippy::redundant_guards
910
)]
1011

1112
fn main() {

0 commit comments

Comments
 (0)