Skip to content

Commit d5429ea

Browse files
committed
Add new redundant_async_block lint
1 parent 5eefbb3 commit d5429ea

13 files changed

+260
-12
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4836,6 +4836,7 @@ Released 2018-09-13
48364836
[`read_zero_byte_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_zero_byte_vec
48374837
[`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl
48384838
[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
4839+
[`redundant_async_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_async_block
48394840
[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
48404841
[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
48414842
[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
519519
crate::ranges::REVERSED_EMPTY_RANGES_INFO,
520520
crate::rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT_INFO,
521521
crate::read_zero_byte_vec::READ_ZERO_BYTE_VEC_INFO,
522+
crate::redundant_async_block::REDUNDANT_ASYNC_BLOCK_INFO,
522523
crate::redundant_clone::REDUNDANT_CLONE_INFO,
523524
crate::redundant_closure_call::REDUNDANT_CLOSURE_CALL_INFO,
524525
crate::redundant_else::REDUNDANT_ELSE_INFO,

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ mod question_mark_used;
251251
mod ranges;
252252
mod rc_clone_in_vec_init;
253253
mod read_zero_byte_vec;
254+
mod redundant_async_block;
254255
mod redundant_clone;
255256
mod redundant_closure_call;
256257
mod redundant_else;
@@ -928,6 +929,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
928929
store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi));
929930
store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead));
930931
store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage));
932+
store.register_early_pass(|| Box::new(redundant_async_block::RedundantAsyncBlock));
931933
// add lints here, do not remove this comment, it's used in `new_lint`
932934
}
933935

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet};
2+
use rustc_ast::ast::*;
3+
use rustc_ast::visit::Visitor as AstVisitor;
4+
use rustc_errors::Applicability;
5+
use rustc_lint::{EarlyContext, EarlyLintPass};
6+
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
8+
declare_clippy_lint! {
9+
/// ### What it does
10+
/// Checks for `async` block that only returns `await` on a future.
11+
///
12+
/// ### Why is this bad?
13+
/// It is simpler and more efficient to use the future directly.
14+
///
15+
/// ### Example
16+
/// ```rust
17+
/// async fn f() -> i32 {
18+
/// 1 + 2
19+
/// }
20+
///
21+
/// let fut = async {
22+
/// f().await
23+
/// };
24+
/// ```
25+
/// Use instead:
26+
/// ```rust
27+
/// async fn f() -> i32 {
28+
/// 1 + 2
29+
/// }
30+
///
31+
/// let fut = f();
32+
/// ```
33+
#[clippy::version = "1.69.0"]
34+
pub REDUNDANT_ASYNC_BLOCK,
35+
complexity,
36+
"`async { future.await }` can be replaced by `future`"
37+
}
38+
declare_lint_pass!(RedundantAsyncBlock => [REDUNDANT_ASYNC_BLOCK]);
39+
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)
50+
{
51+
span_lint_and_sugg(
52+
cx,
53+
REDUNDANT_ASYNC_BLOCK,
54+
expr.span,
55+
"this async expression only awaits a single future",
56+
"you can reduce it to",
57+
snippet(cx, future.span, "..").into_owned(),
58+
Applicability::MachineApplicable,
59+
);
60+
}
61+
}
62+
}
63+
64+
/// Check whether an expression contains `.await`
65+
fn await_in_expr(expr: &Expr) -> bool {
66+
let mut detector = AwaitDetector::default();
67+
detector.visit_expr(expr);
68+
detector.await_found
69+
}
70+
71+
#[derive(Default)]
72+
struct AwaitDetector {
73+
await_found: bool,
74+
}
75+
76+
impl<'ast> AstVisitor<'ast> for AwaitDetector {
77+
fn visit_expr(&mut self, ex: &'ast Expr) {
78+
match (&ex.kind, self.await_found) {
79+
(ExprKind::Await(_), _) => self.await_found = true,
80+
(_, false) => rustc_ast::visit::walk_expr(self, ex),
81+
_ => (),
82+
}
83+
}
84+
}

