about summary refs log tree commit diff
path: root/clippy_lints/src
diff options
context:
space:
mode:
authorTimo <30553356+y21@users.noreply.github.com>2025-03-20 15:18:29 +0000
committerGitHub <noreply@github.com>2025-03-20 15:18:29 +0000
commitf2feb0f1a9f5ef77249a6fdf66362af079a7f6c6 (patch)
tree04231041648f3d065dbcde04fddbb2efae4ae39e /clippy_lints/src
parent221ae5f1767375c2eb8e9f326db609012d0bcfd0 (diff)
parent90dbc5bf94de29304c18d840c8c90f09f2d32cf6 (diff)
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`
Diffstat (limited to 'clippy_lints/src')
-rw-r--r--clippy_lints/src/loops/never_loop.rs25
1 files changed, 21 insertions, 4 deletions
diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs
index b679fdfadc3..3f8de774eef 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::{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>,
@@ -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,
                     );
                 }
             });
@@ -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?