Skip to content

Commit 8334a58

Browse files
committed
Auto merge of rust-lang#5909 - khuey:async_yields_async, r=yaahc
Add a lint for an async block/closure that yields a type that is itself awaitable. This catches bugs of the form tokio::spawn(async move { let f = some_async_thing(); f // Oh no I forgot to await f so that work will never complete. }); See the two XXXkhuey comments and the unfixed `_l` structure for things that need more thought. *Please keep the line below* changelog: none
2 parents 001c1c5 + 04912ca commit 8334a58

File tree

7 files changed

+331
-0
lines changed

7 files changed

+331
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,7 @@ Released 2018-09-13
15121512
[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants
15131513
[`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern
15141514
[`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops
1515+
[`async_yields_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#async_yields_async
15151516
[`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock
15161517
[`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask
15171518
[`bind_instead_of_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#bind_instead_of_map
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use crate::utils::{implements_trait, snippet, span_lint_and_then};
2+
use rustc_errors::Applicability;
3+
use rustc_hir::{AsyncGeneratorKind, Body, BodyId, ExprKind, GeneratorKind, QPath};
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_session::{declare_lint_pass, declare_tool_lint};
6+
7+
declare_clippy_lint! {
8+
/// **What it does:** Checks for async blocks that yield values of types
9+
/// that can themselves be awaited.
10+
///
11+
/// **Why is this bad?** An await is likely missing.
12+
///
13+
/// **Known problems:** None.
14+
///
15+
/// **Example:**
16+
///
17+
/// ```rust
18+
/// async fn foo() {}
19+
///
20+
/// fn bar() {
21+
/// let x = async {
22+
/// foo()
23+
/// };
24+
/// }
25+
/// ```
26+
/// Use instead:
27+
/// ```rust
28+
/// async fn foo() {}
29+
///
30+
/// fn bar() {
31+
/// let x = async {
32+
/// foo().await
33+
/// };
34+
/// }
35+
/// ```
36+
pub ASYNC_YIELDS_ASYNC,
37+
correctness,
38+
"async blocks that return a type that can be awaited"
39+
}
40+
41+
declare_lint_pass!(AsyncYieldsAsync => [ASYNC_YIELDS_ASYNC]);
42+
43+
impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync {
44+
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
45+
use AsyncGeneratorKind::{Block, Closure};
46+
// For functions, with explicitly defined types, don't warn.
47+
// XXXkhuey maybe we should?
48+
if let Some(GeneratorKind::Async(Block | Closure)) = body.generator_kind {
49+
if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() {
50+
let body_id = BodyId {
51+
hir_id: body.value.hir_id,
52+
};
53+
let def_id = cx.tcx.hir().body_owner_def_id(body_id);
54+
let typeck_results = cx.tcx.typeck(def_id);
55+
let expr_ty = typeck_results.expr_ty(&body.value);
56+
57+
if implements_trait(cx, expr_ty, future_trait_def_id, &[]) {
58+
let return_expr_span = match &body.value.kind {
59+
// XXXkhuey there has to be a better way.
60+
ExprKind::Block(block, _) => block.expr.map(|e| e.span),
61+
ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span),
62+
_ => None,
63+
};
64+
if let Some(return_expr_span) = return_expr_span {
65+
span_lint_and_then(
66+
cx,
67+
ASYNC_YIELDS_ASYNC,
68+
return_expr_span,
69+
"an async construct yields a type which is itself awaitable",
70+
|db| {
71+
db.span_label(body.value.span, "outer async construct");
72+
db.span_label(return_expr_span, "awaitable value not awaited");
73+
db.span_suggestion(
74+
return_expr_span,
75+
"consider awaiting this value",
76+
format!("{}.await", snippet(cx, return_expr_span, "..")),
77+
Applicability::MaybeIncorrect,
78+
);
79+
},
80+
);
81+
}
82+
}
83+
}
84+
}
85+
}
86+
}

clippy_lints/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ mod arithmetic;
154154
mod as_conversions;
155155
mod assertions_on_constants;
156156
mod assign_ops;
157+
mod async_yields_async;
157158
mod atomic_ordering;
158159
mod attrs;
159160
mod await_holding_lock;
@@ -483,6 +484,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
483484
&assertions_on_constants::ASSERTIONS_ON_CONSTANTS,
484485
&assign_ops::ASSIGN_OP_PATTERN,
485486
&assign_ops::MISREFACTORED_ASSIGN_OP,
487+
&async_yields_async::ASYNC_YIELDS_ASYNC,
486488
&atomic_ordering::INVALID_ATOMIC_ORDERING,
487489
&attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
488490
&attrs::DEPRECATED_CFG_ATTR,
@@ -1099,6 +1101,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10991101
store.register_late_pass(|| box unwrap_in_result::UnwrapInResult);
11001102
store.register_late_pass(|| box self_assignment::SelfAssignment);
11011103
store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs);
1104+
store.register_late_pass(|| box async_yields_async::AsyncYieldsAsync);
11021105

11031106
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
11041107
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1232,6 +1235,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12321235
LintId::of(&assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
12331236
LintId::of(&assign_ops::ASSIGN_OP_PATTERN),
12341237
LintId::of(&assign_ops::MISREFACTORED_ASSIGN_OP),
1238+
LintId::of(&async_yields_async::ASYNC_YIELDS_ASYNC),
12351239
LintId::of(&atomic_ordering::INVALID_ATOMIC_ORDERING),
12361240
LintId::of(&attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
12371241
LintId::of(&attrs::DEPRECATED_CFG_ATTR),
@@ -1675,6 +1679,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16751679

16761680
store.register_group(true, "clippy::correctness", Some("clippy_correctness"), vec![
16771681
LintId::of(&approx_const::APPROX_CONSTANT),
1682+
LintId::of(&async_yields_async::ASYNC_YIELDS_ASYNC),
16781683
LintId::of(&atomic_ordering::INVALID_ATOMIC_ORDERING),
16791684
LintId::of(&attrs::DEPRECATED_SEMVER),
16801685
LintId::of(&attrs::MISMATCHED_TARGET_OS),

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
5252
deprecation: None,
5353
module: "assign_ops",
5454
},
55+
Lint {
56+
name: "async_yields_async",
57+
group: "correctness",
58+
desc: "async blocks that return a type that can be awaited",
59+
deprecation: None,
60+
module: "async_yields_async",
61+
},
5562
Lint {
5663
name: "await_holding_lock",
5764
group: "pedantic",

tests/ui/async_yields_async.fixed

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// run-rustfix
2+
// edition:2018
3+
4+
#![feature(async_closure)]
5+
#![warn(clippy::async_yields_async)]
6+
7+
use core::future::Future;
8+
use core::pin::Pin;
9+
use core::task::{Context, Poll};
10+
11+
struct CustomFutureType;
12+
13+
impl Future for CustomFutureType {
14+
type Output = u8;
15+
16+
fn poll(self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> {
17+
Poll::Ready(3)
18+
}
19+
}
20+
21+
fn custom_future_type_ctor() -> CustomFutureType {
22+
CustomFutureType
23+
}
24+
25+
async fn f() -> CustomFutureType {
26+
// Don't warn for functions since you have to explicitly declare their
27+
// return types.
28+
CustomFutureType
29+
}
30+
31+
#[rustfmt::skip]
32+
fn main() {
33+
let _f = {
34+
3
35+
};
36+
let _g = async {
37+
3
38+
};
39+
let _h = async {
40+
async {
41+
3
42+
}.await
43+
};
44+
let _i = async {
45+
CustomFutureType.await
46+
};
47+
let _i = async || {
48+
3
49+
};
50+
let _j = async || {
51+
async {
52+
3
53+
}.await
54+
};
55+
let _k = async || {
56+
CustomFutureType.await
57+
};
58+
let _l = async || CustomFutureType.await;
59+
let _m = async || {
60+
println!("I'm bored");
61+
// Some more stuff
62+
63+
// Finally something to await
64+
CustomFutureType.await
65+
};
66+
let _n = async || custom_future_type_ctor();
67+
let _o = async || f();
68+
}

tests/ui/async_yields_async.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// run-rustfix
2+
// edition:2018
3+
4+
#![feature(async_closure)]
5+
#![warn(clippy::async_yields_async)]
6+
7+
use core::future::Future;
8+
use core::pin::Pin;
9+
use core::task::{Context, Poll};
10+
11+
struct CustomFutureType;
12+
13+
impl Future for CustomFutureType {
14+
type Output = u8;
15+
16+
fn poll(self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> {
17+
Poll::Ready(3)
18+
}
19+
}
20+
21+
fn custom_future_type_ctor() -> CustomFutureType {
22+
CustomFutureType
23+
}
24+
25+
async fn f() -> CustomFutureType {
26+
// Don't warn for functions since you have to explicitly declare their
27+
// return types.
28+
CustomFutureType
29+
}
30+
31+
#[rustfmt::skip]
32+
fn main() {
33+
let _f = {
34+
3
35+
};
36+
let _g = async {
37+
3
38+
};
39+
let _h = async {
40+
async {
41+
3
42+
}
43+
};
44+
let _i = async {
45+
CustomFutureType
46+
};
47+
let _i = async || {
48+
3
49+
};
50+
let _j = async || {
51+
async {
52+
3
53+
}
54+
};
55+
let _k = async || {
56+
CustomFutureType
57+
};
58+
let _l = async || CustomFutureType;
59+
let _m = async || {
60+
println!("I'm bored");
61+
// Some more stuff
62+
63+
// Finally something to await
64+
CustomFutureType
65+
};
66+
let _n = async || custom_future_type_ctor();
67+
let _o = async || f();
68+
}

tests/ui/async_yields_async.stderr

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
error: an async construct yields a type which is itself awaitable
2+
--> $DIR/async_yields_async.rs:40:9
3+
|
4+
LL | let _h = async {
5+
| ____________________-
6+
LL | | async {
7+
| |_________^
8+
LL | || 3
9+
LL | || }
10+
| ||_________^ awaitable value not awaited
11+
LL | | };
12+
| |_____- outer async construct
13+
|
14+
= note: `-D clippy::async-yields-async` implied by `-D warnings`
15+
help: consider awaiting this value
16+
|
17+
LL | async {
18+
LL | 3
19+
LL | }.await
20+
|
21+
22+
error: an async construct yields a type which is itself awaitable
23+
--> $DIR/async_yields_async.rs:45:9
24+
|
25+
LL | let _i = async {
26+
| ____________________-
27+
LL | | CustomFutureType
28+
| | ^^^^^^^^^^^^^^^^
29+
| | |
30+
| | awaitable value not awaited
31+
| | help: consider awaiting this value: `CustomFutureType.await`
32+
LL | | };
33+
| |_____- outer async construct
34+
35+
error: an async construct yields a type which is itself awaitable
36+
--> $DIR/async_yields_async.rs:51:9
37+
|
38+
LL | let _j = async || {
39+
| _______________________-
40+
LL | | async {
41+
| |_________^
42+
LL | || 3
43+
LL | || }
44+
| ||_________^ awaitable value not awaited
45+
LL | | };
46+
| |_____- outer async construct
47+
|
48+
help: consider awaiting this value
49+
|
50+
LL | async {
51+
LL | 3
52+
LL | }.await
53+
|
54+
55+
error: an async construct yields a type which is itself awaitable
56+
--> $DIR/async_yields_async.rs:56:9
57+
|
58+
LL | let _k = async || {
59+
| _______________________-
60+
LL | | CustomFutureType
61+
| | ^^^^^^^^^^^^^^^^
62+
| | |
63+
| | awaitable value not awaited
64+
| | help: consider awaiting this value: `CustomFutureType.await`
65+
LL | | };
66+
| |_____- outer async construct
67+
68+
error: an async construct yields a type which is itself awaitable
69+
--> $DIR/async_yields_async.rs:58:23
70+
|
71+
LL | let _l = async || CustomFutureType;
72+
| ^^^^^^^^^^^^^^^^
73+
| |
74+
| outer async construct
75+
| awaitable value not awaited
76+
| help: consider awaiting this value: `CustomFutureType.await`
77+
78+
error: an async construct yields a type which is itself awaitable
79+
--> $DIR/async_yields_async.rs:64:9
80+
|
81+
LL | let _m = async || {
82+
| _______________________-
83+
LL | | println!("I'm bored");
84+
LL | | // Some more stuff
85+
LL | |
86+
LL | | // Finally something to await
87+
LL | | CustomFutureType
88+
| | ^^^^^^^^^^^^^^^^
89+
| | |
90+
| | awaitable value not awaited
91+
| | help: consider awaiting this value: `CustomFutureType.await`
92+
LL | | };
93+
| |_____- outer async construct
94+
95+
error: aborting due to 6 previous errors
96+

0 commit comments

Comments
 (0)