From 1e73a9eb4be45092fdd5b76cc50db00970c852f7 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sun, 21 May 2023 23:07:30 +0200 Subject: [PATCH] do not consider `await` in nested `async` blocks --- clippy_lints/src/unused_async.rs | 48 +++++++++++++++++++++++++++++--- tests/ui/unused_async.rs | 20 +++++++++++++ tests/ui/unused_async.stderr | 21 +++++++++++--- 3 files changed, 81 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs index 55651a28b..c1339d946 100644 --- a/clippy_lints/src/unused_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -1,5 +1,5 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor}; +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; +use rustc_hir::intravisit::{walk_body, walk_expr, walk_fn, FnKind, Visitor}; use rustc_hir::{Body, Expr, ExprKind, FnDecl, YieldSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; @@ -42,6 +42,10 @@ declare_lint_pass!(UnusedAsync => [UNUSED_ASYNC]); struct AsyncFnVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, found_await: bool, + /// Also keep track of `await`s in nested async blocks so we can mention + /// it in a note + found_await_in_async_block: bool, + async_depth: usize, } impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> { @@ -49,7 +53,11 @@ impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { if let ExprKind::Yield(_, YieldSource::Await { .. }) = ex.kind { - self.found_await = true; + if self.async_depth == 1 { + self.found_await = true; + } else { + self.found_await_in_async_block = true; + } } walk_expr(self, ex); } @@ -57,6 +65,20 @@ impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> { fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } + + fn visit_body(&mut self, b: &'tcx Body<'tcx>) { + let is_async_block = matches!(b.generator_kind, Some(rustc_hir::GeneratorKind::Async(_))); + + if is_async_block { + self.async_depth += 1; + } + + walk_body(self, b); + + if is_async_block { + self.async_depth -= 1; + } + } } impl<'tcx> LateLintPass<'tcx> for UnusedAsync { @@ -70,9 +92,27 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { def_id: LocalDefId, ) { if !span.from_expansion() && fn_kind.asyncness().is_async() { - let mut visitor = AsyncFnVisitor { cx, found_await: false }; + let mut visitor = AsyncFnVisitor { + cx, + found_await: false, + async_depth: 0, + found_await_in_async_block: false, + }; walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id); if !visitor.found_await { + span_lint_and_then( + cx, + UNUSED_ASYNC, + span, + "unused `async` for function with no await statements", + |diag| { + diag.help("consider removing the `async` from this function"); + + if visitor.found_await_in_async_block { + diag.note("`await` used in an async block, which does not require the enclosing function to be `async`"); + } + }, + ); span_lint_and_help( cx, UNUSED_ASYNC, diff --git a/tests/ui/unused_async.rs b/tests/ui/unused_async.rs index 4ca7f29b3..bfaa5dadf 100644 --- a/tests/ui/unused_async.rs +++ b/tests/ui/unused_async.rs @@ -3,6 +3,26 @@ use std::future::Future; use std::pin::Pin; +mod issue10800 { + #![allow(dead_code, unused_must_use, clippy::no_effect)] + + use std::future::ready; + + async fn async_block_await() { + async { + ready(()).await; + }; + } + + async fn normal_block_await() { + { + { + ready(()).await; + } + } + } +} + async fn foo() -> i32 { 4 } diff --git a/tests/ui/unused_async.stderr b/tests/ui/unused_async.stderr index cff3eccbd..e39f9b20b 100644 --- a/tests/ui/unused_async.stderr +++ b/tests/ui/unused_async.stderr @@ -1,5 +1,19 @@ error: unused `async` for function with no await statements - --> $DIR/unused_async.rs:6:1 + --> $DIR/unused_async.rs:11:5 + | +LL | / async fn async_block_await() { +LL | | async { +LL | | ready(()).await; +LL | | }; +LL | | } + | |_____^ + | + = help: consider removing the `async` from this function + = note: `await` used in an async block, which does not require the enclosing function to be `async` + = note: `-D clippy::unused-async` implied by `-D warnings` + +error: unused `async` for function with no await statements + --> $DIR/unused_async.rs:26:1 | LL | / async fn foo() -> i32 { LL | | 4 @@ -7,10 +21,9 @@ LL | | } | |_^ | = help: consider removing the `async` from this function - = note: `-D clippy::unused-async` implied by `-D warnings` error: unused `async` for function with no await statements - --> $DIR/unused_async.rs:17:5 + --> $DIR/unused_async.rs:37:5 | LL | / async fn unused(&self) -> i32 { LL | | 1 @@ -19,5 +32,5 @@ LL | | } | = help: consider removing the `async` from this function -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors