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.