Skip to content

Commit c6af9da

Browse files
committed
fix the loop issue
fix lint
1 parent c5dbd1d commit c6af9da

File tree

3 files changed

+136
-5
lines changed

3 files changed

+136
-5
lines changed

clippy_lints/src/loops/infinite_loop.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use hir::intravisit::{Visitor, walk_expr};
44
use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind};
55
use rustc_ast::Label;
66
use rustc_errors::Applicability;
7-
use rustc_hir as hir;
7+
use rustc_hir::{self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind};
88
use rustc_lint::{LateContext, LintContext};
99
use rustc_span::sym;
1010

@@ -29,6 +29,10 @@ pub(super) fn check<'tcx>(
2929
return;
3030
}
3131

32+
if is_inside_unawaited_async_block(cx, expr) {
33+
return;
34+
}
35+
3236
if expr.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, expr) {
3337
return;
3438
}
@@ -60,15 +64,48 @@ pub(super) fn check<'tcx>(
6064
}
6165
}
6266

67+
/// Check if the given expression is inside an async block that is not being awaited.
68+
/// This helps avoid false positives when async blocks are spawned or assigned to variables.
69+
fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
70+
let current_hir_id = expr.hir_id;
71+
for (_, parent_node) in cx.tcx.hir_parent_iter(current_hir_id) {
72+
if let Node::Expr(Expr {
73+
kind:
74+
ExprKind::Closure(Closure {
75+
kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
76+
..
77+
}),
78+
..
79+
}) = parent_node
80+
{
81+
return !is_async_block_awaited(cx, expr);
82+
}
83+
}
84+
false
85+
}
86+
87+
fn is_async_block_awaited(cx: &LateContext<'_>, async_expr: &Expr<'_>) -> bool {
88+
for (_, parent_node) in cx.tcx.hir_parent_iter(async_expr.hir_id) {
89+
if let Node::Expr(Expr {
90+
kind: ExprKind::Match(_, _, hir::MatchSource::AwaitDesugar),
91+
..
92+
}) = parent_node
93+
{
94+
return true;
95+
}
96+
}
97+
false
98+
}
99+
63100
fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<FnRetTy<'tcx>> {
64101
for (_, parent_node) in cx.tcx.hir_parent_iter(expr.hir_id) {
65102
match parent_node {
66103
// Skip `Coroutine` closures, these are the body of `async fn`, not async closures.
67104
// This is because we still need to backtrack one parent node to get the `OpaqueDef` ty.
68105
Node::Expr(Expr {
69106
kind:
70-
ExprKind::Closure(hir::Closure {
71-
kind: hir::ClosureKind::Coroutine(_),
107+
ExprKind::Closure(Closure {
108+
kind: ClosureKind::Coroutine(_),
72109
..
73110
}),
74111
..
@@ -90,7 +127,7 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
90127
..
91128
})
92129
| Node::Expr(Expr {
93-
kind: ExprKind::Closure(hir::Closure { fn_decl: decl, .. }),
130+
kind: ExprKind::Closure(Closure { fn_decl: decl, .. }),
94131
..
95132
}) => return Some(decl.output),
96133
_ => (),

tests/ui/infinite_loops.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,4 +450,76 @@ mod issue_12338 {
450450
}
451451
}
452452

453+
#[allow(clippy::let_underscore_future, clippy::empty_loop)]
454+
mod issue_14000 {
455+
use super::do_something;
456+
457+
async fn foo() {
458+
let _ = async move {
459+
loop {
460+
//~^ infinite_loop
461+
do_something();
462+
}
463+
}
464+
.await;
465+
let _ = async move {
466+
loop {
467+
//~^ infinite_loop
468+
continue;
469+
}
470+
}
471+
.await;
472+
}
473+
474+
fn bar() {
475+
let _ = async move {
476+
loop {
477+
do_something();
478+
}
479+
};
480+
481+
let _ = async move {
482+
loop {
483+
continue;
484+
}
485+
};
486+
}
487+
}
488+
489+
#[allow(clippy::let_underscore_future)]
490+
mod tokio_spawn_test {
491+
use super::do_something;
492+
493+
fn install_ticker() {
494+
let mut schedule = std::time::Duration::from_secs(5);
495+
// This should NOT trigger the lint because the async block is spawned, not awaited
496+
std::thread::spawn(move || {
497+
async move {
498+
loop {
499+
// This loop should not trigger infinite_loop lint
500+
do_something();
501+
}
502+
}
503+
});
504+
}
505+
506+
fn spawn_async_block() {
507+
// This should NOT trigger the lint because the async block is not awaited
508+
let _handle = async move {
509+
loop {
510+
do_something();
511+
}
512+
};
513+
}
514+
515+
fn await_async_block() {
516+
// This SHOULD trigger the lint because the async block is awaited
517+
let _ = async move {
518+
loop {
519+
do_something();
520+
}
521+
};
522+
}
523+
}
524+
453525
fn main() {}

tests/ui/infinite_loops.stderr

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,5 +311,27 @@ help: if this is intentional, consider specifying `!` as function return
311311
LL | fn continue_outer() -> ! {
312312
| ++++
313313

314-
error: aborting due to 21 previous errors
314+
error: infinite loop detected
315+
--> tests/ui/infinite_loops.rs:459:13
316+
|
317+
LL | / loop {
318+
LL | |
319+
LL | | do_something();
320+
LL | | }
321+
| |_____________^
322+
|
323+
= help: if this is not intended, try adding a `break` or `return` condition in the loop
324+
325+
error: infinite loop detected
326+
--> tests/ui/infinite_loops.rs:466:13
327+
|
328+
LL | / loop {
329+
LL | |
330+
LL | | continue;
331+
LL | | }
332+
| |_____________^
333+
|
334+
= help: if this is not intended, try adding a `break` or `return` condition in the loop
335+
336+
error: aborting due to 23 previous errors
315337

0 commit comments

Comments
 (0)