1
- use clippy_utils:: { diagnostics:: span_lint_and_sugg, source:: snippet} ;
2
- use rustc_ast:: ast:: { Expr , ExprKind , Stmt , StmtKind } ;
3
- use rustc_ast:: visit:: Visitor as AstVisitor ;
1
+ use std:: ops:: ControlFlow ;
2
+
3
+ use clippy_utils:: {
4
+ diagnostics:: span_lint_and_sugg,
5
+ peel_blocks,
6
+ source:: { snippet, walk_span_to_context} ,
7
+ visitors:: for_each_expr,
8
+ } ;
4
9
use rustc_errors:: Applicability ;
5
- use rustc_lint:: { EarlyContext , EarlyLintPass } ;
10
+ use rustc_hir:: { AsyncGeneratorKind , Closure , Expr , ExprKind , GeneratorKind , MatchSource } ;
11
+ use rustc_lint:: { LateContext , LateLintPass } ;
12
+ use rustc_middle:: { lint:: in_external_macro, ty:: UpvarCapture } ;
6
13
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
7
14
8
15
declare_clippy_lint ! {
@@ -14,106 +21,88 @@ declare_clippy_lint! {
14
21
///
15
22
/// ### Example
16
23
/// ```rust
17
- /// async fn f() -> i32 {
18
- /// 1 + 2
19
- /// }
20
- ///
24
+ /// let f = async {
25
+ /// 1 + 2
26
+ /// };
21
27
/// let fut = async {
22
- /// f() .await
28
+ /// f.await
23
29
/// };
24
30
/// ```
25
31
/// Use instead:
26
32
/// ```rust
27
- /// async fn f() -> i32 {
28
- /// 1 + 2
29
- /// }
30
- ///
31
- /// let fut = f();
33
+ /// let f = async {
34
+ /// 1 + 2
35
+ /// };
36
+ /// let fut = f;
32
37
/// ```
33
38
#[ clippy:: version = "1.69.0" ]
34
39
pub REDUNDANT_ASYNC_BLOCK ,
35
- nursery ,
40
+ complexity ,
36
41
"`async { future.await }` can be replaced by `future`"
37
42
}
38
43
declare_lint_pass ! ( RedundantAsyncBlock => [ REDUNDANT_ASYNC_BLOCK ] ) ;
39
44
40
- impl EarlyLintPass for RedundantAsyncBlock {
41
- fn check_expr ( & mut self , cx : & EarlyContext < ' _ > , expr : & Expr ) {
42
- if expr. span . from_expansion ( ) {
43
- return ;
44
- }
45
- if let ExprKind :: Async ( _, _, block) = & expr. kind && block. stmts . len ( ) == 1 &&
46
- let Some ( Stmt { kind : StmtKind :: Expr ( last) , .. } ) = block. stmts . last ( ) &&
47
- let ExprKind :: Await ( future) = & last. kind &&
48
- !future. span . from_expansion ( ) &&
49
- !await_in_expr ( future)
45
+ impl < ' tcx > LateLintPass < ' tcx > for RedundantAsyncBlock {
46
+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
47
+ let span = expr. span ;
48
+ if !in_external_macro ( cx. tcx . sess , span) &&
49
+ let Some ( body_expr) = desugar_async_block ( cx, expr) &&
50
+ let Some ( expr) = desugar_await ( peel_blocks ( body_expr) ) &&
51
+ // The await prefix must not come from a macro as its content could change in the future.
52
+ expr. span . ctxt ( ) == body_expr. span . ctxt ( ) &&
53
+ // An async block does not have immediate side-effects from a `.await` point-of-view.
54
+ ( !expr. can_have_side_effects ( ) || desugar_async_block ( cx, expr) . is_some ( ) ) &&
55
+ let Some ( shortened_span) = walk_span_to_context ( expr. span , span. ctxt ( ) )
50
56
{
51
- if captures_value ( last) {
52
- // If the async block captures variables then there is no equivalence.
53
- return ;
54
- }
55
-
56
57
span_lint_and_sugg (
57
58
cx,
58
59
REDUNDANT_ASYNC_BLOCK ,
59
- expr . span ,
60
+ span,
60
61
"this async expression only awaits a single future" ,
61
62
"you can reduce it to" ,
62
- snippet ( cx, future . span , ".." ) . into_owned ( ) ,
63
+ snippet ( cx, shortened_span , ".." ) . into_owned ( ) ,
63
64
Applicability :: MachineApplicable ,
64
65
) ;
65
66
}
66
67
}
67
68
}
68
69
69
- /// Check whether an expression contains `.await`
70
- fn await_in_expr ( expr : & Expr ) -> bool {
71
- let mut detector = AwaitDetector :: default ( ) ;
72
- detector. visit_expr ( expr) ;
73
- detector. await_found
74
- }
75
-
76
- #[ derive( Default ) ]
77
- struct AwaitDetector {
78
- await_found : bool ,
79
- }
80
-
81
- impl < ' ast > AstVisitor < ' ast > for AwaitDetector {
82
- fn visit_expr ( & mut self , ex : & ' ast Expr ) {
83
- match ( & ex. kind , self . await_found ) {
84
- ( ExprKind :: Await ( _) , _) => self . await_found = true ,
85
- ( _, false ) => rustc_ast:: visit:: walk_expr ( self , ex) ,
86
- _ => ( ) ,
87
- }
70
+ /// If `expr` is a desugared `async` block, return the original expression if it does not capture
71
+ /// any variable by ref.
72
+ fn desugar_async_block < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) -> Option < & ' tcx Expr < ' tcx > > {
73
+ if let ExprKind :: Closure ( Closure { body, def_id, .. } ) = expr. kind &&
74
+ let body = cx. tcx . hir ( ) . body ( * body) &&
75
+ matches ! ( body. generator_kind, Some ( GeneratorKind :: Async ( AsyncGeneratorKind :: Block ) ) )
76
+ {
77
+ cx
78
+ . typeck_results ( )
79
+ . closure_min_captures
80
+ . get ( def_id)
81
+ . map_or ( true , |m| {
82
+ m. values ( ) . all ( |places| {
83
+ places
84
+ . iter ( )
85
+ . all ( |place| matches ! ( place. info. capture_kind, UpvarCapture :: ByValue ) )
86
+ } )
87
+ } )
88
+ . then_some ( body. value )
89
+ } else {
90
+ None
88
91
}
89
92
}
90
93
91
- /// Check whether an expression may have captured a local variable.
92
- /// This is done by looking for paths with only one segment, except as
93
- /// a prefix of `.await` since this would be captured by value.
94
- ///
95
- /// This function will sometimes return `true` even tough there are no
96
- /// captures happening: at the AST level, it is impossible to
97
- /// dinstinguish a function call from a call to a closure which comes
98
- /// from the local environment.
99
- fn captures_value ( expr : & Expr ) -> bool {
100
- let mut detector = CaptureDetector :: default ( ) ;
101
- detector. visit_expr ( expr) ;
102
- detector. capture_found
103
- }
104
-
105
- #[ derive( Default ) ]
106
- struct CaptureDetector {
107
- capture_found : bool ,
108
- }
109
-
110
- impl < ' ast > AstVisitor < ' ast > for CaptureDetector {
111
- fn visit_expr ( & mut self , ex : & ' ast Expr ) {
112
- match ( & ex. kind , self . capture_found ) {
113
- ( ExprKind :: Await ( fut) , _) if matches ! ( fut. kind, ExprKind :: Path ( ..) ) => ( ) ,
114
- ( ExprKind :: Path ( _, path) , _) if path. segments . len ( ) == 1 => self . capture_found = true ,
115
- ( _, false ) => rustc_ast:: visit:: walk_expr ( self , ex) ,
116
- _ => ( ) ,
117
- }
94
+ /// If `expr` is a desugared `.await`, return the original expression if it does not come from a
95
+ /// macro expansion.
96
+ fn desugar_await < ' tcx > ( expr : & ' tcx Expr < ' _ > ) -> Option < & ' tcx Expr < ' tcx > > {
97
+ if let ExprKind :: Match ( match_value, _, MatchSource :: AwaitDesugar ) = expr. kind &&
98
+ let ExprKind :: Call ( _, [ into_future_arg] ) = match_value. kind &&
99
+ let ctxt = expr. span . ctxt ( ) &&
100
+ for_each_expr ( into_future_arg, |e|
101
+ walk_span_to_context ( e. span , ctxt)
102
+ . map_or ( ControlFlow :: Break ( ( ) ) , |_| ControlFlow :: Continue ( ( ) ) ) ) . is_none ( )
103
+ {
104
+ Some ( into_future_arg)
105
+ } else {
106
+ None
118
107
}
119
108
}
0 commit comments