Skip to content

Commit 8b0bf64

Browse files
committed
Auto merge of rust-lang#11818 - y21:more_redundant_guards, r=llogiq
[`redundant_guards`]: catch `is_empty`, `starts_with` and `ends_with` on slices and `str`s Fixes rust-lang#11807 Few things worth mentioning: - Taking `snippet`s is now done at callsite, instead of passing a span and doing it in `emit_redundant_guards`. This is because we now need custom suggestion strings in certain places, like `""` for `str::is_empty`. - This now uses `snippet` instead of `snippet_with_applicability`. I don't think this really makes any difference for `MaybeIncorrect`, though? - This could also lint byte strings, as they're of type `&[u8; N]`, but that can be ugly so I decided to leave it out for now changelog: [`redundant_guards`]: catch `str::is_empty`, `slice::is_empty`, `slice::starts_with` and `slice::ends_with`
2 parents 57397a5 + 8f9c738 commit 8b0bf64

File tree

5 files changed

+293
-24
lines changed

5 files changed

+293
-24
lines changed

clippy_lints/src/manual_async_fn.rs

+6-9
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,11 @@ fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>)
187187
}
188188

189189
fn suggested_ret(cx: &LateContext<'_>, output: &Ty<'_>) -> Option<(&'static str, String)> {
190-
match output.kind {
191-
TyKind::Tup(tys) if tys.is_empty() => {
192-
let sugg = "remove the return type";
193-
Some((sugg, String::new()))
194-
},
195-
_ => {
196-
let sugg = "return the output of the future directly";
197-
snippet_opt(cx, output.span).map(|snip| (sugg, format!(" -> {snip}")))
198-
},
190+
if let TyKind::Tup([]) = output.kind {
191+
let sugg = "remove the return type";
192+
Some((sugg, String::new()))
193+
} else {
194+
let sugg = "return the output of the future directly";
195+
snippet_opt(cx, output.span).map(|snip| (sugg, format!(" -> {snip}")))
199196
}
200197
}

clippy_lints/src/matches/redundant_guards.rs

+88-14
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::path_to_local;
3-
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::source::snippet;
44
use clippy_utils::visitors::{for_each_expr, is_local_used};
55
use rustc_ast::{BorrowKind, LitKind};
66
use rustc_errors::Applicability;
77
use rustc_hir::def::{DefKind, Res};
88
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind};
99
use rustc_lint::LateContext;
1010
use rustc_span::symbol::Ident;
11-
use rustc_span::Span;
11+
use rustc_span::{Span, Symbol};
12+
use std::borrow::Cow;
1213
use std::ops::ControlFlow;
1314

1415
use super::REDUNDANT_GUARDS;
@@ -41,7 +42,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) {
4142
(PatKind::Ref(..), None) | (_, Some(_)) => continue,
4243
_ => arm.pat.span,
4344
};
44-
emit_redundant_guards(cx, outer_arm, if_expr.span, pat_span, &binding, arm.guard);
45+
emit_redundant_guards(
46+
cx,
47+
outer_arm,
48+
if_expr.span,
49+
snippet(cx, pat_span, "<binding>"),
50+
&binding,
51+
arm.guard,
52+
);
4553
}
4654
// `Some(x) if let Some(2) = x`
4755
else if let Guard::IfLet(let_expr) = guard
@@ -52,7 +60,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) {
5260
(PatKind::Ref(..), None) | (_, Some(_)) => continue,
5361
_ => let_expr.pat.span,
5462
};
55-
emit_redundant_guards(cx, outer_arm, let_expr.span, pat_span, &binding, None);
63+
emit_redundant_guards(
64+
cx,
65+
outer_arm,
66+
let_expr.span,
67+
snippet(cx, pat_span, "<binding>"),
68+
&binding,
69+
None,
70+
);
5671
}
5772
// `Some(x) if x == Some(2)`
5873
// `Some(x) if Some(2) == x`
@@ -78,11 +93,76 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) {
7893
(ExprKind::AddrOf(..), None) | (_, Some(_)) => continue,
7994
_ => pat.span,
8095
};
81-
emit_redundant_guards(cx, outer_arm, if_expr.span, pat_span, &binding, None);
96+
emit_redundant_guards(
97+
cx,
98+
outer_arm,
99+
if_expr.span,
100+
snippet(cx, pat_span, "<binding>"),
101+
&binding,
102+
None,
103+
);
104+
} else if let Guard::If(if_expr) = guard
105+
&& let ExprKind::MethodCall(path, recv, args, ..) = if_expr.kind
106+
&& let Some(binding) = get_pat_binding(cx, recv, outer_arm)
107+
{
108+
check_method_calls(cx, outer_arm, path.ident.name, recv, args, if_expr, &binding);
82109
}
83110
}
84111
}
85112

