-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Open
Wassasin
wants to merge
16
commits into
rust-lang:master
Choose a base branch
from
Wassasin:unused_async_trait_impl
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
2c59897
Add unused_async_trait_impl lint
Wassasin 8a8e0ae
Adjusted description and use declare_lint_pass
Wassasin 345a48f
Remove the execution of unused_async_trait_impl tests, added todo!() …
Wassasin 6802082
Simplified by using check_impl_item instead of check_fn
Wassasin ded1b73
Use std_or_core to determine the builtin_crate instead of assuming core
Wassasin 5ba562f
Added no_std version of test
Wassasin 6378a15
Use snippet_with_applicability to ensure the suggestion can be given …
Wassasin 5670f89
Improved lint description
Wassasin 1fa2639
Fix lint span
Wassasin 6bd9e3a
Slightly shorten snippet_with_applicability-line
Wassasin 081977b
Fix indentation for body block
Wassasin c9ee761
Fix whitespace around the removed async keyword
Wassasin 65e9892
Added indented testcase
Wassasin 09816b2
Clarify how the construction of the body snippet works
Wassasin 84b451b
Made example slightly more complicate to demonstrate correct indentation
Wassasin cdff8eb
Expanded on testcases
Wassasin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| use clippy_utils::diagnostics::span_lint_hir_and_then; | ||
| 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::intravisit::{Visitor, walk_expr}; | ||
| use rustc_hir::{ | ||
| Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, ImplItem, ImplItemKind, IsAsync, | ||
| YieldSource, | ||
| }; | ||
| use rustc_lint::{LateContext, LateLintPass}; | ||
| use rustc_middle::hir::nested_filter; | ||
| use rustc_session::declare_lint_pass; | ||
|
|
||
| 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.94.0"] | ||
| pub UNUSED_ASYNC_TRAIT_IMPL, | ||
| pedantic, | ||
| "finds async trait impl functions with no await statements" | ||
| } | ||
|
|
||
| declare_lint_pass!(UnusedAsyncTraitImpl => [UNUSED_ASYNC_TRAIT_IMPL]); | ||
|
|
||
| struct AsyncFnVisitor<'a, 'tcx> { | ||
| cx: &'a LateContext<'tcx>, | ||
| found_await: bool, | ||
| async_depth: usize, | ||
| } | ||
|
|
||
| impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> { | ||
| type NestedFilter = nested_filter::OnlyBodies; | ||
|
|
||
| fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { | ||
| if let ExprKind::Yield(_, YieldSource::Await { .. }) = ex.kind | ||
| && self.async_depth == 1 | ||
| { | ||
| self.found_await = true; | ||
| } | ||
|
|
||
| let is_async_block = matches!( | ||
| ex.kind, | ||
| ExprKind::Closure(Closure { | ||
| kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)), | ||
| .. | ||
| }) | ||
| ); | ||
|
|
||
| if is_async_block { | ||
| self.async_depth += 1; | ||
| } | ||
|
|
||
| walk_expr(self, ex); | ||
|
|
||
| if is_async_block { | ||
| self.async_depth -= 1; | ||
| } | ||
| } | ||
|
|
||
| fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { | ||
| self.cx.tcx | ||
| } | ||
| } | ||
|
|
||
| impl<'tcx> LateLintPass<'tcx> for UnusedAsyncTraitImpl { | ||
| 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) | ||
Wassasin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| let mut visitor = AsyncFnVisitor { | ||
| cx, | ||
| found_await: false, | ||
| 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", | ||
| |diag| { | ||
| let mut app = Applicability::MachineApplicable; | ||
|
|
||
| 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); | ||
Wassasin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // 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), | ||
Wassasin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ); | ||
|
|
||
| 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!( | ||
| "a Future can be constructed from the return value with `{builtin_crate}::future::ready`" | ||
| )); | ||
| diag.multipart_suggestion( | ||
| format!("consider removing the `async` from this function and returning `impl Future<Output = {signature_snippet}>` instead"), | ||
| sugg, | ||
| app | ||
| ); | ||
| }, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn async_fn_contains_todo_unimplemented_macro(cx: &LateContext<'_>, body: &Body<'_>) -> bool { | ||
| if 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 | ||
| && block.stmts.is_empty() | ||
| && let Some(expr) = block.expr | ||
| && let ExprKind::DropTemps(inner) = expr.kind | ||
| { | ||
| return is_todo_unimplemented_stub(cx, inner); | ||
| } | ||
|
|
||
| false | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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! | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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! | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.