-
Notifications
You must be signed in to change notification settings - Fork 2k
Add unused_async_trait_impl lint #16244
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
base: master
Are you sure you want to change the base?
Changes from all commits
5c469d5
6544c2c
e73d51a
aef2e2e
a647f23
2e36afc
5a84626
359fb83
8e0b18f
d7bda9b
7ad6dd6
5af570e
0b93c4f
5ed514f
1d66093
aa69ef5
1fc329c
ba9f66a
e1696b9
b56d50a
0cd53ac
98edbc2
175b982
4078b17
b62462c
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_hir_and_then; | ||||||||||||||||||||
| use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; | ||||||||||||||||||||
| use clippy_utils::is_def_id_trait_method; | ||||||||||||||||||||
| use clippy_utils::source::{HasSession, snippet_with_applicability}; | ||||||||||||||||||||
| use clippy_utils::usage::is_todo_unimplemented_stub; | ||||||||||||||||||||
| use rustc_errors::Applicability; | ||||||||||||||||||||
| use rustc_hir::def::DefKind; | ||||||||||||||||||||
| use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn}; | ||||||||||||||||||||
| use rustc_hir::{ | ||||||||||||||||||||
| Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, | ||||||||||||||||||||
| TraitItem, YieldSource, | ||||||||||||||||||||
| Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Defaultness, Expr, ExprKind, FnDecl, HirId, | ||||||||||||||||||||
| ImplItem, ImplItemKind, IsAsync, Node, TraitItem, YieldSource, | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| use rustc_lint::{LateContext, LateLintPass}; | ||||||||||||||||||||
| use rustc_middle::hir::nested_filter; | ||||||||||||||||||||
|
|
@@ -43,7 +45,52 @@ declare_clippy_lint! { | |||||||||||||||||||
| "finds async functions with no await statements" | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| impl_lint_pass!(UnusedAsync => [UNUSED_ASYNC]); | ||||||||||||||||||||
| declare_clippy_lint! { | ||||||||||||||||||||
| /// ### What it does | ||||||||||||||||||||
| /// Checks for trait method implementations that are declared `async` but have no `.await`s inside of them. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// ### Why is this bad? | ||||||||||||||||||||
| /// Async functions with no async code create computational overhead. | ||||||||||||||||||||
| /// Even though the trait requires the method to return a future, | ||||||||||||||||||||
| /// returning a `core::future::ready` with the result is more efficient | ||||||||||||||||||||
| /// as it reduces the number of states in the Future state machine by at least one. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// Note that the behaviour is slightly different when using `core::future::ready`, | ||||||||||||||||||||
| /// as the value is computed immediately and stored in a future for later retrieval at the first (and only valid) call to `poll`. | ||||||||||||||||||||
| /// An `async` block generates code that completely defers the computation of this value until the Future is polled. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// ### Example | ||||||||||||||||||||
| /// ```no_run | ||||||||||||||||||||
| /// trait AsyncTrait { | ||||||||||||||||||||
| /// async fn get_random_number() -> i64; | ||||||||||||||||||||
| /// } | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// impl AsyncTrait for () { | ||||||||||||||||||||
| /// async fn get_random_number() -> i64 { | ||||||||||||||||||||
| /// 4 // Chosen by fair dice roll. Guaranteed to be random. | ||||||||||||||||||||
| /// } | ||||||||||||||||||||
| /// } | ||||||||||||||||||||
| /// ``` | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// Use instead: | ||||||||||||||||||||
| /// ```no_run | ||||||||||||||||||||
| /// trait AsyncTrait { | ||||||||||||||||||||
| /// async fn get_random_number() -> i64; | ||||||||||||||||||||
| /// } | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// impl AsyncTrait for () { | ||||||||||||||||||||
| /// fn get_random_number() -> impl Future<Output = i64> { | ||||||||||||||||||||
| /// core::future::ready(4) // Chosen by fair dice roll. Guaranteed to be random. | ||||||||||||||||||||
| /// } | ||||||||||||||||||||
| /// } | ||||||||||||||||||||
| /// ``` | ||||||||||||||||||||
| #[clippy::version = "1.97.0"] | ||||||||||||||||||||
| pub UNUSED_ASYNC_TRAIT_IMPL, | ||||||||||||||||||||
| pedantic, | ||||||||||||||||||||
| "finds async trait impl functions with no await statements" | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| impl_lint_pass!(UnusedAsync => [UNUSED_ASYNC, UNUSED_ASYNC_TRAIT_IMPL]); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| #[derive(Default)] | ||||||||||||||||||||
| pub struct UnusedAsync { | ||||||||||||||||||||
|
|
@@ -194,6 +241,70 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { | |||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { | ||||||||||||||||||||
| if let ImplItemKind::Fn(ref sig, body_id) = impl_item.kind | ||||||||||||||||||||
| && let IsAsync::Async(async_span) = sig.header.asyncness | ||||||||||||||||||||
| && let body = cx.tcx.hir_body(body_id) | ||||||||||||||||||||
| && !async_fn_contains_todo_unimplemented_macro(cx, body) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| let mut visitor = AsyncFnVisitor { | ||||||||||||||||||||
| cx, | ||||||||||||||||||||
| found_await: false, | ||||||||||||||||||||
| await_in_async_block: None, | ||||||||||||||||||||
| async_depth: 0, | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| visitor.visit_nested_body(body_id); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if !visitor.found_await | ||||||||||||||||||||
| && let Some(builtin_crate) = clippy_utils::std_or_core(cx) | ||||||||||||||||||||
| && let ExprKind::Closure(closure) = body.value.kind | ||||||||||||||||||||
| && let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind | ||||||||||||||||||||
| && let body = cx.tcx.hir_body(closure.body) | ||||||||||||||||||||
| && let ExprKind::Block(block, _) = body.value.kind | ||||||||||||||||||||
| && let Some(expr) = block.expr | ||||||||||||||||||||
| && let ExprKind::DropTemps(inner) = expr.kind | ||||||||||||||||||||
| && let ExprKind::Block(block, _) = inner.kind | ||||||||||||||||||||
| && let Some(tail_expr) = block.expr | ||||||||||||||||||||
| { | ||||||||||||||||||||
| span_lint_and_then( | ||||||||||||||||||||
| cx, | ||||||||||||||||||||
| UNUSED_ASYNC_TRAIT_IMPL, | ||||||||||||||||||||
| impl_item.span, | ||||||||||||||||||||
| "unused `async` for async trait impl function with no `.await` statements", | ||||||||||||||||||||
| |diag| { | ||||||||||||||||||||
| // The suggestion might be incorrect. The future changes from awaiting for the first poll to | ||||||||||||||||||||
| // evaluate the expression, to immediately evaluate the expression. | ||||||||||||||||||||
| let mut app = Applicability::MaybeIncorrect; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let async_span = cx.sess().source_map().span_extend_while_whitespace(async_span); | ||||||||||||||||||||
| let signature_snippet = snippet_with_applicability(cx, sig.decl.output.span(), "_", &mut app); | ||||||||||||||||||||
| let tail_snippet = snippet_with_applicability(cx, tail_expr.span, "_", &mut app).to_string(); | ||||||||||||||||||||
|
Comment on lines
+281
to
+282
Contributor
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. Just in theory, both of these might be macro calls. For the type, this could look like this: (I concur that this is somewhat contrived) macro_rules! opt {
($t:ty) => { Option<$t> }
}
async fn foo() -> opt!(u32) {
Some(4)
}And for the trailing expression: async fn foo() -> Vec<u32> {
vec![]
}Thankfully, the fix is pretty simple:
Suggested change
(See the documentation on Could you please add the examples above as test cases? |
||||||||||||||||||||
|
|
||||||||||||||||||||
| let sugg = vec![ | ||||||||||||||||||||
| (async_span, String::new()), | ||||||||||||||||||||
| ( | ||||||||||||||||||||
| sig.decl.output.span(), | ||||||||||||||||||||
| format!("impl Future<Output = {signature_snippet}>"), | ||||||||||||||||||||
| ), | ||||||||||||||||||||
| ( | ||||||||||||||||||||
| tail_expr.span, | ||||||||||||||||||||
| format!("{builtin_crate}::future::ready({tail_snippet})"), | ||||||||||||||||||||
| ), | ||||||||||||||||||||
| ]; | ||||||||||||||||||||
| diag.help(format!( | ||||||||||||||||||||
|
Contributor
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 think this should be a note, as it doesn't by itself say what to do, but rather gives some context around the fix. See https://doc.rust-lang.org/clippy/development/emitting_lints.html#how-to-choose-between-notes-help-messages-and-suggestions
Suggested change
|
||||||||||||||||||||
| "`{builtin_crate}::future::ready` creates a `Future` which returns the value immediately when `poll`ed" | ||||||||||||||||||||
| )); | ||||||||||||||||||||
| diag.multipart_suggestion( | ||||||||||||||||||||
| format!("consider removing the `async` from this function and returning `impl Future<Output = {signature_snippet}>` instead"), | ||||||||||||||||||||
| sugg, | ||||||||||||||||||||
| app | ||||||||||||||||||||
| ); | ||||||||||||||||||||
|
Comment on lines
+299
to
+302
Contributor
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. nit: it seems like rustfmt gave up on formatting this part because of a format literal that's too long. Breaking it up should fix this:
Suggested change
|
||||||||||||||||||||
| }, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| #![warn(clippy::unused_async_trait_impl)] | ||
|
|
||
| trait HasAsyncMethod { | ||
| async fn do_something() -> u32; | ||
| } | ||
|
|
||
| struct Inefficient; | ||
| struct Efficient; | ||
| struct Stub; | ||
|
|
||
| impl HasAsyncMethod for Inefficient { | ||
| fn do_something() -> impl Future<Output = u32> { | ||
| //~^ unused_async_trait_impl | ||
| std::future::ready(1) | ||
| } | ||
| } | ||
|
|
||
| impl HasAsyncMethod for Efficient { | ||
| fn do_something() -> impl Future<Output = u32> { | ||
| std::future::ready(1) | ||
| } | ||
| } | ||
|
|
||
| impl HasAsyncMethod for Stub { | ||
| async fn do_something() -> u32 { | ||
| todo!() // Do not emit the lint in this case. | ||
| } | ||
| } | ||
|
|
||
| // Test to check if the identation of the various snippets goes as intended. | ||
| mod indented { | ||
| struct Indented; | ||
|
|
||
| impl crate::HasAsyncMethod for Indented { | ||
| fn do_something() -> impl Future<Output = u32> { | ||
| //~^ unused_async_trait_impl | ||
| let mut x = 0; | ||
| for y in 0..64 { | ||
| x = (x + 1) * y; | ||
| } | ||
|
|
||
| let fake_fut = async { | ||
| if x == 0 { | ||
| panic!("Fake example"); | ||
| } | ||
| }; | ||
|
|
||
| std::future::ready(x) | ||
| } | ||
| } | ||
|
|
||
| struct Complex<T>(std::marker::PhantomData<T>); | ||
|
|
||
| impl<T> crate::HasAsyncMethod for Complex<T> | ||
| where | ||
| T: Sized, | ||
| { | ||
| fn do_something() -> impl Future<Output = u32> { | ||
| //~^ unused_async_trait_impl | ||
| std::future::ready(5) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| trait HasDefaultAsyncMethod { | ||
| // The lint should not suggest a change for trait fn's as changing that decl | ||
| // implies a less restrictive Future type. | ||
| async fn do_something() -> u32 { | ||
| 0 | ||
| } | ||
| } | ||
|
|
||
| impl HasDefaultAsyncMethod for Stub { | ||
| // Nothing! | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| #![warn(clippy::unused_async_trait_impl)] | ||
|
|
||
| trait HasAsyncMethod { | ||
| async fn do_something() -> u32; | ||
| } | ||
|
|
||
| struct Inefficient; | ||
| struct Efficient; | ||
| struct Stub; | ||
|
|
||
| impl HasAsyncMethod for Inefficient { | ||
| async fn do_something() -> u32 { | ||
| //~^ unused_async_trait_impl | ||
| 1 | ||
| } | ||
| } | ||
|
|
||
| impl HasAsyncMethod for Efficient { | ||
| fn do_something() -> impl Future<Output = u32> { | ||
| std::future::ready(1) | ||
| } | ||
| } | ||
|
|
||
| impl HasAsyncMethod for Stub { | ||
| async fn do_something() -> u32 { | ||
| todo!() // Do not emit the lint in this case. | ||
| } | ||
| } | ||
|
|
||
| // Test to check if the identation of the various snippets goes as intended. | ||
| mod indented { | ||
| struct Indented; | ||
|
|
||
| impl crate::HasAsyncMethod for Indented { | ||
| async fn do_something() -> u32 { | ||
| //~^ unused_async_trait_impl | ||
| let mut x = 0; | ||
| for y in 0..64 { | ||
| x = (x + 1) * y; | ||
| } | ||
|
|
||
| let fake_fut = async { | ||
| if x == 0 { | ||
| panic!("Fake example"); | ||
| } | ||
| }; | ||
|
|
||
| x | ||
| } | ||
| } | ||
|
|
||
| struct Complex<T>(std::marker::PhantomData<T>); | ||
|
|
||
| impl<T> crate::HasAsyncMethod for Complex<T> | ||
| where | ||
| T: Sized, | ||
| { | ||
| async fn do_something() -> u32 { | ||
| //~^ unused_async_trait_impl | ||
| 5 | ||
| } | ||
| } | ||
| } | ||
|
|
||
| trait HasDefaultAsyncMethod { | ||
| // The lint should not suggest a change for trait fn's as changing that decl | ||
| // implies a less restrictive Future type. | ||
| async fn do_something() -> u32 { | ||
| 0 | ||
| } | ||
| } | ||
|
|
||
| impl HasDefaultAsyncMethod for Stub { | ||
| // Nothing! | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| error: unused `async` for async trait impl function with no `.await` statements | ||
| --> tests/ui/unused_async_trait_impl.rs:12:5 | ||
| | | ||
| LL | / async fn do_something() -> u32 { | ||
| LL | | | ||
| LL | | 1 | ||
| LL | | } | ||
| | |_____^ | ||
| | | ||
| = help: `std::future::ready` creates a `Future` which returns the value immediately when `poll`ed | ||
| = note: `-D clippy::unused-async-trait-impl` implied by `-D warnings` | ||
| = help: to override `-D warnings` add `#[allow(clippy::unused_async_trait_impl)]` | ||
| help: consider removing the `async` from this function and returning `impl Future<Output = u32>` instead | ||
| | | ||
| LL ~ fn do_something() -> impl Future<Output = u32> { | ||
| LL | | ||
| LL ~ std::future::ready(1) | ||
| | | ||
|
|
||
| error: unused `async` for async trait impl function with no `.await` statements | ||
| --> tests/ui/unused_async_trait_impl.rs:35:9 | ||
| | | ||
| LL | / async fn do_something() -> u32 { | ||
| LL | | | ||
| LL | | let mut x = 0; | ||
| LL | | for y in 0..64 { | ||
| ... | | ||
| LL | | x | ||
| LL | | } | ||
| | |_________^ | ||
| | | ||
| = help: `std::future::ready` creates a `Future` which returns the value immediately when `poll`ed | ||
| help: consider removing the `async` from this function and returning `impl Future<Output = u32>` instead | ||
| | | ||
| LL ~ fn do_something() -> impl Future<Output = u32> { | ||
| LL | | ||
| ... | ||
| LL | | ||
| LL ~ std::future::ready(x) | ||
| | | ||
|
|
||
| error: unused `async` for async trait impl function with no `.await` statements | ||
| --> tests/ui/unused_async_trait_impl.rs:58:9 | ||
| | | ||
| LL | / async fn do_something() -> u32 { | ||
| LL | | | ||
| LL | | 5 | ||
| LL | | } | ||
| | |_________^ | ||
| | | ||
| = help: `std::future::ready` creates a `Future` which returns the value immediately when `poll`ed | ||
| help: consider removing the `async` from this function and returning `impl Future<Output = u32>` instead | ||
| | | ||
| LL ~ fn do_something() -> impl Future<Output = u32> { | ||
| LL | | ||
| LL ~ std::future::ready(5) | ||
| | | ||
|
|
||
| error: aborting due to 3 previous errors | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| #![no_std] | ||
| #![warn(clippy::unused_async_trait_impl)] | ||
|
|
||
| trait HasAsyncMethod { | ||
| async fn do_something() -> u32; | ||
| } | ||
|
|
||
| struct Inefficient; | ||
|
|
||
| impl HasAsyncMethod for Inefficient { | ||
| fn do_something() -> impl Future<Output = u32> { | ||
| //~^ unused_async_trait_impl | ||
| core::future::ready(1) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Async fn desugaring is somewhat unintuitive imo... What do you think about adding some documentation, like this perhaps: