-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[unused_async
]: don't lint if paths reference async fn without immediate call
#11200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,13 @@ | ||
use clippy_utils::diagnostics::span_lint_and_then; | ||
use clippy_utils::is_def_id_trait_method; | ||
use rustc_data_structures::fx::FxHashMap; | ||
use rustc_hir::def::DefKind; | ||
use rustc_hir::intravisit::{walk_body, walk_expr, walk_fn, FnKind, Visitor}; | ||
use rustc_hir::{Body, Expr, ExprKind, FnDecl, YieldSource}; | ||
use rustc_hir::{Body, Expr, ExprKind, FnDecl, Node, YieldSource}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_middle::hir::nested_filter; | ||
use rustc_session::{declare_lint_pass, declare_tool_lint}; | ||
use rustc_span::def_id::LocalDefId; | ||
use rustc_session::{declare_tool_lint, impl_lint_pass}; | ||
use rustc_span::def_id::{LocalDefId, LocalDefIdSet}; | ||
use rustc_span::Span; | ||
|
||
declare_clippy_lint! { | ||
|
@@ -38,7 +40,23 @@ declare_clippy_lint! { | |
"finds async functions with no await statements" | ||
} | ||
|
||
declare_lint_pass!(UnusedAsync => [UNUSED_ASYNC]); | ||
#[derive(Default)] | ||
pub struct UnusedAsync { | ||
/// Keeps track of async functions used as values (i.e. path expressions to async functions that | ||
/// are not immediately called) | ||
async_fns_as_value: LocalDefIdSet, | ||
/// Functions with unused `async`, linted post-crate after we've found all uses of local async | ||
/// functions | ||
unused_async_fns: FxHashMap<LocalDefId, UnusedAsyncFn>, | ||
} | ||
|
||
#[derive(Copy, Clone)] | ||
struct UnusedAsyncFn { | ||
fn_span: Span, | ||
await_in_async_block: Option<Span>, | ||
} | ||
|
||
impl_lint_pass!(UnusedAsync => [UNUSED_ASYNC]); | ||
|
||
struct AsyncFnVisitor<'a, 'tcx> { | ||
cx: &'a LateContext<'tcx>, | ||
|
@@ -101,24 +119,71 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { | |
}; | ||
walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id); | ||
if !visitor.found_await { | ||
span_lint_and_then( | ||
cx, | ||
UNUSED_ASYNC, | ||
span, | ||
"unused `async` for function with no await statements", | ||
|diag| { | ||
diag.help("consider removing the `async` from this function"); | ||
|
||
if let Some(span) = visitor.await_in_async_block { | ||
diag.span_note( | ||
span, | ||
"`await` used in an async block, which does not require \ | ||
the enclosing function to be `async`", | ||
); | ||
} | ||
// Don't lint just yet, but store the necessary information for later. | ||
// The actual linting happens in `check_crate_post`, once we've found all | ||
// uses of local async functions that do require asyncness to pass typeck | ||
self.unused_async_fns.insert( | ||
def_id, | ||
UnusedAsyncFn { | ||
await_in_async_block: visitor.await_in_async_block, | ||
fn_span: span, | ||
}, | ||
); | ||
} | ||
} | ||
} | ||
|
||
fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, hir_id: rustc_hir::HirId) { | ||
fn is_node_func_call(node: Node<'_>, expected_receiver: Span) -> bool { | ||
matches!( | ||
node, | ||
Node::Expr(Expr { | ||
kind: ExprKind::Call(Expr { span, .. }, _) | ExprKind::MethodCall(_, Expr { span, .. }, ..), | ||
.. | ||
}) if *span == expected_receiver | ||
) | ||
} | ||
|
||
// Find paths to local async functions that aren't immediately called. | ||
// E.g. `async fn f() {}; let x = f;` | ||
// Depending on how `x` is used, f's asyncness might be required despite not having any `await` | ||
// statements, so don't lint at all if there are any such paths. | ||
Comment on lines
+147
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this can use #11166 once that's merged, that way some cases are still linted There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unlikely to be the case. The signatures of the two functions are incompatible. |
||
if let Some(def_id) = path.res.opt_def_id() | ||
&& let Some(local_def_id) = def_id.as_local() | ||
&& let Some(DefKind::Fn) = cx.tcx.opt_def_kind(def_id) | ||
&& cx.tcx.asyncness(def_id).is_async() | ||
&& !is_node_func_call(cx.tcx.hir().get_parent(hir_id), path.span) | ||
{ | ||
self.async_fns_as_value.insert(local_def_id); | ||
} | ||
} | ||
|
||
// After collecting all unused `async` and problematic paths to such functions, | ||
// lint those unused ones that didn't have any path expressions to them. | ||
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { | ||
let iter = self | ||
.unused_async_fns | ||
.iter() | ||
.filter_map(|(did, item)| (!self.async_fns_as_value.contains(did)).then_some(item)); | ||
|
||
for fun in iter { | ||
span_lint_and_then( | ||
y21 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
cx, | ||
UNUSED_ASYNC, | ||
fun.fn_span, | ||
"unused `async` for function with no await statements", | ||
|diag| { | ||
diag.help("consider removing the `async` from this function"); | ||
|
||
if let Some(span) = fun.await_in_async_block { | ||
diag.span_note( | ||
span, | ||
"`await` used in an async block, which does not require \ | ||
the enclosing function to be `async`", | ||
); | ||
} | ||
}, | ||
); | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.