Skip to content

Commit 3bce957

Browse files
committed
Add unused_async_trait_impl lint
1 parent 80fce9b commit 3bce957

File tree

7 files changed

+256
-0
lines changed

7 files changed

+256
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7089,6 +7089,7 @@ Released 2018-09-13
70897089
[`unstable_as_mut_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_mut_slice
70907090
[`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice
70917091
[`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async
7092+
[`unused_async_trait_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async_trait_impl
70927093
[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
70937094
[`unused_enumerate_index`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_enumerate_index
70947095
[`unused_format_specs`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_format_specs

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
768768
crate::unnested_or_patterns::UNNESTED_OR_PATTERNS_INFO,
769769
crate::unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME_INFO,
770770
crate::unused_async::UNUSED_ASYNC_INFO,
771+
crate::unused_async_trait_impl::UNUSED_ASYNC_TRAIT_IMPL_INFO,
771772
crate::unused_io_amount::UNUSED_IO_AMOUNT_INFO,
772773
crate::unused_peekable::UNUSED_PEEKABLE_INFO,
773774
crate::unused_result_ok::UNUSED_RESULT_OK_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ mod unneeded_struct_pattern;
375375
mod unnested_or_patterns;
376376
mod unsafe_removed_from_name;
377377
mod unused_async;
378+
mod unused_async_trait_impl;
378379
mod unused_io_amount;
379380
mod unused_peekable;
380381
mod unused_result_ok;
@@ -852,6 +853,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
852853
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
853854
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
854855
Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))),
856+
Box::new(|_| Box::new(unused_async_trait_impl::UnusedAsyncTraitImpl)),
855857
// add late passes here, used by `cargo dev new_lint`
856858
];
857859
store.late_passes.extend(late_lints);
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
use clippy_utils::diagnostics::span_lint_hir_and_then;
2+
use clippy_utils::is_def_id_trait_method;
3+
use clippy_utils::source::SpanRangeExt;
4+
use clippy_utils::usage::is_todo_unimplemented_stub;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
7+
use rustc_hir::{
8+
Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Defaultness, Expr, ExprKind, FnDecl, IsAsync, Node,
9+
TraitItem, YieldSource,
10+
};
11+
use rustc_lint::{LateContext, LateLintPass};
12+
use rustc_middle::hir::nested_filter;
13+
use rustc_session::impl_lint_pass;
14+
use rustc_span::Span;
15+
use rustc_span::def_id::LocalDefId;
16+
17+
declare_clippy_lint! {
18+
/// ### What it does
19+
/// Checks for trait function implementations that are declared `async` but have no `.await`s inside of them.
20+
///
21+
/// ### Why is this bad?
22+
/// Async functions with no async code create computational overhead.
23+
/// Even though the trait requires the function to return a future,
24+
/// returning a `core::future::ready` with the result is more efficient.
25+
///
26+
/// ### Example
27+
/// ```no_run
28+
/// impl AsyncTrait for Example {
29+
/// async fn get_random_number() -> i64 {
30+
/// 4 // Chosen by fair dice roll. Guaranteed to be random.
31+
/// }
32+
/// }
33+
/// ```
34+
///
35+
/// Use instead:
36+
/// ```no_run
37+
/// impl AsyncTrait for Example {
38+
/// fn get_random_number() -> impl Future<Output = i64> {
39+
/// core::future::ready(4) // Chosen by fair dice roll. Guaranteed to be random.
40+
/// }
41+
/// }
42+
///
43+
/// ### Note
44+
/// An `async` block generates code that defers execution until the Future is polled.
45+
/// When using `core::future::ready` the code is executed immediately.
46+
/// ```
47+
#[clippy::version = "1.94.0"]
48+
pub UNUSED_ASYNC_TRAIT_IMPL,
49+
pedantic,
50+
"finds async trait impl functions with no await statements"
51+
}
52+
53+
pub struct UnusedAsyncTraitImpl;
54+
55+
impl_lint_pass!(UnusedAsyncTraitImpl => [UNUSED_ASYNC_TRAIT_IMPL]);
56+
57+
struct AsyncFnVisitor<'a, 'tcx> {
58+
cx: &'a LateContext<'tcx>,
59+
found_await: bool,
60+
async_depth: usize,
61+
}
62+
63+
impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> {
64+
type NestedFilter = nested_filter::OnlyBodies;
65+
66+
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
67+
if let ExprKind::Yield(_, YieldSource::Await { .. }) = ex.kind
68+
&& self.async_depth == 1
69+
{
70+
self.found_await = true;
71+
}
72+
73+
let is_async_block = matches!(
74+
ex.kind,
75+
ExprKind::Closure(Closure {
76+
kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
77+
..
78+
})
79+
);
80+
81+
if is_async_block {
82+
self.async_depth += 1;
83+
}
84+
85+
walk_expr(self, ex);
86+
87+
if is_async_block {
88+
self.async_depth -= 1;
89+
}
90+
}
91+
92+
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
93+
self.cx.tcx
94+
}
95+
}
96+
97+
impl<'tcx> LateLintPass<'tcx> for UnusedAsyncTraitImpl {
98+
fn check_fn(
99+
&mut self,
100+
cx: &LateContext<'tcx>,
101+
fn_kind: FnKind<'tcx>,
102+
fn_decl: &'tcx FnDecl<'tcx>,
103+
body: &Body<'tcx>,
104+
span: Span,
105+
def_id: LocalDefId,
106+
) {
107+
if let IsAsync::Async(async_span) = fn_kind.asyncness()
108+
&& !span.from_expansion()
109+
&& is_def_id_trait_method(cx, def_id)
110+
&& !is_default_trait_impl(cx, def_id)
111+
&& !async_fn_contains_todo_unimplemented_macro(cx, body)
112+
{
113+
let mut visitor = AsyncFnVisitor {
114+
cx,
115+
found_await: false,
116+
async_depth: 0,
117+
};
118+
walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id);
119+
if !visitor.found_await {
120+
span_lint_hir_and_then(
121+
cx,
122+
UNUSED_ASYNC_TRAIT_IMPL,
123+
cx.tcx.local_def_id_to_hir_id(def_id),
124+
span,
125+
"unused `async` for async trait impl function with no await statements",
126+
|diag| {
127+
if let Some(output_src) = fn_decl.output.span().get_source_text(cx) {
128+
if let Some(body_src) = body.value.span.get_source_text(cx) {
129+
let output_str = output_src.as_str();
130+
let body_str = body_src.as_str();
131+
132+
let sugg = vec![
133+
(async_span, String::new()),
134+
(fn_decl.output.span(), format!("impl Future<Output = {output_str}>")),
135+
(body.value.span, format!("{{ core::future::ready({body_str}) }}")),
136+
];
137+
138+
diag.help(
139+
"a Future can be constructed from the return value with `core::future::ready`",
140+
);
141+
diag.multipart_suggestion(
142+
format!("consider removing the `async` from this function and returning `impl Future<Output = {output_str}>` instead"),
143+
sugg,
144+
Applicability::MachineApplicable
145+
);
146+
}
147+
}
148+
},
149+
);
150+
}
151+
}
152+
}
153+
}
154+
155+
fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
156+
matches!(
157+
cx.tcx.hir_node_by_def_id(def_id),
158+
Node::TraitItem(TraitItem {
159+
defaultness: Defaultness::Default { .. },
160+
..
161+
})
162+
)
163+
}
164+
165+
fn async_fn_contains_todo_unimplemented_macro(cx: &LateContext<'_>, body: &Body<'_>) -> bool {
166+
if let ExprKind::Closure(closure) = body.value.kind
167+
&& let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind
168+
&& let body = cx.tcx.hir_body(closure.body)
169+
&& let ExprKind::Block(block, _) = body.value.kind
170+
&& block.stmts.is_empty()
171+
&& let Some(expr) = block.expr
172+
&& let ExprKind::DropTemps(inner) = expr.kind
173+
{
174+
return is_todo_unimplemented_stub(cx, inner);
175+
}
176+
177+
false
178+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#![warn(clippy::unused_async_trait_impl)]
2+
3+
trait HasAsyncMethod {
4+
async fn do_something() -> u32;
5+
}
6+
7+
struct Inefficient;
8+
struct Efficient;
9+
10+
impl HasAsyncMethod for Inefficient {
11+
fn do_something() -> impl Future<Output = u32> { core::future::ready({
12+
//~^ unused_async_trait_impl
13+
1
14+
}) }
15+
}
16+
17+
impl HasAsyncMethod for Efficient {
18+
fn do_something() -> impl Future<Output = u32> {
19+
core::future::ready(1)
20+
}
21+
}
22+
23+
fn main() {
24+
Inefficient::do_something();
25+
Efficient::do_something();
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#![warn(clippy::unused_async_trait_impl)]
2+
3+
trait HasAsyncMethod {
4+
async fn do_something() -> u32;
5+
}
6+
7+
struct Inefficient;
8+
struct Efficient;
9+
10+
impl HasAsyncMethod for Inefficient {
11+
async fn do_something() -> u32 {
12+
//~^ unused_async_trait_impl
13+
1
14+
}
15+
}
16+
17+
impl HasAsyncMethod for Efficient {
18+
fn do_something() -> impl Future<Output = u32> {
19+
core::future::ready(1)
20+
}
21+
}
22+
23+
fn main() {
24+
Inefficient::do_something();
25+
Efficient::do_something();
26+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: unused `async` for async trait impl function with no await statements
2+
--> tests/ui/unused_async_trait_impl.rs:11:5
3+
|
4+
LL | / async fn do_something() -> u32 {
5+
LL | |
6+
LL | | 1
7+
LL | | }
8+
| |_____^
9+
|
10+
= help: a Future can be constructed from the return value with `core::future::ready`
11+
= note: `-D clippy::unused-async-trait-impl` implied by `-D warnings`
12+
= help: to override `-D warnings` add `#[allow(clippy::unused_async_trait_impl)]`
13+
help: consider removing the `async` from this function and returning `impl Future<Output = u32>` instead
14+
|
15+
LL ~ fn do_something() -> impl Future<Output = u32> { core::future::ready({
16+
LL +
17+
LL + 1
18+
LL + }) }
19+
|
20+
21+
error: aborting due to 1 previous error
22+

0 commit comments

Comments
 (0)