Skip to content

Commit deb5587

Browse files
Basic lint detecting closure-returning-async-block
1 parent 6f3ad0a commit deb5587

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

compiler/rustc_lint/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ lint_cfg_attr_no_attributes =
187187
188188
lint_check_name_unknown_tool = unknown lint tool: `{$tool_name}`
189189
190+
lint_closure_returning_async_block = closure returning async block can be made into an async closure
191+
.label = this async block can be removed, and the closure can be turned into an async closure
192+
190193
lint_command_line_source = `forbid` lint level was set on command line
191194
192195
lint_confusable_identifier_pair = found both `{$existing_sym}` and `{$sym}` as identifiers, which look alike
+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use rustc_hir as hir;
2+
use rustc_macros::LintDiagnostic;
3+
use rustc_session::{declare_lint, declare_lint_pass};
4+
use rustc_span::Span;
5+
6+
use crate::{LateContext, LateLintPass};
7+
8+
declare_lint! {
9+
/// The `closure_returning_async_block` lint detects cases where users
10+
/// write a closure that returns an async block.
11+
///
12+
/// ### Example
13+
///
14+
/// ```
15+
/// #![warn(closure_returning_async_block)]
16+
/// let c = |x: &str| async {};
17+
/// ```
18+
///
19+
/// {{produces}}
20+
///
21+
/// ### Explanation
22+
///
23+
/// Using an async closure is preferable over a closure that returns an
24+
/// async block, since async closures are less restrictive in how its
25+
/// captures are allowed to be used.
26+
///
27+
/// For example, this code does not work with a closure returning an async
28+
/// block:
29+
///
30+
/// ```rust,compile_fail
31+
/// async fn callback(x: &str) {}
32+
///
33+
/// let captured_str = String::new();
34+
/// let c = move || async {
35+
/// callback(&captured_str).await;
36+
/// };
37+
/// ```
38+
///
39+
/// But it does work with async closures:
40+
///
41+
/// ```rust
42+
/// #![feature(async_closure)]
43+
///
44+
/// async fn callback(x: &str) {}
45+
///
46+
/// let captured_str = String::new();
47+
/// let c = async move || {
48+
/// callback(&captured_str).await;
49+
/// };
50+
/// ```
51+
pub CLOSURE_RETURNING_ASYNC_BLOCK,
52+
Allow,
53+
"closure that returns `async {}` could be rewritten as an async closure",
54+
@feature_gate = async_closure;
55+
}
56+
57+
declare_lint_pass!(
58+
/// Lint for potential usages of async closures and async fn trait bounds.
59+
AsyncClosureUsage => [CLOSURE_RETURNING_ASYNC_BLOCK]
60+
);
61+
62+
impl<'tcx> LateLintPass<'tcx> for AsyncClosureUsage {
63+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
64+
let hir::ExprKind::Closure(&hir::Closure {
65+
body,
66+
kind: hir::ClosureKind::Closure,
67+
fn_decl_span,
68+
..
69+
}) = expr.kind
70+
else {
71+
return;
72+
};
73+
74+
let mut body = cx.tcx.hir().body(body).value;
75+
76+
// Only peel blocks that have no expressions.
77+
while let hir::ExprKind::Block(&hir::Block { stmts: [], expr: Some(tail), .. }, None) =
78+
body.kind
79+
{
80+
body = tail;
81+
}
82+
83+
let hir::ExprKind::Closure(&hir::Closure {
84+
kind:
85+
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
86+
hir::CoroutineDesugaring::Async,
87+
hir::CoroutineSource::Block,
88+
)),
89+
fn_decl_span: async_decl_span,
90+
..
91+
}) = body.kind
92+
else {
93+
return;
94+
};
95+
96+
cx.tcx.emit_node_span_lint(
97+
CLOSURE_RETURNING_ASYNC_BLOCK,
98+
expr.hir_id,
99+
fn_decl_span,
100+
ClosureReturningAsyncBlock { async_decl_span },
101+
);
102+
}
103+
}
104+
105+
#[derive(LintDiagnostic)]
106+
#[diag(lint_closure_returning_async_block)]
107+
struct ClosureReturningAsyncBlock {
108+
#[label]
109+
async_decl_span: Span,
110+
}

compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#![feature(trait_upcasting)]
4242
// tidy-alphabetical-end
4343

44+
mod async_closures;
4445
mod async_fn_in_trait;
4546
pub mod builtin;
4647
mod context;
@@ -86,6 +87,7 @@ use rustc_hir::def_id::LocalModDefId;
8687
use rustc_middle::query::Providers;
8788
use rustc_middle::ty::TyCtxt;
8889

90+
use async_closures::AsyncClosureUsage;
8991
use async_fn_in_trait::AsyncFnInTrait;
9092
use builtin::*;
9193
use deref_into_dyn_supertrait::*;
@@ -227,6 +229,7 @@ late_lint_methods!(
227229
MapUnitFn: MapUnitFn,
228230
MissingDebugImplementations: MissingDebugImplementations,
229231
MissingDoc: MissingDoc,
232+
AsyncClosureUsage: AsyncClosureUsage,
230233
AsyncFnInTrait: AsyncFnInTrait,
231234
NonLocalDefinitions: NonLocalDefinitions::default(),
232235
ImplTraitOvercaptures: ImplTraitOvercaptures,

0 commit comments

Comments
 (0)