-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add new redundant_async_block
lint
#10448
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
Conversation
r? @llogiq (rustbot has picked a reviewer for you, use r? to override) |
984422a
to
1bd895a
Compare
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be good if we had an async fn function_returning_a_future() {}
so the example actually compiles. That applies to both code snippets.
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.
Done
/// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"this async-await expression is equivalent to the awaited future itself", | |
"this async expression only awaits a single future", |
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.
Done.
let fut1 = async { 42 }; | ||
let fut2 = async move { fut1.await }; | ||
|
||
let fut = async { async { 42 }.await }; |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
That's all right. 👍
} | ||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 }