Add new redundant_async_block lint#10448
Conversation
|
r? @llogiq (rustbot has picked a reviewer for you, use r? to override) |
984422a to
1bd895a
Compare
llogiq
left a comment
There was a problem hiding this comment.
I like this lint, but I think we can make the message shorter. Also I'd want to see some more tests. Finally, before we introduce a warn-by-default lint, we should at least run a lintcheck to get a handle on churn.
| /// ### Example | ||
| /// ```rust | ||
| /// await { | ||
| /// function_returning_a_future().await |
There was a problem hiding this comment.
Might be good if we had an async fn function_returning_a_future() {} so the example actually compiles. That applies to both code snippets.
| /// Checks for `async` block that only returns `await` on a future. | ||
| /// | ||
| /// ### Why is this bad? | ||
| /// It is shorter to directly use the future. |
There was a problem hiding this comment.
I'm curious: Does the async await really get reduced to a single call, or even inlined in release mode? If not, there would even be a perf angle.
There was a problem hiding this comment.
I can still see the await process in the MIR expansion in release mode, so I don't think this is optimized away.
| /// ``` | ||
| #[clippy::version = "1.69.0"] | ||
| pub REDUNDANT_ASYNC_BLOCK, | ||
| complexity, |
There was a problem hiding this comment.
Do we have a lintcheck run to ensure we don't get too much churn when introducing this lint?
Nowadays we are quite cautious when adding warn-by-default lints.
There was a problem hiding this comment.
If you are talking about a cargo lintcheck, yes, I ran one (maybe I should mention it in the description). Is there a way to do something similar to a crater run for lint checking?
| cx, | ||
| REDUNDANT_ASYNC_BLOCK, | ||
| expr.span, | ||
| "this async-await expression is equivalent to the awaited future itself", |
There was a problem hiding this comment.
| "this async-await expression is equivalent to the awaited future itself", | |
| "this async expression only awaits a single future", |
| let fut1 = async { 42 }; | ||
| let fut2 = async move { fut1.await }; | ||
|
|
||
| let fut = async { async { 42 }.await }; |
There was a problem hiding this comment.
I'd like to see a test where the future is macro-generated, a test where the whole block outside the future / with the future is macro-generated by an crate-internal/external macro each.
The internal ones may lint, but with the correct snippet; the external ones I think should not lint (because we don't expect people to be able to change code external to their crate). I want to be sure the span.from_expansion() check is sufficient.
There was a problem hiding this comment.
Checking inside a macro needs a bit more work, because the awaiten future must not come from a macro parameter, as it might contain .await itself.
There was a problem hiding this comment.
Right now, it doesn't look into macros at all. I've added a MISSED OPPORTUNITY marker, but I'm not sure how to check inside the macro that no parameter (or another macro) is used as the .await receiver.
| } | ||
| if let ExprKind::Async(_, _, block) = &expr.kind && block.stmts.len() == 1 && | ||
| let Some(Stmt { kind: StmtKind::Expr(last), .. }) = block.stmts.last() && | ||
| let ExprKind::Await(future) = &last.kind |
There was a problem hiding this comment.
A check for future.span.from_expansion() would be good, and an accompanying test for something like
macro_rules! await_in_macro {
($e:expr) => { std::convert::identity($e).await };
}
async { await_in_macro!(foo()) }This could come up with futures::poll like here but having our own macro definition means we don't have to rely on future's implementation staying the same
There was a problem hiding this comment.
You're right, without the test on the future itself the proposed solution involved copying code coming from the macro. Done.
|
In addition to all those comments, there is one check missing: there must be no other async { func1(func2().await)).await }I'll work on that, probably in the next few days. Edit: done, through a visitor |
09cc034 to
cd8f6db
Compare
|
What did your lintcheck run come up with? I might do a run to reproduce and see what it turns up. |
Nothing, as expected. $ cargo lintcheck -j 0 --filter redundant_async_block
[…]
$ cat lintcheck-logs/lintcheck_crates_logs.txt
clippy 0.1.69
### Reports
### Stats:
| lint | count |
| -------------------------------------------------- | ----- |
### ICEs:
You need to rebase this branch on |
|
☔ The latest upstream changes (presumably #10415) made this pull request unmergeable. Please resolve the merge conflicts. |
|
r=me when rebased. |
cd8f6db to
3872d17
Compare
|
Checked with the 500 most recently downloaded crates of crates.io: this lint was not triggered (the complete 470klines report is available at https://rfc1149.net/tmp/top500_logs.txt.gz). |
|
Yeah, I also ran lintcheck. Thanks everyone! @bors r+ |
|
🔒 Merge conflict This pull request and the master branch diverged in a way that cannot be automatically merged. Please rebase on top of the latest master branch, and let the reviewer approve again. How do I rebase?Assuming
You may also read Git Rebasing to Resolve Conflicts by Drew Blessing for a short tutorial. Please avoid the "Resolve conflicts" button on GitHub. It uses Sometimes step 4 will complete without asking for resolution. This is usually due to difference between how Error message |
3872d17 to
d5429ea
Compare
|
@llogiq: rebased |
|
Thanks again! @bors r+ |
|
☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test |
|
#10509 notes that this lint triggers even in cases where the suggestion is a behavior change. |
Fixes #10444
changelog: [
redundant_async_block]: new lint to detectasync { future.await }