| use rustc_hir as hir; | |
| use rustc_macros::{Diagnostic, Subdiagnostic}; | |
| use rustc_session::{declare_lint, declare_lint_pass}; | |
| use rustc_span::Span; | |
| use crate::{LateContext, LateLintPass}; | |
| declare_lint! { | |
| /// The `closure_returning_async_block` lint detects cases where users | |
| /// write a closure that returns an async block. | |
| /// | |
| /// ### Example | |
| /// | |
| /// ```rust | |
| /// #![warn(closure_returning_async_block)] | |
| /// let c = |x: &str| async {}; | |
| /// ``` | |
| /// | |
| /// {{produces}} | |
| /// | |
| /// ### Explanation | |
| /// | |
| /// Using an async closure is preferable over a closure that returns an | |
| /// async block, since async closures are less restrictive in how its | |
| /// captures are allowed to be used. | |
| /// | |
| /// For example, this code does not work with a closure returning an async | |
| /// block: | |
| /// | |
| /// ```rust,compile_fail | |
| /// async fn callback(x: &str) {} | |
| /// | |
| /// let captured_str = String::new(); | |
| /// let c = move || async { | |
| /// callback(&captured_str).await; | |
| /// }; | |
| /// ``` | |
| /// | |
| /// But it does work with async closures: | |
| /// | |
| /// ```rust | |
| /// async fn callback(x: &str) {} | |
| /// | |
| /// let captured_str = String::new(); | |
| /// let c = async move || { | |
| /// callback(&captured_str).await; | |
| /// }; | |
| /// ``` | |
| pub CLOSURE_RETURNING_ASYNC_BLOCK, | |
| Allow, | |
| "closure that returns `async {}` could be rewritten as an async closure", | |
| } | |
| declare_lint_pass!( | |
| /// Lint for potential usages of async closures and async fn trait bounds. | |
| AsyncClosureUsage => [CLOSURE_RETURNING_ASYNC_BLOCK] | |
| ); | |
| impl<'tcx> LateLintPass<'tcx> for AsyncClosureUsage { | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { | |
| let hir::ExprKind::Closure(&hir::Closure { | |
| body, | |
| kind: hir::ClosureKind::Closure, | |
| fn_decl_span, | |
| .. | |
| }) = expr.kind | |
| else { | |
| return; | |
| }; | |
| let mut body = cx.tcx.hir_body(body).value; | |
| // Only peel blocks that have no expressions. | |
| while let hir::ExprKind::Block(&hir::Block { stmts: [], expr: Some(tail), .. }, None) = | |
| body.kind | |
| { | |
| body = tail; | |
| } | |
| let hir::ExprKind::Closure(&hir::Closure { | |
| kind: | |
| hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared( | |
| hir::CoroutineDesugaring::Async, | |
| hir::CoroutineSource::Block, | |
| )), | |
| fn_decl_span: async_decl_span, | |
| .. | |
| }) = body.kind | |
| else { | |
| return; | |
| }; | |
| let deletion_span = cx.tcx.sess.source_map().span_extend_while_whitespace(async_decl_span); | |
| cx.tcx.emit_node_span_lint( | |
| CLOSURE_RETURNING_ASYNC_BLOCK, | |
| expr.hir_id, | |
| fn_decl_span, | |
| ClosureReturningAsyncBlock { | |
| async_decl_span, | |
| sugg: AsyncClosureSugg { | |
| deletion_span, | |
| insertion_span: fn_decl_span.shrink_to_lo(), | |
| }, | |
| }, | |
| ); | |
| } | |
| } | |
| #[derive(Diagnostic)] | |
| #[diag("closure returning async block can be made into an async closure")] | |
| struct ClosureReturningAsyncBlock { | |
| #[label( | |
| "this async block can be removed, and the closure can be turned into an async closure" | |
| )] | |
| async_decl_span: Span, | |
| #[subdiagnostic] | |
| sugg: AsyncClosureSugg, | |
| } | |
| #[derive(Subdiagnostic)] | |
| #[multipart_suggestion("turn this into an async closure", applicability = "maybe-incorrect")] | |
| struct AsyncClosureSugg { | |
| #[suggestion_part(code = "")] | |
| deletion_span: Span, | |
| #[suggestion_part(code = "async ")] | |
| insertion_span: Span, | |
| } |