about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-09-28 19:18:56 +0000
committerbors <bors@rust-lang.org>2024-09-28 19:18:56 +0000
commit897f0e4749ce1d2d2d2857c31417432f8b53d455 (patch)
tree7930b4ed11ef64f4f3d31d131c3f1b9fe264151b
parentb367d3422c713113258d4f652e7295a59e5fddde (diff)
parentd24a63199a5ad140fd52f6feb2e279e2a51cc201 (diff)
Auto merge of #13471 - y21:issue13466-fp, r=llogiq
Remove method call receiver special casing in `unused_async` lint

Fixes the false positive mentioned in https://github.com/rust-lang/rust-clippy/issues/13466#issuecomment-2380025514.

The false negative in the OP would be nice to fix too, but I'd rather do that in a separate PR because it's much more involved

Before this change, the `unused_async` lint would check if the async fn is also used anywhere and avoid linting if so. The exception is if the async function is immediately called, because the returned future handling can be easily removed (and also if we don't have some exceptions then the lint wouldn't trigger anywhere) *or* if it's a method call receiver.

I'm not exactly sure why I implemented that special casing for method call receivers in #11200, but it doesn't make much sense in hindsight imo. Especially given that method calls are essentially equivalent to function calls with the receiver as the first argument, which was the primary motivation for not linting in the first place (async fn passed to another function, like `axum::get(handler)` where handler has to be an async fn).

changelog: none
-rw-r--r--clippy_lints/src/unused_async.rs23
-rw-r--r--tests/ui/unused_async.rs16
-rw-r--r--tests/ui/unused_async.stderr4
3 files changed, 28 insertions, 15 deletions
diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs
index a1f08cf6623..0ed64fd1f8e 100644
--- a/clippy_lints/src/unused_async.rs
+++ b/clippy_lints/src/unused_async.rs
@@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then;
 use clippy_utils::is_def_id_trait_method;
 use rustc_hir::def::DefKind;
 use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
-use rustc_hir::{Body, Expr, ExprKind, FnDecl, Node, YieldSource};
+use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Node, YieldSource};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::hir::nested_filter;
 use rustc_session::impl_lint_pass;
@@ -137,17 +137,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
         }
     }
 
-    fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, hir_id: rustc_hir::HirId) {
-        fn is_node_func_call(node: Node<'_>, expected_receiver: Span) -> bool {
-            matches!(
-                node,
-                Node::Expr(Expr {
-                    kind: ExprKind::Call(Expr { span, .. }, _) | ExprKind::MethodCall(_, Expr { span, .. }, ..),
-                    ..
-                }) if *span == expected_receiver
-            )
-        }
-
+    fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, hir_id: HirId) {
         // Find paths to local async functions that aren't immediately called.
         // E.g. `async fn f() {}; let x = f;`
         // Depending on how `x` is used, f's asyncness might be required despite not having any `await`
@@ -156,7 +146,14 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
             && let Some(local_def_id) = def_id.as_local()
             && cx.tcx.def_kind(def_id) == DefKind::Fn
             && cx.tcx.asyncness(def_id).is_async()
-            && !is_node_func_call(cx.tcx.parent_hir_node(hir_id), path.span)
+            && let parent = cx.tcx.parent_hir_node(hir_id)
+            && !matches!(
+                parent,
+                Node::Expr(Expr {
+                    kind: ExprKind::Call(Expr { span, .. }, _),
+                    ..
+                }) if *span == path.span
+            )
         {
             self.async_fns_as_value.insert(local_def_id);
         }
diff --git a/tests/ui/unused_async.rs b/tests/ui/unused_async.rs
index 7ec8a3adb4c..838d6f0aa97 100644
--- a/tests/ui/unused_async.rs
+++ b/tests/ui/unused_async.rs
@@ -55,6 +55,22 @@ mod issue9695 {
     }
 }
 
+mod issue13466 {
+    use std::future::Future;
+
+    struct Wrap<F>(F);
+    impl<F> From<F> for Wrap<F> {
+        fn from(f: F) -> Self {
+            Self(f)
+        }
+    }
+    fn takes_fut<F: Fn() -> Fut, Fut: Future>(_: Wrap<F>) {}
+    async fn unused_async() {}
+    fn fp() {
+        takes_fut(unused_async.into());
+    }
+}
+
 async fn foo() -> i32 {
     //~^ ERROR: unused `async` for function with no await statements
     4
diff --git a/tests/ui/unused_async.stderr b/tests/ui/unused_async.stderr
index 337c650e029..4811df63658 100644
--- a/tests/ui/unused_async.stderr
+++ b/tests/ui/unused_async.stderr
@@ -27,7 +27,7 @@ LL |     async fn f3() {}
    = help: consider removing the `async` from this function
 
 error: unused `async` for function with no await statements
-  --> tests/ui/unused_async.rs:58:1
+  --> tests/ui/unused_async.rs:74:1
    |
 LL | / async fn foo() -> i32 {
 LL | |
@@ -38,7 +38,7 @@ LL | | }
    = help: consider removing the `async` from this function
 
 error: unused `async` for function with no await statements
-  --> tests/ui/unused_async.rs:70:5
+  --> tests/ui/unused_async.rs:86:5
    |
 LL | /     async fn unused(&self) -> i32 {
 LL | |