-
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 18 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,15 @@ | ||||||||||||||||||||
| use clippy_utils::diagnostics::span_lint_hir_and_then; | ||||||||||||||||||||
| use clippy_utils::is_def_id_trait_method; | ||||||||||||||||||||
| use clippy_utils::source::{ | ||||||||||||||||||||
| HasSession, indent_of, reindent_multiline, snippet_block_with_applicability, 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 +47,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 +243,87 @@ 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) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| span_lint_hir_and_then( | ||||||||||||||||||||
| cx, | ||||||||||||||||||||
| UNUSED_ASYNC_TRAIT_IMPL, | ||||||||||||||||||||
| cx.tcx.local_def_id_to_hir_id(impl_item.owner_id.def_id), | ||||||||||||||||||||
| impl_item.span, | ||||||||||||||||||||
| "unused `async` for async trait impl function with no await statements", | ||||||||||||||||||||
|
Wassasin marked this conversation as resolved.
Outdated
|
||||||||||||||||||||
| |diag| { | ||||||||||||||||||||
| let mut app = Applicability::MachineApplicable; | ||||||||||||||||||||
|
Wassasin marked this conversation as resolved.
Outdated
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| 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); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Fetch body snippet and truncate excess indentation. Like this: | ||||||||||||||||||||
| // { | ||||||||||||||||||||
| // 4 | ||||||||||||||||||||
| // } | ||||||||||||||||||||
| let body_snippet = snippet_block_with_applicability(cx, body.value.span, "_", None, &mut app); | ||||||||||||||||||||
|
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. One way to make this even nicer would be to strip away the curlies if the function body is just a single expression. I'd be insane to ask you to do that now, on top of everything you've already done, so could you please just leave a TODO comment for this?
Author
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. Changed it but the expansion is a bit messy. Initially I used the span of the expression, but we are losing any comments that might be been put in the body. I opted to remove the curly braces manually from the original body span.
Author
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. See this commit for the difference between expressions & manually trimming the braces |
||||||||||||||||||||
| // Wrap body snippet in `std::future::ready(...)` and indent everything by one level, like this: | ||||||||||||||||||||
| // core::future::ready({ | ||||||||||||||||||||
| // 4 | ||||||||||||||||||||
| // }) | ||||||||||||||||||||
| let new_body_inner_snippet: String = reindent_multiline( | ||||||||||||||||||||
| &format!("{builtin_crate}::future::ready({body_snippet})"), | ||||||||||||||||||||
| false, | ||||||||||||||||||||
| Some(4), | ||||||||||||||||||||
| ); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let sugg = vec![ | ||||||||||||||||||||
| (async_span, String::new()), | ||||||||||||||||||||
| ( | ||||||||||||||||||||
| sig.decl.output.span(), | ||||||||||||||||||||
| format!("impl Future<Output = {signature_snippet}>"), | ||||||||||||||||||||
| ), | ||||||||||||||||||||
| ( | ||||||||||||||||||||
| body.value.span, | ||||||||||||||||||||
| // Wrap the entire snippet in fresh curly braces and indent everything except the first | ||||||||||||||||||||
| // line by the indentation level of the original body snippet, like this: | ||||||||||||||||||||
| // { | ||||||||||||||||||||
| // <indent> core::future::ready({ | ||||||||||||||||||||
| // <indent> 4 | ||||||||||||||||||||
| // <indent> } | ||||||||||||||||||||
| // <indent> } | ||||||||||||||||||||
| reindent_multiline( | ||||||||||||||||||||
| &format!("{{\n{new_body_inner_snippet}\n}}"), | ||||||||||||||||||||
| true, | ||||||||||||||||||||
| indent_of(cx, body.value.span), | ||||||||||||||||||||
| ), | ||||||||||||||||||||
| ), | ||||||||||||||||||||
| ]; | ||||||||||||||||||||
| 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
|
||||||||||||||||||||
| "a Future can be constructed from the return value with `{builtin_crate}::future::ready`" | ||||||||||||||||||||
|
Wassasin marked this conversation as resolved.
Outdated
|
||||||||||||||||||||
| )); | ||||||||||||||||||||
| 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,81 @@ | ||
| #![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> { | ||
| std::future::ready({ | ||
| //~^ 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 { | ||
| fn do_something() -> impl Future<Output = u32> { | ||
| std::future::ready({ | ||
| //~^ 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, | ||
| { | ||
| fn do_something() -> impl Future<Output = u32> { | ||
| std::future::ready({ | ||
| //~^ 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,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! | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.