tests/ui/async_yields_async.fixed

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![feature(lint_reasons)]
33
#![feature(async_closure)]
44
#![warn(clippy::async_yields_async)]
5+
#![allow(clippy::redundant_async_block)]
56

67
use core::future::Future;
78
use core::pin::Pin;

tests/ui/async_yields_async.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![feature(lint_reasons)]
33
#![feature(async_closure)]
44
#![warn(clippy::async_yields_async)]
5+
#![allow(clippy::redundant_async_block)]
56

67
use core::future::Future;
78
use core::pin::Pin;

tests/ui/async_yields_async.stderr

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: an async construct yields a type which is itself awaitable
2-
--> $DIR/async_yields_async.rs:39:9
2+
--> $DIR/async_yields_async.rs:40:9
33
|
44
LL | let _h = async {
55
| _____________________-
@@ -19,7 +19,7 @@ LL + }.await
1919
|
2020

2121
error: an async construct yields a type which is itself awaitable
22-
--> $DIR/async_yields_async.rs:44:9
22+
--> $DIR/async_yields_async.rs:45:9
2323
|
2424
LL | let _i = async {
2525
| ____________________-
@@ -32,7 +32,7 @@ LL | | };
3232
| |_____- outer async construct
3333

3434
error: an async construct yields a type which is itself awaitable
35-
--> $DIR/async_yields_async.rs:50:9
35+
--> $DIR/async_yields_async.rs:51:9
3636
|
3737
LL | let _j = async || {
3838
| ________________________-
@@ -51,7 +51,7 @@ LL + }.await
5151
|
5252

5353
error: an async construct yields a type which is itself awaitable
54-
--> $DIR/async_yields_async.rs:55:9
54+
--> $DIR/async_yields_async.rs:56:9
5555
|
5656
LL | let _k = async || {
5757
| _______________________-
@@ -64,7 +64,7 @@ LL | | };
6464
| |_____- outer async construct
6565

6666
error: an async construct yields a type which is itself awaitable
67-
--> $DIR/async_yields_async.rs:57:23
67+
--> $DIR/async_yields_async.rs:58:23
6868
|
6969
LL | let _l = async || CustomFutureType;
7070
| ^^^^^^^^^^^^^^^^
@@ -74,7 +74,7 @@ LL | let _l = async || CustomFutureType;
7474
| help: consider awaiting this value: `CustomFutureType.await`
7575

