Skip to content

Commit a5eec3b

Browse files
committed
make never_loop applicability more flexible
1 parent 649cef0 commit a5eec3b

File tree

4 files changed

+95
-4
lines changed

4 files changed

+95
-4
lines changed

clippy_lints/src/loops/never_loop.rs

+20-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>,
@@ -27,14 +29,19 @@ pub(super) fn check<'tcx>(
2729
..
2830
}) = for_loop
2931
{
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`.
32+
let app = if let Some(block_expr) = block.expr
33+
&& !contains_any_break_or_continue(block_expr)
34+
{
35+
Applicability::MachineApplicable
36+
} else {
37+
Applicability::Unspecified
38+
};
39+
3340
diag.span_suggestion_verbose(
3441
for_span.with_hi(iterator.span.hi()),
3542
"if you need the first element of the iterator, try writing",
3643
for_to_if_let_sugg(cx, iterator, pat),
37-
Applicability::Unspecified,
44+
app,
3845
);
3946
}
4047
});
@@ -43,6 +50,15 @@ pub(super) fn check<'tcx>(
4350
}
4451
}
4552

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

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)