From aaf870c2dfc60b7a05d1f1db00b833fb44bf1318 Mon Sep 17 00:00:00 2001 From: lapla-cogito Date: Wed, 12 Feb 2025 22:25:58 +0900 Subject: [PATCH] make `never_loop` applicability more flexible --- clippy_lints/src/loops/never_loop.rs | 23 +++++++++++++++++++---- tests/ui/never_loop.rs | 6 ++++++ tests/ui/never_loop.stderr | 15 ++++++++++++++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index b679fdfadc3a..818b1ca9eea4 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -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::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>, @@ -27,14 +29,19 @@ pub(super) fn check<'tcx>( .. }) = 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`. + let app = if let Some(block_expr) = block.expr + && !is_loop_contains_break_continue(block_expr) + { + 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, ); } }); @@ -43,6 +50,14 @@ pub(super) fn check<'tcx>( } } +fn is_loop_contains_break_continue(expr: &Expr<'_>) -> bool { + for_each_expr_without_closures(expr, |e| match e.kind { + ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()), + _ => ControlFlow::Continue(()), + }) + .is_some() +} + /// The `never_loop` analysis keeps track of three things: /// /// * Has any (reachable) code path hit a `continue` of the main loop? diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index 93c69209c698..71d21625be87 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -409,6 +409,12 @@ pub fn issue12205() -> Option<()> { } } +fn no_break_or_continue_loop() { + for i in [1, 2, 3].iter().skip(1) { + return; + } +} + fn main() { test1(); test2(); diff --git a/tests/ui/never_loop.stderr b/tests/ui/never_loop.stderr index dab3488af106..aac42f86e46e 100644 --- a/tests/ui/never_loop.stderr +++ b/tests/ui/never_loop.stderr @@ -175,5 +175,18 @@ LL | | unimplemented!("not yet"); LL | | } | |_____^ -error: aborting due to 16 previous errors +error: this loop never actually loops + --> tests/ui/never_loop.rs:413:5 + | +LL | / for i in [1, 2, 3].iter().skip(1) { +LL | | return; +LL | | } + | |_____^ + | +help: if you need the first element of the iterator, try writing + | +LL | if let Some(i) = [1, 2, 3].iter().skip(1).next() { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 17 previous errors