7676
error: an async construct yields a type which is itself awaitable
77-
--> $DIR/async_yields_async.rs:63:9
77+
--> $DIR/async_yields_async.rs:64:9
7878
|
7979
LL | let _m = async || {
8080
| _______________________-

tests/ui/redundant_async_block.fixed

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// run-rustfix
2+
3+
#![allow(unused)]
4+
#![warn(clippy::redundant_async_block)]
5+
6+
async fn func1(n: usize) -> usize {
7+
n + 1
8+
}
9+
10+
async fn func2() -> String {
11+
let s = String::from("some string");
12+
let f = async { (*s).to_owned() };
13+
let x = f;
14+
x.await
15+
}
16+
17+
macro_rules! await_in_macro {
18+
($e:expr) => {
19+
std::convert::identity($e).await
20+
};
21+
}
22+
23+
async fn func3(n: usize) -> usize {
24+
// Do not lint (suggestion would be `std::convert::identity(func1(n))`
25+
// which copies code from inside the macro)
26+
async move { await_in_macro!(func1(n)) }.await
27+
}
28+
29+
// This macro should never be linted as `$e` might contain `.await`
30+
macro_rules! async_await_parameter_in_macro {
31+
($e:expr) => {
32+
async { $e.await }
33+
};
34+
}
35+
36+
// MISSED OPPORTUNITY: this macro could be linted as the `async` block does not
37+
// contain code coming from the parameters
38+
macro_rules! async_await_in_macro {
39+
($f:expr) => {
40+
($f)(async { func2().await })
41+
};
42+
}
43+
44+
fn main() {
45+
let fut1 = async { 17 };
46+
let fut2 = fut1;
47+
48+
let fut1 = async { 25 };
49+
let fut2 = fut1;
50+
51+
let fut = async { 42 };
52+
53+
// Do not lint: not a single expression
54+
let fut = async {
55+
func1(10).await;
56+
func2().await
57+
};
58+
59+
// Do not lint: expression contains `.await`
60+
let fut = async { func1(func2().await.len()).await };
61+
62+
let fut = async_await_parameter_in_macro!(func2());
63+
let fut = async_await_in_macro!(std::convert::identity);
64+
}

tests/ui/redundant_async_block.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// run-rustfix
2+
3+
#![allow(unused)]
4+
#![warn(clippy::redundant_async_block)]
5+
6+
async fn func1(n: usize) -> usize {
7+
n + 1
8+
}
9+
10+
async fn func2() -> String {
11+
let s = String::from("some string");
12+
let f = async { (*s).to_owned() };
13+
let x = async { f.await };
14+
x.await
15+
}
16+
17+
macro_rules! await_in_macro {
18+
($e:expr) => {
19+
std::convert::identity($e).await
20+
};
21+
}
22+
23+
async fn func3(n: usize) -> usize {
24+
// Do not lint (suggestion would be `std::convert::identity(func1(n))`
25+
// which copies code from inside the macro)
26+
async move { await_in_macro!(func1(n)) }.await
27+
}
28+
29+
// This macro should never be linted as `$e` might contain `.await`
30+
macro_rules! async_await_parameter_in_macro {
31+
($e:expr) => {
32+
async { $e.await }
33+
};
34+
}
35+
36+
// MISSED OPPORTUNITY: this macro could be linted as the `async` block does not
37+
// contain code coming from the parameters
38+
macro_rules! async_await_in_macro {
39+
($f:expr) => {
40+
($f)(async { func2().await })
41+
};
42+
}
43+
44+
fn main() {
45+
let fut1 = async { 17 };
46+
let fut2 = async { fut1.await };
47+
48+
let fut1 = async { 25 };
49+
let fut2 = async move { fut1.await };
50+
51+
let fut = async { async { 42 }.await };
52+
53+
// Do not lint: not a single expression
54+
let fut = async {
55+
func1(10).await;
56+
func2().await
57+
};
58+
59+
// Do not lint: expression contains `.await`
60+
let fut = async { func1(func2().await.len()).await };
61+
62+
let fut = async_await_parameter_in_macro!(func2());
63+
let fut = async_await_in_macro!(std::convert::identity);
64+
}

tests/ui/redundant_async_block.stderr

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: this async expression only awaits a single future
2+
--> $DIR/redundant_async_block.rs:13:13
3+
|
4+
LL | let x = async { f.await };
5+
| ^^^^^^^^^^^^^^^^^ help: you can reduce it to: `f`
6+
|
7+
= note: `-D clippy::redundant-async-block` implied by `-D warnings`
8+
9+
error: this async expression only awaits a single future
10+
--> $DIR/redundant_async_block.rs:46:16
11+
|
12+
LL | let fut2 = async { fut1.await };
13+
| ^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut1`
14+
15+
error: this async expression only awaits a single future
16+
--> $DIR/redundant_async_block.rs:49:16
17+
|
18+
LL | let fut2 = async move { fut1.await };
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `fut1`
20+
21+
error: this async expression only awaits a single future
22+
--> $DIR/redundant_async_block.rs:51:15
23+
|
24+
LL | let fut = async { async { 42 }.await };
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `async { 42 }`
26+
27+
error: aborting due to 4 previous errors
28+

tests/ui/redundant_closure_call_fixable.fixed

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#![feature(async_closure)]
44
#![warn(clippy::redundant_closure_call)]
5+
#![allow(clippy::redundant_async_block)]
56
#![allow(unused)]
67

78
async fn something() -> u32 {

tests/ui/redundant_closure_call_fixable.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#![feature(async_closure)]
44
#![warn(clippy::redundant_closure_call)]
5+
#![allow(clippy::redundant_async_block)]
56
#![allow(unused)]
67

78
async fn something() -> u32 {

0 commit comments

Comments
 (0)