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,
}