|
| 1 | +use clippy_utils::diagnostics::span_lint_and_help; |
| 2 | +use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor}; |
| 3 | +use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnHeader, HirId, IsAsync, Item, ItemKind, YieldSource}; |
| 4 | +use rustc_lint::{LateContext, LateLintPass}; |
| 5 | +use rustc_middle::hir::map::Map; |
| 6 | +use rustc_session::{declare_lint_pass, declare_tool_lint}; |
| 7 | +use rustc_span::Span; |
| 8 | + |
| 9 | +declare_clippy_lint! { |
| 10 | + /// **What it does:** Checks for functions that are declared `async` but have no `.await`s inside of them. |
| 11 | + /// |
| 12 | + /// **Why is this bad?** Async functions with no async code create overhead, both mentally and computationally. |
| 13 | + /// Callers of async methods either need to be calling from an async function themselves or run it on an executor, both of which |
| 14 | + /// causes runtime overhead and hassle for the caller. |
| 15 | + /// |
| 16 | + /// **Known problems:** None |
| 17 | + /// |
| 18 | + /// **Example:** |
| 19 | + /// |
| 20 | + /// ```rust |
| 21 | + /// // Bad |
| 22 | + /// async fn get_random_number() -> i64 { |
| 23 | + /// 4 // Chosen by fair dice roll. Guaranteed to be random. |
| 24 | + /// } |
| 25 | + /// let number_future = get_random_number(); |
| 26 | + /// |
| 27 | + /// // Good |
| 28 | + /// fn get_random_number_improved() -> i64 { |
| 29 | + /// 4 // Chosen by fair dice roll. Guaranteed to be random. |
| 30 | + /// } |
| 31 | + /// let number_future = async { get_random_number_improved() }; |
| 32 | + /// ``` |
| 33 | + pub UNNECESSARY_ASYNC, |
| 34 | + pedantic, |
| 35 | + "finds async functions with no await statements" |
| 36 | +} |
| 37 | + |
| 38 | +declare_lint_pass!(UnnecessaryAsync => [UNNECESSARY_ASYNC]); |
| 39 | + |
| 40 | +struct AsyncFnVisitor<'a, 'tcx> { |
| 41 | + cx: &'a LateContext<'tcx>, |
| 42 | + found_await: bool, |
| 43 | +} |
| 44 | + |
| 45 | +impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> { |
| 46 | + type Map = Map<'tcx>; |
| 47 | + |
| 48 | + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { |
| 49 | + if let ExprKind::Yield(_, YieldSource::Await { .. }) = ex.kind { |
| 50 | + self.found_await = true; |
| 51 | + } |
| 52 | + walk_expr(self, ex); |
| 53 | + } |
| 54 | + |
| 55 | + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { |
| 56 | + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +impl<'tcx> LateLintPass<'tcx> for UnnecessaryAsync { |
| 61 | + fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { |
| 62 | + if let ItemKind::Trait(..) = item.kind { |
| 63 | + return; |
| 64 | + } |
| 65 | + } |
| 66 | + fn check_fn( |
| 67 | + &mut self, |
| 68 | + cx: &LateContext<'tcx>, |
| 69 | + fn_kind: FnKind<'tcx>, |
| 70 | + fn_decl: &'tcx FnDecl<'tcx>, |
| 71 | + body: &Body<'tcx>, |
| 72 | + span: Span, |
| 73 | + hir_id: HirId, |
| 74 | + ) { |
| 75 | + if let FnKind::ItemFn(_, _, FnHeader { asyncness, .. }, _) = &fn_kind { |
| 76 | + if matches!(asyncness, IsAsync::Async) { |
| 77 | + let mut visitor = AsyncFnVisitor { cx, found_await: false }; |
| 78 | + walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), span, hir_id); |
| 79 | + if !visitor.found_await { |
| 80 | + span_lint_and_help( |
| 81 | + cx, |
| 82 | + UNNECESSARY_ASYNC, |
| 83 | + span, |
| 84 | + "unnecessary `async` for function with no await statements", |
| 85 | + None, |
| 86 | + "consider removing the `async` from this function", |
| 87 | + ); |
| 88 | + } |
| 89 | + } |
| 90 | + } |
| 91 | + } |
| 92 | +} |
0 commit comments