Skip to content

Commit f7204da

Browse files
committed
make never_loop applicability more flexible
1 parent ffa1caf commit f7204da

File tree

4 files changed

+49
-4
lines changed

4 files changed

+49
-4
lines changed

clippy_lints/src/loops/never_loop.rs

+19-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::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+
&& !is_loop_contains_break_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,14 @@ pub(super) fn check<'tcx>(
4350
}
4451
}
4552

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

tests/ui/never_loop_fixable.fixed

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
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+
return;
6+
}
7+
}

tests/ui/never_loop_fixable.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
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+
return;
6+
}
7+
}

tests/ui/never_loop_fixable.stderr

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 | | return;
6+
LL | | }
7+
| |_____^
8+
|
9+
= note: `#[deny(clippy::never_loop)]` on by default
10+
help: if you need the first element of the iterator, try writing
11+
|
12+
LL | if let Some(i) = [1, 2, 3].iter().next() {
13+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14+
15+
error: aborting due to 1 previous error
16+

0 commit comments

Comments
 (0)