mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-03 13:37:37 +00:00
130 lines
3.6 KiB
Rust
130 lines
3.6 KiB
Rust
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,
|
|
}
|