about summary refs log tree commit diff
diff options
context:
space:
mode:
authorChengxu Bian <cbian564@gmail.com>2025-06-27 22:22:20 -0400
committerChengxu Bian <cbian564@gmail.com>2025-08-07 22:17:37 -0400
commitc945393f671a5d981d0e782aacb6bc29d2ff7a0f (patch)
tree8764a28cf64fb08a69dd13208bd7e97b55d02e79
parent32a216ecab289cccc00aa823f3bfffcee74687ae (diff)
downloadrust-c945393f671a5d981d0e782aacb6bc29d2ff7a0f.tar.gz
rust-c945393f671a5d981d0e782aacb6bc29d2ff7a0f.zip
fix inf loop
-rw-r--r--clippy_lints/src/loops/infinite_loop.rs48
-rw-r--r--tests/ui/infinite_loops.rs71
-rw-r--r--tests/ui/infinite_loops.stderr24
3 files changed, 137 insertions, 6 deletions
diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/infinite_loop.rs
index 797ff1f3986..a71e6963f8c 100644
--- a/clippy_lints/src/loops/infinite_loop.rs
+++ b/clippy_lints/src/loops/infinite_loop.rs
@@ -1,10 +1,11 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
 use hir::intravisit::{Visitor, walk_expr};
-use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind};
 use rustc_ast::Label;
 use rustc_errors::Applicability;
-use rustc_hir as hir;
+use rustc_hir::{
+    self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, FnRetTy, FnSig, Node, TyKind,
+};
 use rustc_lint::{LateContext, LintContext};
 use rustc_span::sym;
 
@@ -29,6 +30,10 @@ pub(super) fn check<'tcx>(
         return;
     }
 
+    if is_inside_unawaited_async_block(cx, expr) {
+        return;
+    }
+
     if expr.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, expr) {
         return;
     }
@@ -60,6 +65,39 @@ pub(super) fn check<'tcx>(
     }
 }
 
+/// Check if the given expression is inside an async block that is not being awaited.
+/// This helps avoid false positives when async blocks are spawned or assigned to variables.
+fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    let current_hir_id = expr.hir_id;
+    for (_, parent_node) in cx.tcx.hir_parent_iter(current_hir_id) {
+        if let Node::Expr(Expr {
+            kind:
+                ExprKind::Closure(Closure {
+                    kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
+                    ..
+                }),
+            ..
+        }) = parent_node
+        {
+            return !is_async_block_awaited(cx, expr);
+        }
+    }
+    false
+}
+
+fn is_async_block_awaited(cx: &LateContext<'_>, async_expr: &Expr<'_>) -> bool {
+    for (_, parent_node) in cx.tcx.hir_parent_iter(async_expr.hir_id) {
+        if let Node::Expr(Expr {
+            kind: ExprKind::Match(_, _, hir::MatchSource::AwaitDesugar),
+            ..
+        }) = parent_node
+        {
+            return true;
+        }
+    }
+    false
+}
+
 fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<FnRetTy<'tcx>> {
     for (_, parent_node) in cx.tcx.hir_parent_iter(expr.hir_id) {
         match parent_node {
@@ -67,8 +105,8 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
             // This is because we still need to backtrack one parent node to get the `OpaqueDef` ty.
             Node::Expr(Expr {
                 kind:
-                    ExprKind::Closure(hir::Closure {
-                        kind: hir::ClosureKind::Coroutine(_),
+                    ExprKind::Closure(Closure {
+                        kind: ClosureKind::Coroutine(_),
                         ..
                     }),
                 ..
@@ -90,7 +128,7 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
                 ..
             })
             | Node::Expr(Expr {
-                kind: ExprKind::Closure(hir::Closure { fn_decl: decl, .. }),
+                kind: ExprKind::Closure(Closure { fn_decl: decl, .. }),
                 ..
             }) => return Some(decl.output),
             _ => (),
diff --git a/tests/ui/infinite_loops.rs b/tests/ui/infinite_loops.rs
index fcd1f795fff..9b8c3933197 100644
--- a/tests/ui/infinite_loops.rs
+++ b/tests/ui/infinite_loops.rs
@@ -450,4 +450,75 @@ mod issue_12338 {
     }
 }
 
+#[allow(clippy::let_underscore_future, clippy::empty_loop)]
+mod issue_14000 {
+    use super::do_something;
+
+    async fn foo() {
+        let _ = async move {
+            loop {
+                //~^ infinite_loop
+                do_something();
+            }
+        }
+        .await;
+        let _ = async move {
+            loop {
+                //~^ infinite_loop
+                continue;
+            }
+        }
+        .await;
+    }
+
+    fn bar() {
+        let _ = async move {
+            loop {
+                do_something();
+            }
+        };
+
+        let _ = async move {
+            loop {
+                continue;
+            }
+        };
+    }
+}
+
+#[allow(clippy::let_underscore_future)]
+mod tokio_spawn_test {
+    use super::do_something;
+
+    fn install_ticker() {
+        // This should NOT trigger the lint because the async block is spawned, not awaited
+        std::thread::spawn(move || {
+            async move {
+                loop {
+                    // This loop should not trigger infinite_loop lint
+                    do_something();
+                }
+            }
+        });
+    }
+
+    fn spawn_async_block() {
+        // This should NOT trigger the lint because the async block is not awaited
+        let _handle = async move {
+            loop {
+                do_something();
+            }
+        };
+    }
+
+    fn await_async_block() {
+        // This SHOULD trigger the lint because the async block is awaited
+        let _ = async move {
+            loop {
+                do_something();
+            }
+        };
+    }
+}
+
 fn main() {}
diff --git a/tests/ui/infinite_loops.stderr b/tests/ui/infinite_loops.stderr
index 4d02636ef4a..4c6b6f725f1 100644
--- a/tests/ui/infinite_loops.stderr
+++ b/tests/ui/infinite_loops.stderr
@@ -311,5 +311,27 @@ help: if this is intentional, consider specifying `!` as function return
 LL | fn continue_outer() -> ! {
    |                     ++++
 
-error: aborting due to 21 previous errors
+error: infinite loop detected
+  --> tests/ui/infinite_loops.rs:459:13
+   |
+LL | /             loop {
+LL | |
+LL | |                 do_something();
+LL | |             }
+   | |_____________^
+   |
+   = help: if this is not intended, try adding a `break` or `return` condition in the loop
+
+error: infinite loop detected
+  --> tests/ui/infinite_loops.rs:466:13
+   |
+LL | /             loop {
+LL | |
+LL | |                 continue;
+LL | |             }
+   | |_____________^
+   |
+   = help: if this is not intended, try adding a `break` or `return` condition in the loop
+
+error: aborting due to 23 previous errors