Skip to content

Commit f2feb0f

Browse files
authored
make never_loop applicability more flexible (#14203)
The applicability of `never_loop` is currently set to `Unspecified`, but if the loop block does not contain `break` or `continue`, it can be `MachineApplicable`. changelog: [`never_loop`]: the applicability is now `MachineApplicable` when the loop block contains neither `break` nor `continue`
2 parents 221ae5f + 90dbc5b commit f2feb0f

6 files changed

+193
-5
lines changed

clippy_lints/src/loops/never_loop.rs

+21-4
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ use clippy_utils::diagnostics::span_lint_and_then;
44
use clippy_utils::higher::ForLoop;
55
use clippy_utils::macros::root_macro_call_first_node;
66
use clippy_utils::source::snippet;
7+
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
78
use rustc_errors::Applicability;
89
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind, StructTailExpr};
910
use rustc_lint::LateContext;
1011
use rustc_span::{Span, sym};
1112
use std::iter::once;
13+
use std::ops::ControlFlow;
1214

1315
pub(super) fn check<'tcx>(
1416
cx: &LateContext<'tcx>,
@@ -24,17 +26,23 @@ pub(super) fn check<'tcx>(
2426
arg: iterator,
2527
pat,
2628
span: for_span,
29+
label,
2730
..
2831
}) = for_loop
2932
{
30-
// Suggests using an `if let` instead. This is `Unspecified` because the
31-
// loop may (probably) contain `break` statements which would be invalid
32-
// in an `if let`.
33+
// If the block contains a break or continue, or if the loop has a label, `MachineApplicable` is not
34+
// appropriate.
35+
let app = if !contains_any_break_or_continue(block) && label.is_none() {
36+
Applicability::MachineApplicable
37+
} else {
38+
Applicability::Unspecified
39+
};
40+
3341
diag.span_suggestion_verbose(
3442
for_span.with_hi(iterator.span.hi()),
3543
"if you need the first element of the iterator, try writing",
3644
for_to_if_let_sugg(cx, iterator, pat),
37-
Applicability::Unspecified,
45+
app,
3846
);
3947
}
4048
});
@@ -43,6 +51,15 @@ pub(super) fn check<'tcx>(
4351
}
4452
}
4553

54+
fn contains_any_break_or_continue(block: &Block<'_>) -> bool {
55+
for_each_expr_without_closures(block, |e| match e.kind {
56+
ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
57+
ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
58+
_ => ControlFlow::Continue(Descend::Yes),
59+
})
60+
.is_some()
61+
}
62+
4663
/// The `never_loop` analysis keeps track of three things:
4764
///
4865
/// * Has any (reachable) code path hit a `continue` of the main loop?

tests/ui/never_loop.rs

+28
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,34 @@ pub fn issue12205() -> Option<()> {
422422
}
423423
}
424424

425+
fn stmt_after_return() {
426+
for v in 0..10 {
427+
//~^ never_loop
428+
break;
429+
println!("{v}");
430+
}
431+
}
432+
433+
fn loop_label() {
434+
'outer: for v in 0..10 {
435+
//~^ never_loop
436+
loop {
437+
//~^ never_loop
438+
break 'outer;
439+
}
440+
return;
441+
}
442+
443+
for v in 0..10 {
444+
//~^ never_loop
445+
'inner: loop {
446+
//~^ never_loop
447+
break 'inner;
448+
}
449+
return;
450+
}
451+
}
452+
425453
fn main() {
426454
test1();
427455
test2();

tests/ui/never_loop.stderr

+69-1
Original file line numberDiff line numberDiff line change
@@ -164,5 +164,73 @@ LL | | unimplemented!("not yet");
164164
LL | | }
165165
| |_____^
166166

