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}
;
12 declare_clippy_lint
! {
14 /// Checks for functions that are declared `async` but have no `.await`s inside of them.
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.
23 /// async fn get_random_number() -> i64 {
24 /// 4 // Chosen by fair dice roll. Guaranteed to be random.
26 /// let number_future = get_random_number();
31 /// fn get_random_number_improved() -> i64 {
32 /// 4 // Chosen by fair dice roll. Guaranteed to be random.
34 /// let number_future = async { get_random_number_improved() };
36 #[clippy::version = "1.54.0"]
39 "finds async functions with no await statements"
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
49 unused_async_fns
: Vec
<UnusedAsyncFn
>,
52 #[derive(Copy, Clone)]
53 struct UnusedAsyncFn
{
56 await_in_async_block
: Option
<Span
>,
59 impl_lint_pass
!(UnusedAsync
=> [UNUSED_ASYNC
]);
61 struct AsyncFnVisitor
<'a
, 'tcx
> {
62 cx
: &'a LateContext
<'tcx
>,
64 /// Also keep track of `await`s in nested async blocks so we can mention
66 await_in_async_block
: Option
<Span
>,
70 impl<'a
, 'tcx
> Visitor
<'tcx
> for AsyncFnVisitor
<'a
, 'tcx
> {
71 type NestedFilter
= nested_filter
::OnlyBodies
;
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
);
82 let is_async_block
= matches
!(
84 ExprKind
::Closure(rustc_hir
::Closure
{
85 kind
: rustc_hir
::ClosureKind
::Coroutine(rustc_hir
::CoroutineKind
::Desugared(
86 rustc_hir
::CoroutineDesugaring
::Async
,
94 self.async_depth
+= 1;
100 self.async_depth
-= 1;
104 fn nested_visit_map(&mut self) -> Self::Map
{
109 impl<'tcx
> LateLintPass
<'tcx
> for UnusedAsync
{
112 cx
: &LateContext
<'tcx
>,
113 fn_kind
: FnKind
<'tcx
>,
114 fn_decl
: &'tcx FnDecl
<'tcx
>,
119 if !span
.from_expansion() && fn_kind
.asyncness().is_async() && !is_def_id_trait_method(cx
, def_id
) {
120 let mut visitor
= AsyncFnVisitor
{
124 await_in_async_block
: None
,
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
,
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
{
145 kind
: ExprKind
::Call(Expr { span, .. }
, _
) | ExprKind
::MethodCall(_
, Expr { span, .. }
, ..),
147 }) if *span
== expected_receiver
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
)
161 self.async_fns_as_value
.insert(local_def_id
);
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
>) {
171 .filter(|UnusedAsyncFn { def_id, .. }
| (!self.async_fns_as_value
.contains(def_id
)));
174 span_lint_hir_and_then(
177 cx
.tcx
.local_def_id_to_hir_id(fun
.def_id
),
179 "unused `async` for function with no await statements",
181 diag
.help("consider removing the `async` from this function");
183 if let Some(span
) = fun
.await_in_async_block
{
186 "`await` used in an async block, which does not require \
187 the enclosing function to be `async`",