Skip to content

Commit 6dcade0

Browse files
committed
Implement let_underscore_future
1 parent 37d338c commit 6dcade0

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

clippy_lints/src/let_underscore.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_help;
2-
use clippy_utils::ty::{is_must_use_ty, match_type};
2+
use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type};
33
use clippy_utils::{is_must_use_func_call, paths};
44
use rustc_hir::{Local, PatKind};
55
use rustc_lint::{LateContext, LateLintPass};
@@ -59,7 +59,31 @@ declare_clippy_lint! {
5959
"non-binding let on a synchronization lock"
6060
}
6161

62-
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK]);
62+
declare_clippy_lint! {
63+
/// ### What it does
64+
/// Checks for `let _ = <expr>` where the resulting type of expr implements `Future`
65+
///
66+
/// ### Why is this bad?
67+
/// Futures must be polled for work to be done. The original intention was most likely to await the future
68+
/// and ignore the resulting value.
69+
///
70+
/// ### Example
71+
/// ```rust,ignore
72+
/// async fn foo() -> Result<(), ()> { }
73+
/// let _ = foo();
74+
/// ```
75+
///
76+
/// Use instead:
77+
/// ```rust,ignore
78+
/// let _ = foo().await;
79+
/// ```
80+
#[clippy::version = "1.66"]
81+
pub LET_UNDERSCORE_FUTURE,
82+
suspicious,
83+
"non-binding let on a future"
84+
}
85+
86+
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE]);
6387

6488
const SYNC_GUARD_PATHS: [&[&str]; 3] = [
6589
&paths::PARKING_LOT_MUTEX_GUARD,
@@ -88,6 +112,16 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
88112
"consider using an underscore-prefixed named \
89113
binding or dropping explicitly with `std::mem::drop`",
90114
);
115+
} else if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
116+
&& implements_trait(cx, cx.typeck_results().expr_ty(init), future_trait_def_id, &[]) {
117+
span_lint_and_help(
118+
cx,
119+
LET_UNDERSCORE_FUTURE,
120+
local.span,
121+
"non-binding let on a future",
122+
None,
123+
"consider awaiting the future or dropping explicitly with `std::mem::drop`"
124+
);
91125
} else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) {
92126
span_lint_and_help(
93127
cx,

tests/ui/let_underscore_future.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::future::Future;
2+
3+
async fn some_async_fn() {}
4+
5+
fn sync_side_effects() {}
6+
fn custom() -> impl Future<Output = ()> {
7+
sync_side_effects();
8+
async {}
9+
}
10+
11+
fn do_something_to_future(future: &mut impl Future<Output = ()>) {}
12+
13+
fn main() {
14+
let _ = some_async_fn();
15+
let _ = custom();
16+
17+
let mut future = some_async_fn();
18+
do_something_to_future(&mut future);
19+
let _ = future;
20+
}

tests/ui/let_underscore_future.stderr

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
error: non-binding let on a future
2+
--> $DIR/let_underscore_future.rs:14:5
3+
|
4+
LL | let _ = some_async_fn();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: consider awaiting the future or dropping explicitly with `std::mem::drop`
8+
= note: `-D clippy::let-underscore-future` implied by `-D warnings`
9+
10+
error: non-binding let on a future
11+
--> $DIR/let_underscore_future.rs:15:5
12+
|
13+
LL | let _ = custom();
14+
| ^^^^^^^^^^^^^^^^^
15+
|
16+
= help: consider awaiting the future or dropping explicitly with `std::mem::drop`
17+
18+
error: non-binding let on a future
19+
--> $DIR/let_underscore_future.rs:19:5
20+
|
21+
LL | let _ = future;
22+
| ^^^^^^^^^^^^^^^
23+
|
24+
= help: consider awaiting the future or dropping explicitly with `std::mem::drop`
25+
26+
error: aborting due to 3 previous errors
27+

0 commit comments

Comments
 (0)