167-
error: aborting due to 16 previous errors
167+
error: this loop never actually loops
168+
--> tests/ui/never_loop.rs:426:5
169+
|
170+
LL | / for v in 0..10 {
171+
LL | |
172+
LL | | break;
173+
LL | | println!("{v}");
174+
LL | | }
175+
| |_____^
176+
|
177+
help: if you need the first element of the iterator, try writing
178+
|
179+
LL - for v in 0..10 {
180+
LL + if let Some(v) = (0..10).next() {
181+
|
182+
183+
error: this loop never actually loops
184+
--> tests/ui/never_loop.rs:434:5
185+
|
186+
LL | / 'outer: for v in 0..10 {
187+
LL | |
188+
LL | | loop {
189+
... |
190+
LL | | return;
191+
LL | | }
192+
| |_____^
193+
|
194+
help: if you need the first element of the iterator, try writing
195+
|
196+
LL - 'outer: for v in 0..10 {
197+
LL + if let Some(v) = (0..10).next() {
198+
|
199+
200+
error: this loop never actually loops
201+
--> tests/ui/never_loop.rs:436:9
202+
|
203+
LL | / loop {
204+
LL | |
205+
LL | | break 'outer;
206+
LL | | }
207+
| |_________^
208+
209+
error: this loop never actually loops
210+
--> tests/ui/never_loop.rs:443:5
211+
|
212+
LL | / for v in 0..10 {
213+
LL | |
214+
LL | | 'inner: loop {
215+
... |
216+
LL | | return;
217+
LL | | }
218+
| |_____^
219+
|
220+
help: if you need the first element of the iterator, try writing
221+
|
222+
LL - for v in 0..10 {
223+
LL + if let Some(v) = (0..10).next() {
224+
|
225+
226+
error: this loop never actually loops
227+
--> tests/ui/never_loop.rs:445:9
228+
|
229+
LL | / 'inner: loop {
230+
LL | |
231+
LL | | break 'inner;
232+
LL | | }
233+
| |_________^
234+
235+
error: aborting due to 21 previous errors
168236

tests/ui/never_loop_fixable.fixed

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![allow(clippy::iter_next_slice, clippy::needless_return)]
2+
3+
fn no_break_or_continue_loop() {
4+
if let Some(i) = [1, 2, 3].iter().next() {
5+
//~^ never_loop
6+
return;
7+
}
8+
}
9+
10+
fn no_break_or_continue_loop_outer() {
11+
if let Some(i) = [1, 2, 3].iter().next() {
12+
//~^ never_loop
13+
return;
14+
loop {
15+
if true {
16+
continue;
17+
}
18+
}
19+
}
20+
}

tests/ui/never_loop_fixable.rs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![allow(clippy::iter_next_slice, clippy::needless_return)]
2+
3+
fn no_break_or_continue_loop() {
4+
for i in [1, 2, 3].iter() {
5+
//~^ never_loop
6+
return;
7+
}
8+
}
9+
10+
fn no_break_or_continue_loop_outer() {
11+
for i in [1, 2, 3].iter() {
12+
//~^ never_loop
13+
return;
14+
loop {
15+
if true {
16+
continue;
17+
}
18+
}
19+
}
20+
}

tests/ui/never_loop_fixable.stderr

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
error: this loop never actually loops
2+
--> tests/ui/never_loop_fixable.rs:4:5
3+
|
4+
LL | / for i in [1, 2, 3].iter() {
5+
LL | |
6+
LL | | return;
7+
LL | | }
8+
| |_____^
9+
|
10+
= note: `#[deny(clippy::never_loop)]` on by default
11+
help: if you need the first element of the iterator, try writing
12+
|
13+
LL - for i in [1, 2, 3].iter() {
14+
LL + if let Some(i) = [1, 2, 3].iter().next() {
15+
|
16+
17+
error: this loop never actually loops
18+
--> tests/ui/never_loop_fixable.rs:11:5
19+
|
20+
LL | / for i in [1, 2, 3].iter() {
21+
LL | |
22+
LL | | return;
23+
LL | | loop {
24+
... |
25+
LL | | }
26+
| |_____^
27+
|
28+
help: if you need the first element of the iterator, try writing
29+
|
30+
LL - for i in [1, 2, 3].iter() {
31+
LL + if let Some(i) = [1, 2, 3].iter().next() {
32+
|
33+
34+
error: aborting due to 2 previous errors
35+

0 commit comments

Comments
 (0)