diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index cfc1a21ea87f..4855f301d0d0 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::{is_must_use_ty, match_type}; +use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type}; use clippy_utils::{is_must_use_func_call, paths}; use rustc_hir::{Local, PatKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -59,7 +59,31 @@ declare_clippy_lint! { "non-binding let on a synchronization lock" } -declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK]); +declare_clippy_lint! { + /// ### What it does + /// Checks for `let _ = ` where the resulting type of expr implements `Future` + /// + /// ### Why is this bad? + /// Futures must be polled for work to be done. The original intention was most likely to await the future + /// and ignore the resulting value. + /// + /// ### Example + /// ```rust,ignore + /// async fn foo() -> Result<(), ()> { } + /// let _ = foo(); + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// let _ = foo().await; + /// ``` + #[clippy::version = "1.66"] + pub LET_UNDERSCORE_FUTURE, + suspicious, + "non-binding let on a future" +} + +declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE]); const SYNC_GUARD_PATHS: [&[&str]; 3] = [ &paths::PARKING_LOT_MUTEX_GUARD, @@ -88,6 +112,16 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore { "consider using an underscore-prefixed named \ binding or dropping explicitly with `std::mem::drop`", ); + } else if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() + && implements_trait(cx, cx.typeck_results().expr_ty(init), future_trait_def_id, &[]) { + span_lint_and_help( + cx, + LET_UNDERSCORE_FUTURE, + local.span, + "non-binding let on a future", + None, + "consider awaiting the future or dropping explicitly with `std::mem::drop`" + ); } else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) { span_lint_and_help( cx, diff --git a/tests/ui/let_underscore_future.rs b/tests/ui/let_underscore_future.rs new file mode 100644 index 000000000000..d8f54cdca912 --- /dev/null +++ b/tests/ui/let_underscore_future.rs @@ -0,0 +1,20 @@ +use std::future::Future; + +async fn some_async_fn() {} + +fn sync_side_effects() {} +fn custom() -> impl Future { + sync_side_effects(); + async {} +} + +fn do_something_to_future(future: &mut impl Future) {} + +fn main() { + let _ = some_async_fn(); + let _ = custom(); + + let mut future = some_async_fn(); + do_something_to_future(&mut future); + let _ = future; +} diff --git a/tests/ui/let_underscore_future.stderr b/tests/ui/let_underscore_future.stderr new file mode 100644 index 000000000000..f1b9b1d709ec --- /dev/null +++ b/tests/ui/let_underscore_future.stderr @@ -0,0 +1,27 @@ +error: non-binding let on a future + --> $DIR/let_underscore_future.rs:14:5 + | +LL | let _ = some_async_fn(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider awaiting the future or dropping explicitly with `std::mem::drop` + = note: `-D clippy::let-underscore-future` implied by `-D warnings` + +error: non-binding let on a future + --> $DIR/let_underscore_future.rs:15:5 + | +LL | let _ = custom(); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider awaiting the future or dropping explicitly with `std::mem::drop` + +error: non-binding let on a future + --> $DIR/let_underscore_future.rs:19:5 + | +LL | let _ = future; + | ^^^^^^^^^^^^^^^ + | + = help: consider awaiting the future or dropping explicitly with `std::mem::drop` + +error: aborting due to 3 previous errors +