Skip to content

Commit 3497086

Browse files
committed
Do not propose to remove async move if variables are captured by ref
1 parent f19db28 commit 3497086

File tree

4 files changed

+147
-6
lines changed

4 files changed

+147
-6
lines changed

clippy_lints/src/redundant_async_block.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ declare_clippy_lint! {
3232
/// ```
3333
#[clippy::version = "1.69.0"]
3434
pub REDUNDANT_ASYNC_BLOCK,
35-
complexity,
35+
nursery,
3636
"`async { future.await }` can be replaced by `future`"
3737
}
3838
declare_lint_pass!(RedundantAsyncBlock => [REDUNDANT_ASYNC_BLOCK]);
@@ -48,6 +48,11 @@ impl EarlyLintPass for RedundantAsyncBlock {
4848
!future.span.from_expansion() &&
4949
!await_in_expr(future)
5050
{
51+
if captures_value(last) {
52+
// If the async block captures variables then there is no equivalence.
53+
return;
54+
}
55+
5156
span_lint_and_sugg(
5257
cx,
5358
REDUNDANT_ASYNC_BLOCK,
@@ -82,3 +87,33 @@ impl<'ast> AstVisitor<'ast> for AwaitDetector {
8287
}
8388
}
8489
}
90+
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+
}
118+
}
119+
}

tests/ui/redundant_async_block.fixed

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#![allow(unused)]
44
#![warn(clippy::redundant_async_block)]
55

6+
use std::future::Future;
7+
68
async fn func1(n: usize) -> usize {
79
n + 1
810
}
@@ -62,3 +64,48 @@ fn main() {
6264
let fut = async_await_parameter_in_macro!(func2());
6365
let fut = async_await_in_macro!(std::convert::identity);
6466
}
67+
68+
#[allow(clippy::let_and_return)]
69+
fn capture_local() -> impl Future<Output = i32> {
70+
// Lint
71+
let fut = async { 17 };
72+
fut
73+
}
74+
75+
fn capture_local_closure(s: &str) -> impl Future<Output = &str> {
76+
let f = move || std::future::ready(s);
77+
// Do not lint: `f` would not live long enough
78+
async move { f().await }
79+
}
80+
81+
#[allow(clippy::let_and_return)]
82+
fn capture_arg(s: &str) -> impl Future<Output = &str> {
83+
// Lint
84+
let fut = async move { s };
85+
fut
86+
}
87+
88+
#[derive(Debug, Clone)]
89+
struct F {}
90+
91+
impl F {
92+
async fn run(&self) {}
93+
}
94+
95+
pub async fn run() {
96+
let f = F {};
97+
let c = f.clone();
98+
// Do not lint: `c` would not live long enough
99+
spawn(async move { c.run().await });
100+
let _f = f;
101+
}
102+
103+
fn spawn<F: Future + 'static>(_: F) {}
104+
105+
async fn work(_: &str) {}
106+
107+
fn capture() {
108+
let val = "Hello World".to_owned();
109+
// Do not lint: `val` would not live long enough
110+
spawn(async { work(&{ val }).await });
111+
}

tests/ui/redundant_async_block.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#![allow(unused)]
44
#![warn(clippy::redundant_async_block)]
55

6+
use std::future::Future;
7+
68
async fn func1(n: usize) -> usize {
79
n + 1
810
}
@@ -62,3 +64,48 @@ fn main() {
6264
let fut = async_await_parameter_in_macro!(func2());
6365
let fut = async_await_in_macro!(std::convert::identity);
6466
}
67+
68+
#[allow(clippy::let_and_return)]
69+
fn capture_local() -> impl Future<Output = i32> {
70+
// Lint
71+
let fut = async { 17 };
72+
async move { fut.await }
73+
}
74+
75+
fn capture_local_closure(s: &str) -> impl Future<Output = &str> {
76+
let f = move || std::future::ready(s);
77+
// Do not lint: `f` would not live long enough
78+
async move { f().await }
79+
}
80+
81+
#[allow(clippy::let_and_return)]
82+
fn capture_arg(s: &str) -> impl Future<Output = &str> {
83+
// Lint
84+
let fut = async move { s };
85+
async move { fut.await }
86+
}
87+
88+
#[derive(Debug, Clone)]
89+
struct F {}
90+
91+
impl F {
92+
async fn run(&self) {}
93+
}
94+
95+
pub async fn run() {
96+
let f = F {};
97+
let c = f.clone();
98+
// Do not lint: `c` would not live long enough
99+
spawn(async move { c.run().await });
100+
let _f = f;
101+
}
102+
103+
fn spawn<F: Future + 'static>(_: F) {}
104+
105+
async fn work(_: &str) {}
106+
107+
fn capture() {
108+
let val = "Hello World".to_owned();
109+
// Do not lint: `val` would not live long enough
110+
spawn(async { work(&{ val }).await });
111+
}

tests/ui/redundant_async_block.stderr

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,40 @@
11
error: this async expression only awaits a single future
2-
--> $DIR/redundant_async_block.rs:13:13
2+
--> $DIR/redundant_async_block.rs:15:13
33
|
44
LL | let x = async { f.await };
55
| ^^^^^^^^^^^^^^^^^ help: you can reduce it to: `f`
66
|
77
= note: `-D clippy::redundant-async-block` implied by `-D warnings`
88

99
error: this async expression only awaits a single future
10-
--> $DIR/redundant_async_block.rs:46:16
10+
--> $DIR/redundant_async_block.rs:48:16
1111
|
1212
LL | let fut2 = async { fut1.await };
1313
| ^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut1`
1414

1515
error: this async expression only awaits a single future
16-
--> $DIR/redundant_async_block.rs:49:16
16+
--> $DIR/redundant_async_block.rs:51:16
1717
|
1818
LL | let fut2 = async move { fut1.await };
1919
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut1`
2020

2121
error: this async expression only awaits a single future
22-
--> $DIR/redundant_async_block.rs:51:15
22+
--> $DIR/redundant_async_block.rs:53:15
2323
|
2424
LL | let fut = async { async { 42 }.await };
2525
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `async { 42 }`
2626

27-
error: aborting due to 4 previous errors
27+
error: this async expression only awaits a single future
28+
--> $DIR/redundant_async_block.rs:72:5
29+
|
30+
LL | async move { fut.await }
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut`
32+
33+
error: this async expression only awaits a single future
34+
--> $DIR/redundant_async_block.rs:85:5
35+
|
36+
LL | async move { fut.await }
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut`
38+
39+
error: aborting due to 6 previous errors
2840

0 commit comments

Comments
 (0)