Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions clippy_lints/src/loops/never_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::ForLoop;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::snippet;
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
use rustc_errors::Applicability;
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind, StructTailExpr};
use rustc_lint::LateContext;
use rustc_span::{Span, sym};
use std::iter::once;
use std::ops::ControlFlow;

pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
Expand All @@ -24,17 +26,23 @@ pub(super) fn check<'tcx>(
arg: iterator,
pat,
span: for_span,
label,
..
}) = for_loop
{
// Suggests using an `if let` instead. This is `Unspecified` because the
// loop may (probably) contain `break` statements which would be invalid
// in an `if let`.
// If the block contains a break or continue, or if the loop has a label, `MachineApplicable` is not
// appropriate.
let app = if !contains_any_break_or_continue(block) && label.is_none() {
Applicability::MachineApplicable
} else {
Applicability::Unspecified
};

diag.span_suggestion_verbose(
for_span.with_hi(iterator.span.hi()),
"if you need the first element of the iterator, try writing",
for_to_if_let_sugg(cx, iterator, pat),
Applicability::Unspecified,
app,
);
}
});
Expand All @@ -43,6 +51,15 @@ pub(super) fn check<'tcx>(
}
}

fn contains_any_break_or_continue(block: &Block<'_>) -> bool {
for_each_expr_without_closures(block, |e| match e.kind {
ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
_ => ControlFlow::Continue(Descend::Yes),
})
.is_some()
}

/// The `never_loop` analysis keeps track of three things:
///
/// * Has any (reachable) code path hit a `continue` of the main loop?
Expand Down
28 changes: 28 additions & 0 deletions tests/ui/never_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,34 @@ pub fn issue12205() -> Option<()> {
}
}

fn stmt_after_return() {
for v in 0..10 {
//~^ never_loop
break;
println!("{v}");
}
}

fn loop_label() {
'outer: for v in 0..10 {
//~^ never_loop
loop {
//~^ never_loop
break 'outer;
}
return;
}

for v in 0..10 {
//~^ never_loop
'inner: loop {
//~^ never_loop
break 'inner;
}
return;
}
}

fn main() {
test1();
test2();
Expand Down
70 changes: 69 additions & 1 deletion tests/ui/never_loop.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -164,5 +164,73 @@ LL | | unimplemented!("not yet");
LL | | }
| |_____^

error: aborting due to 16 previous errors
error: this loop never actually loops
--> tests/ui/never_loop.rs:426:5
|
LL | / for v in 0..10 {
LL | |
LL | | break;
LL | | println!("{v}");
LL | | }
| |_____^
|
help: if you need the first element of the iterator, try writing
|
LL - for v in 0..10 {
LL + if let Some(v) = (0..10).next() {
|

error: this loop never actually loops
--> tests/ui/never_loop.rs:434:5
|
LL | / 'outer: for v in 0..10 {
LL | |
LL | | loop {
... |
LL | | return;
LL | | }
| |_____^
|
help: if you need the first element of the iterator, try writing
|
LL - 'outer: for v in 0..10 {
LL + if let Some(v) = (0..10).next() {
|

error: this loop never actually loops
--> tests/ui/never_loop.rs:436:9
|
LL | / loop {
LL | |
LL | | break 'outer;
LL | | }
| |_________^

error: this loop never actually loops
--> tests/ui/never_loop.rs:443:5
|
LL | / for v in 0..10 {
LL | |
LL | | 'inner: loop {
... |
LL | | return;
LL | | }
| |_____^
|
help: if you need the first element of the iterator, try writing
|
LL - for v in 0..10 {
LL + if let Some(v) = (0..10).next() {
|

error: this loop never actually loops
--> tests/ui/never_loop.rs:445:9
|
LL | / 'inner: loop {
LL | |
LL | | break 'inner;
LL | | }
| |_________^

error: aborting due to 21 previous errors

20 changes: 20 additions & 0 deletions tests/ui/never_loop_fixable.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#![allow(clippy::iter_next_slice, clippy::needless_return)]

fn no_break_or_continue_loop() {
if let Some(i) = [1, 2, 3].iter().next() {
//~^ never_loop
return;
}
}

fn no_break_or_continue_loop_outer() {
if let Some(i) = [1, 2, 3].iter().next() {
//~^ never_loop
return;
loop {
if true {
continue;
}
}
}
}
20 changes: 20 additions & 0 deletions tests/ui/never_loop_fixable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#![allow(clippy::iter_next_slice, clippy::needless_return)]

fn no_break_or_continue_loop() {
for i in [1, 2, 3].iter() {
//~^ never_loop
return;
}
}

fn no_break_or_continue_loop_outer() {
for i in [1, 2, 3].iter() {
//~^ never_loop
return;
loop {
if true {
continue;
}
}
}
}
35 changes: 35 additions & 0 deletions tests/ui/never_loop_fixable.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
error: this loop never actually loops
--> tests/ui/never_loop_fixable.rs:4:5
|
LL | / for i in [1, 2, 3].iter() {
LL | |
LL | | return;
LL | | }
| |_____^
|
= note: `#[deny(clippy::never_loop)]` on by default
help: if you need the first element of the iterator, try writing
|
LL - for i in [1, 2, 3].iter() {
LL + if let Some(i) = [1, 2, 3].iter().next() {
|

error: this loop never actually loops
--> tests/ui/never_loop_fixable.rs:11:5
|
LL | / for i in [1, 2, 3].iter() {
LL | |
LL | | return;
LL | | loop {
... |
LL | | }
| |_____^
|
help: if you need the first element of the iterator, try writing
|
LL - for i in [1, 2, 3].iter() {
LL + if let Some(i) = [1, 2, 3].iter().next() {
|

error: aborting due to 2 previous errors