|
1 |
| -use clippy_utils::diagnostics::span_lint_and_then; |
| 1 | +use clippy_utils::diagnostics::span_lint_hir_and_then; |
2 | 2 | use clippy_utils::is_def_id_trait_method;
|
| 3 | +use rustc_hir::def::DefKind; |
3 | 4 | use rustc_hir::intravisit::{walk_body, walk_expr, walk_fn, FnKind, Visitor};
|
4 |
| -use rustc_hir::{Body, Expr, ExprKind, FnDecl, YieldSource}; |
| 5 | +use rustc_hir::{Body, Expr, ExprKind, FnDecl, Node, YieldSource}; |
5 | 6 | use rustc_lint::{LateContext, LateLintPass};
|
6 | 7 | use rustc_middle::hir::nested_filter;
|
7 |
| -use rustc_session::{declare_lint_pass, declare_tool_lint}; |
8 |
| -use rustc_span::def_id::LocalDefId; |
| 8 | +use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 9 | +use rustc_span::def_id::{LocalDefId, LocalDefIdSet}; |
9 | 10 | use rustc_span::Span;
|
10 | 11 |
|
11 | 12 | declare_clippy_lint! {
|
@@ -38,7 +39,24 @@ declare_clippy_lint! {
|
38 | 39 | "finds async functions with no await statements"
|
39 | 40 | }
|
40 | 41 |
|
41 |
| -declare_lint_pass!(UnusedAsync => [UNUSED_ASYNC]); |
| 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]); |
42 | 60 |
|
43 | 61 | struct AsyncFnVisitor<'a, 'tcx> {
|
44 | 62 | cx: &'a LateContext<'tcx>,
|
@@ -101,24 +119,70 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
|
101 | 119 | };
|
102 | 120 | walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id);
|
103 | 121 | if !visitor.found_await {
|
104 |
| - span_lint_and_then( |
105 |
| - cx, |
106 |
| - UNUSED_ASYNC, |
107 |
| - span, |
108 |
| - "unused `async` for function with no await statements", |
109 |
| - |diag| { |
110 |
| - diag.help("consider removing the `async` from this function"); |
111 |
| - |
112 |
| - if let Some(span) = visitor.await_in_async_block { |
113 |
| - diag.span_note( |
114 |
| - span, |
115 |
| - "`await` used in an async block, which does not require \ |
116 |
| - the enclosing function to be `async`", |
117 |
| - ); |
118 |
| - } |
119 |
| - }, |
120 |
| - ); |
| 122 | + // Don't lint just yet, but store the necessary information for later. |
| 123 | + // The actual linting happens in `check_crate_post`, once we've found all |
| 124 | + // uses of local async functions that do require asyncness to pass typeck |
| 125 | + self.unused_async_fns.push(UnusedAsyncFn { |
| 126 | + await_in_async_block: visitor.await_in_async_block, |
| 127 | + fn_span: span, |
| 128 | + def_id, |
| 129 | + }); |
121 | 130 | }
|
122 | 131 | }
|
123 | 132 | }
|
| 133 | + |
| 134 | + fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, hir_id: rustc_hir::HirId) { |
| 135 | + fn is_node_func_call(node: Node<'_>, expected_receiver: Span) -> bool { |
| 136 | + matches!( |
| 137 | + node, |
| 138 | + Node::Expr(Expr { |
| 139 | + kind: ExprKind::Call(Expr { span, .. }, _) | ExprKind::MethodCall(_, Expr { span, .. }, ..), |
| 140 | + .. |
| 141 | + }) if *span == expected_receiver |
| 142 | + ) |
| 143 | + } |
| 144 | + |
| 145 | + // Find paths to local async functions that aren't immediately called. |
| 146 | + // E.g. `async fn f() {}; let x = f;` |
| 147 | + // Depending on how `x` is used, f's asyncness might be required despite not having any `await` |
| 148 | + // statements, so don't lint at all if there are any such paths. |
| 149 | + if let Some(def_id) = path.res.opt_def_id() |
| 150 | + && let Some(local_def_id) = def_id.as_local() |
| 151 | + && let Some(DefKind::Fn) = cx.tcx.opt_def_kind(def_id) |
| 152 | + && cx.tcx.asyncness(def_id).is_async() |
| 153 | + && !is_node_func_call(cx.tcx.hir().get_parent(hir_id), path.span) |
| 154 | + { |
| 155 | + self.async_fns_as_value.insert(local_def_id); |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + // After collecting all unused `async` and problematic paths to such functions, |
| 160 | + // lint those unused ones that didn't have any path expressions to them. |
| 161 | + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { |
| 162 | + let iter = self |
| 163 | + .unused_async_fns |
| 164 | + .iter() |
| 165 | + .filter(|UnusedAsyncFn { def_id, .. }| (!self.async_fns_as_value.contains(def_id))); |
| 166 | + |
| 167 | + for fun in iter { |
| 168 | + span_lint_hir_and_then( |
| 169 | + cx, |
| 170 | + UNUSED_ASYNC, |
| 171 | + cx.tcx.local_def_id_to_hir_id(fun.def_id), |
| 172 | + fun.fn_span, |
| 173 | + "unused `async` for function with no await statements", |
| 174 | + |diag| { |
| 175 | + diag.help("consider removing the `async` from this function"); |
| 176 | + |
| 177 | + if let Some(span) = fun.await_in_async_block { |
| 178 | + diag.span_note( |
| 179 | + span, |
| 180 | + "`await` used in an async block, which does not require \ |
| 181 | + the enclosing function to be `async`", |
| 182 | + ); |
| 183 | + } |
| 184 | + }, |
| 185 | + ); |
| 186 | + } |
| 187 | + } |
124 | 188 | }
|
0 commit comments