1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
use rustc_hir as hir;
use rustc_macros::{LintDiagnostic, 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
/// #![feature(async_closure)]
///
/// 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",
@feature_gate = 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(LintDiagnostic)]
#[diag(lint_closure_returning_async_block)]
struct ClosureReturningAsyncBlock {
#[label]
async_decl_span: Span,
#[subdiagnostic]
sugg: AsyncClosureSugg,
}
#[derive(Subdiagnostic)]
#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")]
struct AsyncClosureSugg {
#[suggestion_part(code = "")]
deletion_span: Span,
#[suggestion_part(code = "async ")]
insertion_span: Span,
}