forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathasync_closures.rs
126 lines (115 loc) · 3.52 KB
/
async_closures.rs
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
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
/// 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(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,
}