src/tools/clippy/clippy_lints/src/unused_async.rs RUST 222 lines View on github.com → Search inside
1use clippy_utils::diagnostics::span_lint_hir_and_then;2use clippy_utils::is_def_id_trait_method;3use clippy_utils::usage::is_todo_unimplemented_stub;4use rustc_hir::def::DefKind;5use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};6use rustc_hir::{7    Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Defaultness, Expr, ExprKind, FnDecl, HirId, Node,8    TraitItem, YieldSource,9};10use rustc_lint::{LateContext, LateLintPass};11use rustc_middle::hir::nested_filter;12use rustc_session::impl_lint_pass;13use rustc_span::Span;14use rustc_span::def_id::{LocalDefId, LocalDefIdSet};1516declare_clippy_lint! {17    /// ### What it does18    /// Checks for functions that are declared `async` but have no `.await`s inside of them.19    ///20    /// ### Why is this bad?21    /// Async functions with no async code create overhead, both mentally and computationally.22    /// Callers of async methods either need to be calling from an async function themselves or run it on an executor, both of which23    /// causes runtime overhead and hassle for the caller.24    ///25    /// ### Example26    /// ```no_run27    /// async fn get_random_number() -> i64 {28    ///     4 // Chosen by fair dice roll. Guaranteed to be random.29    /// }30    /// let number_future = get_random_number();31    /// ```32    ///33    /// Use instead:34    /// ```no_run35    /// fn get_random_number_improved() -> i64 {36    ///     4 // Chosen by fair dice roll. Guaranteed to be random.37    /// }38    /// let number_future = async { get_random_number_improved() };39    /// ```40    #[clippy::version = "1.54.0"]41    pub UNUSED_ASYNC,42    pedantic,43    "finds async functions with no await statements"44}4546impl_lint_pass!(UnusedAsync => [UNUSED_ASYNC]);4748#[derive(Default)]49pub struct UnusedAsync {50    /// Keeps track of async functions used as values (i.e. path expressions to async functions that51    /// are not immediately called)52    async_fns_as_value: LocalDefIdSet,53    /// Functions with unused `async`, linted post-crate after we've found all uses of local async54    /// functions55    unused_async_fns: Vec<UnusedAsyncFn>,56}5758#[derive(Copy, Clone)]59struct UnusedAsyncFn {60    def_id: LocalDefId,61    fn_span: Span,62    await_in_async_block: Option<Span>,63}6465struct AsyncFnVisitor<'a, 'tcx> {66    cx: &'a LateContext<'tcx>,67    found_await: bool,68    /// Also keep track of `await`s in nested async blocks so we can mention69    /// it in a note70    await_in_async_block: Option<Span>,71    async_depth: usize,72}7374impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> {75    type NestedFilter = nested_filter::OnlyBodies;7677    fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {78        if let ExprKind::Yield(_, YieldSource::Await { .. }) = ex.kind {79            if self.async_depth == 1 {80                self.found_await = true;81            } else if self.await_in_async_block.is_none() {82                self.await_in_async_block = Some(ex.span);83            }84        }8586        let is_async_block = matches!(87            ex.kind,88            ExprKind::Closure(Closure {89                kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),90                ..91            })92        );9394        if is_async_block {95            self.async_depth += 1;96        }9798        walk_expr(self, ex);99100        if is_async_block {101            self.async_depth -= 1;102        }103    }104105    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {106        self.cx.tcx107    }108}109110impl<'tcx> LateLintPass<'tcx> for UnusedAsync {111    fn check_fn(112        &mut self,113        cx: &LateContext<'tcx>,114        fn_kind: FnKind<'tcx>,115        fn_decl: &'tcx FnDecl<'tcx>,116        body: &Body<'tcx>,117        span: Span,118        def_id: LocalDefId,119    ) {120        if !span.from_expansion()121            && fn_kind.asyncness().is_async()122            && !is_def_id_trait_method(cx, def_id)123            && !is_default_trait_impl(cx, def_id)124            && !async_fn_contains_todo_unimplemented_macro(cx, body)125        {126            let mut visitor = AsyncFnVisitor {127                cx,128                found_await: false,129                await_in_async_block: None,130                async_depth: 0,131            };132            walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id);133            if !visitor.found_await {134                // Don't lint just yet, but store the necessary information for later.135                // The actual linting happens in `check_crate_post`, once we've found all136                // uses of local async functions that do require asyncness to pass typeck137                self.unused_async_fns.push(UnusedAsyncFn {138                    def_id,139                    fn_span: span,140                    await_in_async_block: visitor.await_in_async_block,141                });142            }143        }144    }145146    fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, hir_id: HirId) {147        // Find paths to local async functions that aren't immediately called.148        // E.g. `async fn f() {}; let x = f;`149        // Depending on how `x` is used, f's asyncness might be required despite not having any `await`150        // statements, so don't lint at all if there are any such paths.151        if let Some(def_id) = path.res.opt_def_id()152            && let Some(local_def_id) = def_id.as_local()153            && cx.tcx.def_kind(def_id) == DefKind::Fn154            && cx.tcx.asyncness(def_id).is_async()155            && let parent = cx.tcx.parent_hir_node(hir_id)156            && !matches!(157                parent,158                Node::Expr(Expr {159                    kind: ExprKind::Call(Expr { span, .. }, _),160                    ..161                }) if *span == path.span162            )163        {164            self.async_fns_as_value.insert(local_def_id);165        }166    }167168    // After collecting all unused `async` and problematic paths to such functions,169    // lint those unused ones that didn't have any path expressions to them.170    fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {171        let iter = self172            .unused_async_fns173            .iter()174            .filter(|UnusedAsyncFn { def_id, .. }| !self.async_fns_as_value.contains(def_id));175176        for fun in iter {177            span_lint_hir_and_then(178                cx,179                UNUSED_ASYNC,180                cx.tcx.local_def_id_to_hir_id(fun.def_id),181                fun.fn_span,182                "unused `async` for function with no await statements",183                |diag| {184                    diag.help("consider removing the `async` from this function");185186                    if let Some(span) = fun.await_in_async_block {187                        diag.span_note(188                            span,189                            "`await` used in an async block, which does not require \190                            the enclosing function to be `async`",191                        );192                    }193                },194            );195        }196    }197}198199fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {200    matches!(201        cx.tcx.hir_node_by_def_id(def_id),202        Node::TraitItem(TraitItem {203            defaultness: Defaultness::Default { .. },204            ..205        })206    )207}208209fn async_fn_contains_todo_unimplemented_macro(cx: &LateContext<'_>, body: &Body<'_>) -> bool {210    if let ExprKind::Closure(closure) = body.value.kind211        && let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind212        && let body = cx.tcx.hir_body(closure.body)213        && let ExprKind::Block(block, _) = body.value.kind214        && let Some(expr) = block.expr215        && let ExprKind::DropTemps(inner) = expr.kind216    {217        return is_todo_unimplemented_stub(cx, inner);218    }219220    false221}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.