Skip to content

Commit e7acebc

Browse files
committed
it works
1 parent 5b23bd4 commit e7acebc

File tree

7 files changed

+790
-0
lines changed

7 files changed

+790
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6084,6 +6084,7 @@ Released 2018-09-13
60846084
[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy
60856085
[`drop_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_non_drop
60866086
[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
6087+
[`duplicate_match_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_match_guards
60876088
[`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod
60886089
[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
60896090
[`duplicated_attributes`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicated_attributes

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
132132
crate::drop_forget_ref::DROP_NON_DROP_INFO,
133133
crate::drop_forget_ref::FORGET_NON_DROP_INFO,
134134
crate::drop_forget_ref::MEM_FORGET_INFO,
135+
crate::duplicate_match_guards::DUPLICATE_MATCH_GUARDS_INFO,
135136
crate::duplicate_mod::DUPLICATE_MOD_INFO,
136137
crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO,
137138
crate::empty_drop::EMPTY_DROP_INFO,
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
2+
use clippy_utils::source::{
3+
HasSession, IntoSpan, SpanRangeExt, indent_of, reindent_multiline, snippet_with_applicability, trim_span,
4+
};
5+
use clippy_utils::{eq_expr_value, span_contains_comment};
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{Arm, ExprKind};
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_session::declare_lint_pass;
10+
use rustc_span::BytePos;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Checks for the same condition being checked in a match guard and in the match body
15+
///
16+
/// ### Why is this bad?
17+
/// This is usually just a typo or a copy and paste error.
18+
///
19+
/// ### Example
20+
/// ```no_run
21+
/// # let n = 0;
22+
/// # let a = 3;
23+
/// # let b = 4;
24+
/// match n {
25+
/// 0 if a > b => {
26+
/// if a > b {
27+
/// return;
28+
/// }
29+
/// }
30+
/// _ => {}
31+
/// }
32+
/// ```
33+
/// Use instead:
34+
/// ```no_run
35+
/// # let n = 0;
36+
/// # let a = 3;
37+
/// # let b = 4;
38+
/// match n {
39+
/// 0 if a > b => {
40+
/// return;
41+
/// }
42+
/// _ => {}
43+
/// }
44+
/// ```
45+
#[clippy::version = "1.92.0"]
46+
pub DUPLICATE_MATCH_GUARDS,
47+
nursery,
48+
"a condition in match body duplicating the match guard"
49+
}
50+
51+
declare_lint_pass!(DuplicateMatchGuards => [DUPLICATE_MATCH_GUARDS]);
52+
53+
impl<'tcx> LateLintPass<'tcx> for DuplicateMatchGuards {
54+
fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx Arm<'_>) {
55+
let Some(guard) = arm.guard else {
56+
return;
57+
};
58+
59+
let (arm_body_expr, body_has_block) = if let ExprKind::Block(block, _) = arm.body.kind {
60+
if block.stmts.is_empty()
61+
&& let Some(trailing_expr) = block.expr
62+
{
63+
(trailing_expr, true)
64+
} else {
65+
// the body contains something other than the `if` clause -- bail out
66+
return;
67+
}
68+
} else {
69+
(arm.body, false)
70+
};
71+
72+
if let ExprKind::If(cond, then, None) = arm_body_expr.kind
73+
&& eq_expr_value(cx, guard, cond.peel_drop_temps())
74+
{
75+
// Make sure that we won't swallow any comments.
76+
// Be extra conservative and bail out on _any_ comment outside of `then`:
77+
//
78+
// <pat> if <guard> => { if <cond> <then> }
79+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^
80+
let sm = cx.sess().source_map();
81+
if span_contains_comment(sm, arm.span.with_hi(then.span.lo()))
82+
|| span_contains_comment(sm, arm.span.with_lo(then.span.hi()))
83+
{
84+
return;
85+
}
86+
87+
// The two expressions may be syntactically different, even if identical semantically
88+
// -- the user might want to replace the condition in the guard with the one in the body.
89+
let mut applicability = Applicability::MaybeIncorrect;
90+
91+
if body_has_block {
92+
// the common case:
93+
// ```
94+
// match 0u32 {
95+
// 0 if true => {
96+
// if true {
97+
// return;
98+
// }
99+
// }
100+
// }
101+
// ```
102+
//
103+
// The arm body already has curlies, so we can remove the ones around `then`
104+
105+
if !then
106+
.span
107+
.check_source_text(cx, |s| s.starts_with('{') && s.ends_with('}'))
108+
{
109+
// Despite being a block, `then` somehow does not start and end with curlies.
110+
// Bail out just to be sure
111+
return;
112+
}
113+
114+
let sugg_span = then
115+
.span
116+
.with_lo(then.span.lo() + BytePos(1))
117+
.with_hi(then.span.hi() - BytePos(1));
118+
119+
let sugg_span = trim_span(sm, sugg_span);
120+
121+
let sugg = snippet_with_applicability(cx, sugg_span, "..", &mut applicability);
122+
123+
// Since we remove one level of curlies, we should be able to dedent `then` left one level, so this:
124+
// ```
125+
// <pat> if <guard> => {
126+
// if <cond> {
127+
// then
128+
// without
129+
// curlies
130+
// }
131+
// }
132+
// ```
133+
// becomes this:
134+
// ```
135+
// <pat> if <guard> => {
136+
// then
137+
// without
138+
// curlies
139+
// }
140+
// ```
141+
let indent = indent_of(cx, sugg_span);
142+
let sugg = reindent_multiline(&sugg, true, indent.map(|i| i.saturating_sub(4)));
143+
144+
span_lint_and_sugg(
145+
cx,
146+
DUPLICATE_MATCH_GUARDS,
147+
arm_body_expr.span,
148+
"condition duplicates match guard",
149+
"remove the condition",
150+
sugg,
151+
applicability,
152+
);
153+
} else {
154+
// The uncommon case (rusfmt would add the curlies here automatically)
155+
// ```
156+
// match 0u32 {
157+
// <pat> if <guard> => if <cond> { return; }
158+
// }
159+
// ```
160+
//
161+
// the arm body doesn't have its own curlies,
162+
// so we need to retain the ones around `then`
163+
164+
let span_to_remove = arm_body_expr
165+
.span
166+
.with_hi(cond.span.hi())
167+
// NOTE: we don't remove trailing whitespace so that there's one space left between `<cond>` and `{`
168+
.with_leading_whitespace(cx)
169+
.into_span();
170+
171+
span_lint_and_then(
172+
cx,
173+
DUPLICATE_MATCH_GUARDS,
174+
arm_body_expr.span,
175+
"condition duplicates match guard",
176+
|diag| {
177+
diag.multipart_suggestion_verbose(
178+
"remove the condition",
179+
vec![(span_to_remove, String::new())],
180+
applicability,
181+
);
182+
},
183+
);
184+
};
185+
}
186+
}
187+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ mod disallowed_types;
119119
mod doc;
120120
mod double_parens;
121121
mod drop_forget_ref;
122+
mod duplicate_match_guards;
122123
mod duplicate_mod;
123124
mod else_if_without_else;
124125
mod empty_drop;
@@ -832,5 +833,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
832833
store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny));
833834
store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg));
834835
store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites));
836+
store.register_late_pass(|_| Box::new(duplicate_match_guards::DuplicateMatchGuards));
835837
// add lints here, do not remove this comment, it's used in `new_lint`
836838
}

0 commit comments

Comments
 (0)