113+
fn check_method_calls<'tcx>(
114+
cx: &LateContext<'tcx>,
115+
arm: &Arm<'tcx>,
116+
method: Symbol,
117+
recv: &Expr<'_>,
118+
args: &[Expr<'_>],
119+
if_expr: &Expr<'_>,
120+
binding: &PatBindingInfo,
121+
) {
122+
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
123+
let slice_like = ty.is_slice() || ty.is_array();
124+
125+
let sugg = if method == sym!(is_empty) {
126+
// `s if s.is_empty()` becomes ""
127+
// `arr if arr.is_empty()` becomes []
128+
129+
if ty.is_str() {
130+
r#""""#.into()
131+
} else if slice_like {
132+
"[]".into()
133+
} else {
134+
return;
135+
}
136+
} else if slice_like
137+
&& let Some(needle) = args.first()
138+
&& let ExprKind::AddrOf(.., needle) = needle.kind
139+
&& let ExprKind::Array(needles) = needle.kind
140+
&& needles.iter().all(|needle| expr_can_be_pat(cx, needle))
141+
{
142+
// `arr if arr.starts_with(&[123])` becomes [123, ..]
143+
// `arr if arr.ends_with(&[123])` becomes [.., 123]
144+
// `arr if arr.starts_with(&[])` becomes [..] (why would anyone write this?)
145+
146+
let mut sugg = snippet(cx, needle.span, "<needle>").into_owned();
147+
148+
if needles.is_empty() {
149+
sugg.insert_str(1, "..");
150+
} else if method == sym!(starts_with) {
151+
sugg.insert_str(sugg.len() - 1, ", ..");
152+
} else if method == sym!(ends_with) {
153+
sugg.insert_str(1, ".., ");
154+
} else {
155+
return;
156+
}
157+
158+
sugg.into()
159+
} else {
160+
return;
161+
};
162+
163+
emit_redundant_guards(cx, arm, if_expr.span, sugg, binding, None);
164+
}
165+
86166
struct PatBindingInfo {
87167
span: Span,
88168
byref_ident: Option<Ident>,
@@ -134,19 +214,16 @@ fn emit_redundant_guards<'tcx>(
134214
cx: &LateContext<'tcx>,
135215
outer_arm: &Arm<'tcx>,
136216
guard_span: Span,
137-
pat_span: Span,
217+
binding_replacement: Cow<'static, str>,
138218
pat_binding: &PatBindingInfo,
139219
inner_guard: Option<Guard<'_>>,
140220
) {
141-
let mut app = Applicability::MaybeIncorrect;
142-
143221
span_lint_and_then(
144222
cx,
145223
REDUNDANT_GUARDS,
146224
guard_span.source_callsite(),
147225
"redundant guard",
148226
|diag| {
149-
let binding_replacement = snippet_with_applicability(cx, pat_span, "<binding_repl>", &mut app);
150227
let suggestion_span = match *pat_binding {
151228
PatBindingInfo {
152229
span,
@@ -170,14 +247,11 @@ fn emit_redundant_guards<'tcx>(
170247
Guard::IfLet(l) => ("if let", l.span),
171248
};
172249

173-
format!(
174-
" {prefix} {}",
175-
snippet_with_applicability(cx, span, "<guard>", &mut app),
176-
)
250+
format!(" {prefix} {}", snippet(cx, span, "<guard>"))
177251
}),
178252
),
179253
],
180-
app,
254+
Applicability::MaybeIncorrect,
181255
);
182256
},
183257
);

