about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/unused_async.rs29
-rw-r--r--clippy_lints/src/unused_self.rs20
-rw-r--r--clippy_utils/src/usage.rs42
-rw-r--r--tests/ui/unused_async.rs10
4 files changed, 78 insertions, 23 deletions
diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs
index e67afc7f5a8..5a3e4b7adf6 100644
--- a/clippy_lints/src/unused_async.rs
+++ b/clippy_lints/src/unused_async.rs
@@ -1,8 +1,12 @@
 use clippy_utils::diagnostics::span_lint_hir_and_then;
 use clippy_utils::is_def_id_trait_method;
+use clippy_utils::usage::is_todo_unimplemented_stub;
 use rustc_hir::def::DefKind;
 use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
-use rustc_hir::{Body, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, TraitItem, YieldSource};
+use rustc_hir::{
+    Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, 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;
@@ -81,11 +85,8 @@ impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> {
 
         let is_async_block = matches!(
             ex.kind,
-            ExprKind::Closure(rustc_hir::Closure {
-                kind: rustc_hir::ClosureKind::Coroutine(rustc_hir::CoroutineKind::Desugared(
-                    rustc_hir::CoroutineDesugaring::Async,
-                    _
-                )),
+            ExprKind::Closure(Closure {
+                kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
                 ..
             })
         );
@@ -120,6 +121,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
             && fn_kind.asyncness().is_async()
             && !is_def_id_trait_method(cx, def_id)
             && !is_default_trait_impl(cx, def_id)
+            && !async_fn_contains_todo_unimplemented_macro(cx, body)
         {
             let mut visitor = AsyncFnVisitor {
                 cx,
@@ -203,3 +205,18 @@ fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
         })
     )
 }
+
+fn async_fn_contains_todo_unimplemented_macro(cx: &LateContext<'_>, body: &Body<'_>) -> bool {
+    if let ExprKind::Closure(closure) = body.value.kind
+        && let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind
+        && let body = cx.tcx.hir_body(closure.body)
+        && let ExprKind::Block(block, _) = body.value.kind
+        && block.stmts.is_empty()
+        && let Some(expr) = block.expr
+        && let ExprKind::DropTemps(inner) = expr.kind
+    {
+        return is_todo_unimplemented_stub(cx, inner);
+    }
+
+    false
+}
diff --git a/clippy_lints/src/unused_self.rs b/clippy_lints/src/unused_self.rs
index 12da891a71b..dff39974a37 100644
--- a/clippy_lints/src/unused_self.rs
+++ b/clippy_lints/src/unused_self.rs
@@ -1,12 +1,10 @@
 use clippy_config::Conf;
 use clippy_utils::diagnostics::span_lint_and_help;
-use clippy_utils::macros::root_macro_call_first_node;
-use clippy_utils::sym;
+use clippy_utils::usage::is_todo_unimplemented_stub;
 use clippy_utils::visitors::is_local_used;
-use rustc_hir::{Body, Impl, ImplItem, ImplItemKind, ItemKind};
+use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::impl_lint_pass;
-use std::ops::ControlFlow;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -60,18 +58,6 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
         let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id;
         let parent_item = cx.tcx.hir_expect_item(parent);
         let assoc_item = cx.tcx.associated_item(impl_item.owner_id);
-        let contains_todo = |cx, body: &'_ Body<'_>| -> bool {
-            clippy_utils::visitors::for_each_expr_without_closures(body.value, |e| {
-                if let Some(macro_call) = root_macro_call_first_node(cx, e)
-                    && cx.tcx.is_diagnostic_item(sym::todo_macro, macro_call.def_id)
-                {
-                    ControlFlow::Break(())
-                } else {
-                    ControlFlow::Continue(())
-                }
-            })
-            .is_some()
-        };
         if let ItemKind::Impl(Impl { of_trait: None, .. }) = parent_item.kind
             && assoc_item.is_method()
             && let ImplItemKind::Fn(.., body_id) = &impl_item.kind
@@ -79,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
             && let body = cx.tcx.hir_body(*body_id)
             && let [self_param, ..] = body.params
             && !is_local_used(cx, body, self_param.pat.hir_id)
-            && !contains_todo(cx, body)
+            && !is_todo_unimplemented_stub(cx, body.value)
         {
             span_lint_and_help(
                 cx,
diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs
index 1b049b6d12c..76d43feee12 100644
--- a/clippy_utils/src/usage.rs
+++ b/clippy_utils/src/usage.rs
@@ -1,3 +1,4 @@
+use crate::macros::root_macro_call_first_node;
 use crate::visitors::{Descend, Visitable, for_each_expr, for_each_expr_without_closures};
 use crate::{self as utils, get_enclosing_loop_or_multi_call_closure};
 use core::ops::ControlFlow;
@@ -9,6 +10,7 @@ use rustc_lint::LateContext;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::mir::FakeReadCause;
 use rustc_middle::ty;
+use rustc_span::sym;
 
 /// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
 pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<HirIdSet> {
@@ -140,6 +142,46 @@ impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> {
     }
 }
 
+/// Checks if the given expression is a macro call to `todo!()` or `unimplemented!()`.
+pub fn is_todo_unimplemented_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    root_macro_call_first_node(cx, expr).is_some_and(|macro_call| {
+        [sym::todo_macro, sym::unimplemented_macro]
+            .iter()
+            .any(|&sym| cx.tcx.is_diagnostic_item(sym, macro_call.def_id))
+    })
+}
+
+/// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression,
+/// or a block whose last expression is a `todo!()` or `unimplemented!()`.
+pub fn is_todo_unimplemented_stub(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    if let ExprKind::Block(block, _) = expr.kind {
+        if let Some(last_expr) = block.expr {
+            return is_todo_unimplemented_macro(cx, last_expr);
+        }
+
+        return block.stmts.last().is_some_and(|stmt| {
+            if let hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) = stmt.kind {
+                return is_todo_unimplemented_macro(cx, expr);
+            }
+            false
+        });
+    }
+
+    is_todo_unimplemented_macro(cx, expr)
+}
+
+/// Checks if the given expression contains macro call to `todo!()` or `unimplemented!()`.
+pub fn contains_todo_unimplement_macro(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
+    for_each_expr_without_closures(expr, |e| {
+        if is_todo_unimplemented_macro(cx, e) {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(())
+        }
+    })
+    .is_some()
+}
+
 pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
     for_each_expr_without_closures(expression, |e| {
         match e.kind {
diff --git a/tests/ui/unused_async.rs b/tests/ui/unused_async.rs
index 433459253dd..7a0be825a2d 100644
--- a/tests/ui/unused_async.rs
+++ b/tests/ui/unused_async.rs
@@ -127,3 +127,13 @@ mod issue14704 {
         async fn cancel(self: Arc<Self>) {}
     }
 }
+
+mod issue15305 {
+    async fn todo_task() -> Result<(), String> {
+        todo!("Implement task");
+    }
+
+    async fn unimplemented_task() -> Result<(), String> {
+        unimplemented!("Implement task");
+    }
+}