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+ } ;
49use 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 } ;
613use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
714
815declare_clippy_lint ! {
@@ -14,106 +21,88 @@ declare_clippy_lint! {
1421 ///
1522 /// ### Example
1623 /// ```rust
17- /// async fn f() -> i32 {
18- /// 1 + 2
19- /// }
20- ///
24+ /// let f = async {
25+ /// 1 + 2
26+ /// };
2127 /// let fut = async {
22- /// f() .await
28+ /// f.await
2329 /// };
2430 /// ```
2531 /// Use instead:
2632 /// ```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;
3237 /// ```
3338 #[ clippy:: version = "1.69.0" ]
3439 pub REDUNDANT_ASYNC_BLOCK ,
35- nursery ,
40+ complexity ,
3641 "`async { future.await }` can be replaced by `future`"
3742}
3843declare_lint_pass ! ( RedundantAsyncBlock => [ REDUNDANT_ASYNC_BLOCK ] ) ;
3944
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 ( ) )
5056 {
51- if captures_value ( last) {
52- // If the async block captures variables then there is no equivalence.
53- return ;
54- }
55-
5657 span_lint_and_sugg (
5758 cx,
5859 REDUNDANT_ASYNC_BLOCK ,
59- expr . span ,
60+ span,
6061 "this async expression only awaits a single future" ,
6162 "you can reduce it to" ,
62- snippet ( cx, future . span , ".." ) . into_owned ( ) ,
63+ snippet ( cx, shortened_span , ".." ) . into_owned ( ) ,
6364 Applicability :: MachineApplicable ,
6465 ) ;
6566 }
6667 }
6768}
6869
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
8891 }
8992}
9093
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
118107 }
119108}
0 commit comments