| use clippy_utils::diagnostics::span_lint_hir_and_then; | |
| use clippy_utils::is_expr_async_block; | |
| use clippy_utils::source::walk_span_to_context; | |
| use clippy_utils::sugg::Sugg; | |
| use clippy_utils::ty::implements_trait; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::{ | |
| Block, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath, | |
| }; | |
| use rustc_lint::{LateContext, LateLintPass}; | |
| use rustc_session::declare_lint_pass; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for async blocks that yield values of types | |
| /// that can themselves be awaited. | |
| /// | |
| /// ### Why is this bad? | |
| /// An await is likely missing. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// async fn foo() {} | |
| /// | |
| /// fn bar() { | |
| /// let x = async { | |
| /// foo() | |
| /// }; | |
| /// } | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```no_run | |
| /// async fn foo() {} | |
| /// | |
| /// fn bar() { | |
| /// let x = async { | |
| /// foo().await | |
| /// }; | |
| /// } | |
| /// ``` | |
| #[clippy::version = "1.48.0"] | |
| pub ASYNC_YIELDS_ASYNC, | |
| correctness, | |
| "async blocks that return a type that can be awaited" | |
| } | |
| declare_lint_pass!(AsyncYieldsAsync => [ASYNC_YIELDS_ASYNC]); | |
| impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync { | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { | |
| let ExprKind::Closure(Closure { | |
| kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, kind)), | |
| body: body_id, | |
| .. | |
| }) = expr.kind | |
| else { | |
| return; | |
| }; | |
| let body_expr = match kind { | |
| CoroutineSource::Fn => { | |
| // For functions, with explicitly defined types, don't warn. | |
| // XXXkhuey maybe we should? | |
| return; | |
| }, | |
| CoroutineSource::Block => cx.tcx.hir_body(*body_id).value, | |
| CoroutineSource::Closure => { | |
| // Like `async fn`, async closures are wrapped in an additional block | |
| // to move all of the closure's arguments into the future. | |
| let async_closure_body = cx.tcx.hir_body(*body_id).value; | |
| let ExprKind::Block(block, _) = async_closure_body.kind else { | |
| return; | |
| }; | |
| let Some(block_expr) = block.expr else { | |
| return; | |
| }; | |
| let ExprKind::DropTemps(body_expr) = block_expr.kind else { | |
| return; | |
| }; | |
| body_expr | |
| }, | |
| }; | |
| let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() else { | |
| return; | |
| }; | |
| let typeck_results = cx.tcx.typeck_body(*body_id); | |
| let expr_ty = typeck_results.expr_ty(body_expr); | |
| if implements_trait(cx, expr_ty, future_trait_def_id, &[]) { | |
| let (return_expr, return_expr_span) = match &body_expr.kind { | |
| ExprKind::Block(Block { expr: Some(e), .. }, _) => (*e, e.span), | |
| ExprKind::Path(QPath::Resolved(_, path)) => (body_expr, path.span), | |
| _ => return, | |
| }; | |
| let return_expr_span = walk_span_to_context(return_expr_span, expr.span.ctxt()).unwrap_or(return_expr_span); | |
| let mut applicability = Applicability::MaybeIncorrect; | |
| let mut return_expr_snip = | |
| Sugg::hir_with_context(cx, return_expr, expr.span.ctxt(), "..", &mut applicability); | |
| if !is_expr_async_block(return_expr) { | |
| return_expr_snip = return_expr_snip.maybe_paren(); | |
| } | |
| span_lint_hir_and_then( | |
| cx, | |
| ASYNC_YIELDS_ASYNC, | |
| body_expr.hir_id, | |
| return_expr_span, | |
| "an async construct yields a type which is itself awaitable", | |
| |db| { | |
| db.span_label(body_expr.span, "outer async construct"); | |
| db.span_label(return_expr_span, "awaitable value not awaited"); | |
| db.span_suggestion( | |
| return_expr_span, | |
| "consider awaiting this value", | |
| format!("{return_expr_snip}.await"), | |
| applicability, | |
| ); | |
| }, | |
| ); | |
| } | |
| } | |
| } |