tests/ui/redundant_guards.fixed

+57
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,60 @@ mod issue11465 {
193193
}
194194
}
195195
}
196+
197+
fn issue11807() {
198+
#![allow(clippy::single_match)]
199+
200+
match Some(Some("")) {
201+
Some(Some("")) => {},
202+
_ => {},
203+
}
204+
205+
match Some(Some(String::new())) {
206+
// Do not lint: String deref-coerces to &str
207+
Some(Some(x)) if x.is_empty() => {},
208+
_ => {},
209+
}
210+
211+
match Some(Some(&[] as &[i32])) {
212+
Some(Some([])) => {},
213+
_ => {},
214+
}
215+
216+
match Some(Some([] as [i32; 0])) {
217+
Some(Some([])) => {},
218+
_ => {},
219+
}
220+
221+
match Some(Some(Vec::<()>::new())) {
222+
// Do not lint: Vec deref-coerces to &[T]
223+
Some(Some(x)) if x.is_empty() => {},
224+
_ => {},
225+
}
226+
227+
match Some(Some(&[] as &[i32])) {
228+
Some(Some([..])) => {},
229+
_ => {},
230+
}
231+
232+
match Some(Some(&[] as &[i32])) {
233+
Some(Some([1, ..])) => {},
234+
_ => {},
235+
}
236+
237+
match Some(Some(&[] as &[i32])) {
238+
Some(Some([1, 2, ..])) => {},
239+
_ => {},
240+
}
241+
242+
match Some(Some(&[] as &[i32])) {
243+
Some(Some([.., 1, 2])) => {},
244+
_ => {},
245+
}
246+
247+
match Some(Some(Vec::<i32>::new())) {
248+
// Do not lint: deref coercion
249+
Some(Some(x)) if x.starts_with(&[1, 2]) => {},
250+
_ => {},
251+
}
252+
}

tests/ui/redundant_guards.rs

+57
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,60 @@ mod issue11465 {
193193
}
194194
}
195195
}
196+
197+
fn issue11807() {
198+
#![allow(clippy::single_match)]
199+
200+
match Some(Some("")) {
201+
Some(Some(x)) if x.is_empty() => {},
202+
_ => {},
203+
}
204+
205+
match Some(Some(String::new())) {
206+
// Do not lint: String deref-coerces to &str
207+
Some(Some(x)) if x.is_empty() => {},
208+
_ => {},
209+
}
210+
211+
match Some(Some(&[] as &[i32])) {
212+
Some(Some(x)) if x.is_empty() => {},
213+
_ => {},
214+
}
215+
216+
match Some(Some([] as [i32; 0])) {
217+
Some(Some(x)) if x.is_empty() => {},
218+
_ => {},
219+
}
220+
221+
match Some(Some(Vec::<()>::new())) {
222+
// Do not lint: Vec deref-coerces to &[T]
223+
Some(Some(x)) if x.is_empty() => {},
224+
_ => {},
225+
}
226+
227+
match Some(Some(&[] as &[i32])) {
228+
Some(Some(x)) if x.starts_with(&[]) => {},
229+
_ => {},
230+
}
231+
232+
match Some(Some(&[] as &[i32])) {
233+
Some(Some(x)) if x.starts_with(&[1]) => {},
234+
_ => {},
235+
}
236+
237+
match Some(Some(&[] as &[i32])) {
238+
Some(Some(x)) if x.starts_with(&[1, 2]) => {},
239+
_ => {},
240+
}
241+
242+
match Some(Some(&[] as &[i32])) {
243+
Some(Some(x)) if x.ends_with(&[1, 2]) => {},
244+
_ => {},
245+
}
246+
247+
match Some(Some(Vec::<i32>::new())) {
248+
// Do not lint: deref coercion
249+
Some(Some(x)) if x.starts_with(&[1, 2]) => {},
250+
_ => {},
251+
}
252+
}

tests/ui/redundant_guards.stderr

+85-1
Original file line numberDiff line numberDiff line change
@@ -203,5 +203,89 @@ LL - B { ref c, .. } if matches!(c, &1) => {},
203203
LL + B { c: 1, .. } => {},
204204
|
205205

206-
error: aborting due to 17 previous errors
206+
error: redundant guard
207+
--> $DIR/redundant_guards.rs:201:26
208+
|
209+
LL | Some(Some(x)) if x.is_empty() => {},
210+
| ^^^^^^^^^^^^
211+
|
212+
help: try
213+
|
214+
LL - Some(Some(x)) if x.is_empty() => {},
215+
LL + Some(Some("")) => {},
216+
|
217+
218+
error: redundant guard
219+
--> $DIR/redundant_guards.rs:212:26
220+
|
221+
LL | Some(Some(x)) if x.is_empty() => {},
222+
| ^^^^^^^^^^^^
223+
|
224+
help: try
225+
|
226+
LL - Some(Some(x)) if x.is_empty() => {},
227+
LL + Some(Some([])) => {},
228+
|
229+
230+
error: redundant guard
231+
--> $DIR/redundant_guards.rs:217:26
232+
|
233+
LL | Some(Some(x)) if x.is_empty() => {},
234+
| ^^^^^^^^^^^^
235+
|
236+
help: try
237+
|
238+
LL - Some(Some(x)) if x.is_empty() => {},
239+
LL + Some(Some([])) => {},
240+
|
241+
242+
error: redundant guard
243+
--> $DIR/redundant_guards.rs:228:26
244+
|
245+
LL | Some(Some(x)) if x.starts_with(&[]) => {},
246+
| ^^^^^^^^^^^^^^^^^^
247+
|
248+
help: try
249+
|
250+
LL - Some(Some(x)) if x.starts_with(&[]) => {},
251+
LL + Some(Some([..])) => {},
252+
|
253+
254+
error: redundant guard
255+
--> $DIR/redundant_guards.rs:233:26
256+
|
257+
LL | Some(Some(x)) if x.starts_with(&[1]) => {},
258+
| ^^^^^^^^^^^^^^^^^^^
259+
|
260+
help: try
261+
|
262+
LL - Some(Some(x)) if x.starts_with(&[1]) => {},
263+
LL + Some(Some([1, ..])) => {},
264+
|
265+
266+
error: redundant guard
267+
--> $DIR/redundant_guards.rs:238:26
268+
|
269+
LL | Some(Some(x)) if x.starts_with(&[1, 2]) => {},
270+
| ^^^^^^^^^^^^^^^^^^^^^^
271+
|
272+
help: try
273+
|
274+
LL - Some(Some(x)) if x.starts_with(&[1, 2]) => {},
275+
LL + Some(Some([1, 2, ..])) => {},
276+
|
277+
278+
error: redundant guard
279+
--> $DIR/redundant_guards.rs:243:26
280+
|
281+
LL | Some(Some(x)) if x.ends_with(&[1, 2]) => {},
282+
| ^^^^^^^^^^^^^^^^^^^^
283+
|
284+
help: try
285+
|
286+
LL - Some(Some(x)) if x.ends_with(&[1, 2]) => {},
287+
LL + Some(Some([.., 1, 2])) => {},
288+
|
289+
290+
error: aborting due to 24 previous errors
207291

0 commit comments

Comments
 (0)