Repository: rust-lang/rust-clippy (Rust)

Issue:
unused_async for an async fn in a trait
### Summary

If you have an async function in a trait that does nothing by default:

```rs
trait MyTrait {
  // having a body here makes its implementation optional
  async fn completed(self, ...) {}
}
```

sure, the `async` isn't used, but you want implementers to be able to use await in an overridden implementation. In this case, `unused_async` should not be warned

### Lint Name

unused_async

### Reproducer

I tried this code:

```rust
pub trait Action {
    // ...

    async fn cancel(self: Arc<Self>) {}
}
```

I saw this happen:

```
clippy: unused `async` for function with no await statements
consider removing the `async` from this function
```

I expected to see this happen:
No warning

### Version

```text
rustc 1.88.0-nightly (0b45675cf 2025-03-31)
binary: rustc
commit-hash: 0b45675cfcec57f30a3794e1a1e18423aa9cf200
commit-date: 2025-03-31
host: x86_64-unknown-linux-gnu
release: 1.88.0-nightly
LLVM version: 20.1.1
```

### Additional Labels

* `I-suggestion-causes-warning`

<!-- TRIAGEBOT_START -->

<!-- TRIAGEBOT_ASSIGN_START -->

<!-- TRIAGEBOT_ASSIGN_DATA_START$${"user":"profetia"}$$TRIAGEBOT_ASSIGN_DATA_END -->

<!-- TRIAGEBOT_ASSIGN_END -->
<!-- TRIAGEBOT_END -->


Discussion:
@rustbot claim



Patch:
diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs
index 1c1c841e9641..8ceaa3dc58ec 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, HirId, Node, YieldSource};
+use rustc_hir::{Body, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, TraitItem, YieldSource};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::hir::nested_filter;
 use rustc_session::impl_lint_pass;
@@ -116,7 +116,11 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
         span: Span,
         def_id: LocalDefId,
     ) {
-        if !span.from_expansion() && fn_kind.asyncness().is_async() && !is_def_id_trait_method(cx, def_id) {
+        if !span.from_expansion()
+            && fn_kind.asyncness().is_async()
+            && !is_def_id_trait_method(cx, def_id)
+            && !is_default_trait_impl(cx, def_id)
+        {
             let mut visitor = AsyncFnVisitor {
                 cx,
                 found_await: false,
@@ -189,3 +193,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
         }
     }
 }
+
+fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
+    matches!(
+        cx.tcx.hir_node_by_def_id(def_id),
+        Node::TraitItem(TraitItem {
+            defaultness: Defaultness::Default { .. },
+            ..
+        })
+    )
+}


Test patch:
diff --git a/tests/ui/unused_async.rs b/tests/ui/unused_async.rs
index 5aaf7b9f5b59..433459253dd7 100644
--- a/tests/ui/unused_async.rs
+++ b/tests/ui/unused_async.rs
@@ -119,3 +119,11 @@ fn main() {
     foo();
     bar();
 }
+
+mod issue14704 {
+    use std::sync::Arc;
+
+    trait Action {
+        async fn cancel(self: Arc<Self>) {}
+    }
+}