]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/unused_async.rs
bump version to 1.80.1+dfsg1-1~bpo12+pve1
[rustc.git] / src / tools / clippy / clippy_lints / src / unused_async.rs
1 use clippy_utils::diagnostics::span_lint_hir_and_then;
2 use clippy_utils::is_def_id_trait_method;
3 use rustc_hir::def::DefKind;
4 use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor};
5 use rustc_hir::{Body, Expr, ExprKind, FnDecl, Node, YieldSource};
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_middle::hir::nested_filter;
8 use rustc_session::impl_lint_pass;
9 use rustc_span::def_id::{LocalDefId, LocalDefIdSet};
10 use rustc_span::Span;
11
12 declare_clippy_lint! {
13 /// ### What it does
14 /// Checks for functions that are declared `async` but have no `.await`s inside of them.
15 ///
16 /// ### Why is this bad?
17 /// Async functions with no async code create overhead, both mentally and computationally.
18 /// Callers of async methods either need to be calling from an async function themselves or run it on an executor, both of which
19 /// causes runtime overhead and hassle for the caller.
20 ///
21 /// ### Example
22 /// ```no_run
23 /// async fn get_random_number() -> i64 {
24 /// 4 // Chosen by fair dice roll. Guaranteed to be random.
25 /// }
26 /// let number_future = get_random_number();
27 /// ```
28 ///
29 /// Use instead:
30 /// ```no_run
31 /// fn get_random_number_improved() -> i64 {
32 /// 4 // Chosen by fair dice roll. Guaranteed to be random.
33 /// }
34 /// let number_future = async { get_random_number_improved() };
35 /// ```
36 #[clippy::version = "1.54.0"]
37 pub UNUSED_ASYNC,
38 pedantic,
39 "finds async functions with no await statements"
40 }
41
42 #[derive(Default)]
43 pub struct UnusedAsync {
44 /// Keeps track of async functions used as values (i.e. path expressions to async functions that
45 /// are not immediately called)
46 async_fns_as_value: LocalDefIdSet,
47 /// Functions with unused `async`, linted post-crate after we've found all uses of local async
48 /// functions
49 unused_async_fns: Vec<UnusedAsyncFn>,
50 }
51
52 #[derive(Copy, Clone)]
53 struct UnusedAsyncFn {
54 def_id: LocalDefId,
55 fn_span: Span,
56 await_in_async_block: Option<Span>,
57 }
58
59 impl_lint_pass!(UnusedAsync => [UNUSED_ASYNC]);
60
61 struct AsyncFnVisitor<'a, 'tcx> {
62 cx: &'a LateContext<'tcx>,
63 found_await: bool,
64 /// Also keep track of `await`s in nested async blocks so we can mention
65 /// it in a note
66 await_in_async_block: Option<Span>,
67 async_depth: usize,
68 }
69
70 impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> {
71 type NestedFilter = nested_filter::OnlyBodies;
72
73 fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
74 if let ExprKind::Yield(_, YieldSource::Await { .. }) = ex.kind {
75 if self.async_depth == 1 {
76 self.found_await = true;
77 } else if self.await_in_async_block.is_none() {
78 self.await_in_async_block = Some(ex.span);
79 }
80 }
81
82 let is_async_block = matches!(
83 ex.kind,
84 ExprKind::Closure(rustc_hir::Closure {
85 kind: rustc_hir::ClosureKind::Coroutine(rustc_hir::CoroutineKind::Desugared(
86 rustc_hir::CoroutineDesugaring::Async,
87 _
88 )),
89 ..
90 })
91 );
92
93 if is_async_block {
94 self.async_depth += 1;
95 }
96
97 walk_expr(self, ex);
98
99 if is_async_block {
100 self.async_depth -= 1;
101 }
102 }
103
104 fn nested_visit_map(&mut self) -> Self::Map {
105 self.cx.tcx.hir()
106 }
107 }
108
109 impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
110 fn check_fn(
111 &mut self,
112 cx: &LateContext<'tcx>,
113 fn_kind: FnKind<'tcx>,
114 fn_decl: &'tcx FnDecl<'tcx>,
115 body: &Body<'tcx>,
116 span: Span,
117 def_id: LocalDefId,
118 ) {
119 if !span.from_expansion() && fn_kind.asyncness().is_async() && !is_def_id_trait_method(cx, def_id) {
120 let mut visitor = AsyncFnVisitor {
121 cx,
122 found_await: false,
123 async_depth: 0,
124 await_in_async_block: None,
125 };
126 walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id);
127 if !visitor.found_await {
128 // Don't lint just yet, but store the necessary information for later.
129 // The actual linting happens in `check_crate_post`, once we've found all
130 // uses of local async functions that do require asyncness to pass typeck
131 self.unused_async_fns.push(UnusedAsyncFn {
132 await_in_async_block: visitor.await_in_async_block,
133 fn_span: span,
134 def_id,
135 });
136 }
137 }
138 }
139
140 fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, hir_id: rustc_hir::HirId) {
141 fn is_node_func_call(node: Node<'_>, expected_receiver: Span) -> bool {
142 matches!(
143 node,
144 Node::Expr(Expr {
145 kind: ExprKind::Call(Expr { span, .. }, _) | ExprKind::MethodCall(_, Expr { span, .. }, ..),
146 ..
147 }) if *span == expected_receiver
148 )
149 }
150
151 // Find paths to local async functions that aren't immediately called.
152 // E.g. `async fn f() {}; let x = f;`
153 // Depending on how `x` is used, f's asyncness might be required despite not having any `await`
154 // statements, so don't lint at all if there are any such paths.
155 if let Some(def_id) = path.res.opt_def_id()
156 && let Some(local_def_id) = def_id.as_local()
157 && cx.tcx.def_kind(def_id) == DefKind::Fn
158 && cx.tcx.asyncness(def_id).is_async()
159 && !is_node_func_call(cx.tcx.parent_hir_node(hir_id), path.span)
160 {
161 self.async_fns_as_value.insert(local_def_id);
162 }
163 }
164
165 // After collecting all unused `async` and problematic paths to such functions,
166 // lint those unused ones that didn't have any path expressions to them.
167 fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
168 let iter = self
169 .unused_async_fns
170 .iter()
171 .filter(|UnusedAsyncFn { def_id, .. }| (!self.async_fns_as_value.contains(def_id)));
172
173 for fun in iter {
174 span_lint_hir_and_then(
175 cx,
176 UNUSED_ASYNC,
177 cx.tcx.local_def_id_to_hir_id(fun.def_id),
178 fun.fn_span,
179 "unused `async` for function with no await statements",
180 |diag| {
181 diag.help("consider removing the `async` from this function");
182
183 if let Some(span) = fun.await_in_async_block {
184 diag.span_note(
185 span,
186 "`await` used in an async block, which does not require \
187 the enclosing function to be `async`",
188 );
189 }
190 },
191 );
192 }
193 }
194 }