Skip to content
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

[ruff] Implement missing await for coroutine (RUF028) #9911

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

mikeleppane
Copy link
Contributor

@mikeleppane mikeleppane commented Feb 9, 2024

Summary

Detect never-awaited coroutines

This rule only applies to simple scenarios. We need to be quite conservative on how to handle coroutines. Because there are many situations in which the programmer does not want to evaluate/await a coroutine but instead use the actual coroutine object.

Closes #9833

Test Plan

cargo test

Copy link
Contributor

github-actions bot commented Feb 9, 2024

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Copy link

codspeed-hq bot commented Feb 9, 2024

CodSpeed Performance Report

Merging #9911 will not alter performance

Comparing mikeleppane:rule(RUF028)/detect-unused-await (0275ffd) with main (8ec5627)

Summary

✅ 30 untouched benchmarks

@zanieb
Copy link
Member

zanieb commented Feb 9, 2024

Thanks for contributing!

It looks like the ecosystem checks are showing some false positives e.g. https://github.com/apache/airflow/blob/48bfb1a970f5b47ba1b385ad809b8324923ddf3e/tests/providers/google/cloud/hooks/test_dataflow.py#L1956 — could you look into those?

@mikeleppane
Copy link
Contributor Author

Thanks for contributing!

It looks like the ecosystem checks are showing some false positives e.g. https://github.com/apache/airflow/blob/48bfb1a970f5b47ba1b385ad809b8324923ddf3e/tests/providers/google/cloud/hooks/test_dataflow.py#L1956 — could you look into those?

Thanks! Good catch.

@zanieb zanieb self-requested a review February 12, 2024 21:10
@MichaReiser MichaReiser added the accepted Ready for implementation label Apr 5, 2024
@MichaReiser
Copy link
Member

This rule fits into ruff as a suspicious rule. I think we should rename the rule to never-awaited-coroutines so that allow(never-awaited-coroutines) reads better.

pass
async def coro():
return another_coro()
await coro() # OK
Copy link

@earonesty earonesty Apr 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this ok? another_coro is never awaited. if you await coro() you will get an unevaluated coro.

this works tho:

async def test_coroutine_with_sync_return():
    async def another_coro():
        pass
    def coro():
        return another_coro()
    await coro()  # OK

@akselerando
Copy link

Hi, any update on when this will be released?

Copy link
Member

@dhruvmanila dhruvmanila left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for implementing this rule!

Can either @zanieb or @AlexWaygood look at this? It's good to go from my side but I've mainly looked at the code part of the rule as I'm not an async expert :)

Comment on lines +131 to +135
/// Generate a [`Fix`] to add `await` for coroutine.
///
/// For example:
/// - Given `asyncio.sleep(1)`, generate `await asyncio.sleep(1)`.
fn generate_fix(call: &ExprCall) -> Expr {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function docs doesn't match what it actually returns. I think we should either return the Fix as mentioned in the docs or update the name to something like create_await_expr. I would recommend to go with the former.

return;
}

let mut diagnostic = Diagnostic::new(MissingAwaitForCoroutine {}, call.range);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The curly braces aren't required for empty structs

Suggested change
let mut diagnostic = Diagnostic::new(MissingAwaitForCoroutine {}, call.range);
let mut diagnostic = Diagnostic::new(MissingAwaitForCoroutine, call.range);

Comment on lines +63 to +67
// Try to detect possible scenarios where await is missing and ignore other cases
// For example, if the call is not a direct child of an statement expression or assignment statement
// then it's not reliable to determine if await is missing.
// User might return coroutine object from a function or pass it as an argument
if !possibly_missing_await(call, checker.semantic()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be useful to move the comment as part of possibly_missing_await's documentation.

Comment on lines +81 to +84
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&generate_fix(call)),
call.range,
)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we could possibly just create an Insertion edit instead at the start of the call expression.

Suggested change
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
checker.generator().expr(&generate_fix(call)),
call.range,
)));
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
"await ".to_string(),
call.start(),
)));

which is basically what the generator does as well ;)

Expr::Await(ast::ExprAwait { value, range: _ }) => {
group_if!(precedence::AWAIT, {
self.p("await ");
self.unparse_expr(value, precedence::MAX);
});
}

Comment on lines +64 to +66
// For example, if the call is not a direct child of an statement expression or assignment statement
// then it's not reliable to determine if await is missing.
// User might return coroutine object from a function or pass it as an argument
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Do you think it would be helpful for the user to know this limitation? If so, we could possibly provide a "Limitations" section in the rule documentation. For example, refer https://docs.astral.sh/ruff/rules/implicit-optional/#limitations.

@dhruvmanila dhruvmanila added rule Implementing or modifying a lint rule preview Related to preview mode features labels May 1, 2024
@dhruvmanila dhruvmanila changed the title rule(RUF028) Implement missing await for coroutine [ruff] Implement missing await for coroutine (RUF028) May 1, 2024
@akselerando
Copy link

Hi again, when can we expect this to be released?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted Ready for implementation preview Related to preview mode features rule Implementing or modifying a lint rule
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Detect unawaited coroutines
7 participants