-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
fix: refactor sem analysis of the await-not-async on generators and list comprehension #18152
base: master
Are you sure you want to change the base?
fix: refactor sem analysis of the await-not-async on generators and list comprehension #18152
Conversation
dacc60f
to
ada4720
Compare
This comment has been minimized.
This comment has been minimized.
2ffa91e
to
43fc65a
Compare
63bad66
to
e3004a1
Compare
for more information, see https://pre-commit.ci
This comment has been minimized.
This comment has been minimized.
mypy/message_registry.py
Outdated
@@ -80,6 +80,8 @@ def with_additional_msg(self, info: str) -> ErrorMessage: | |||
ASYNC_FOR_OUTSIDE_COROUTINE: Final = '"async for" outside async function' | |||
ASYNC_WITH_OUTSIDE_COROUTINE: Final = '"async with" outside async function' | |||
|
|||
AWAIT_WITH_OUTSIDE_COROUTINE: Final = '"await" outside coroutine ("async def")' |
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 prefer this message to be consistent with previous two: "await" outside async function
, but probably that isn't worth changing an existing message.
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.
Yes, I moved it here and kept the same message that was defined before. Took the liberty to now also provide another key in here for the hardcoded message for "await outside functions" that was as well present as a string when we visit Await Expressions specifically.
mypy/message_registry.py
Outdated
@@ -80,6 +80,8 @@ def with_additional_msg(self, info: str) -> ErrorMessage: | |||
ASYNC_FOR_OUTSIDE_COROUTINE: Final = '"async for" outside async function' | |||
ASYNC_WITH_OUTSIDE_COROUTINE: Final = '"async with" outside async function' | |||
|
|||
AWAIT_WITH_OUTSIDE_COROUTINE: Final = '"await" outside coroutine ("async def")' |
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.
Did you really intend to call it AWAIT_WITH_OUTSIDE_COROUTINE
? This looks like a copying issue to me, why WITH
? And please remove the blank line before this defn, those three messages are related to each other
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.
indeed was a yanking error. thank you for pointing that out.
expr, | ||
code=codes.AWAIT_NOT_ASYNC, | ||
serious=True, | ||
) | ||
|
||
expr.generator.accept(self) | ||
|
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.
Shouldn't visit_set_comprehension
below use the same logic?
@@ -6090,7 +6104,12 @@ def visit_set_comprehension(self, expr: SetComprehension) -> None: | |||
def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> None: |
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.
And here as well? Probably worth extracting into a helper method
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.
extracted it to a small method and apply the check on both dict and list comprehensions visits
@@ -1013,13 +1013,17 @@ async def foo(x: int) -> int: ... | |||
|
|||
# These are allowed in some cases: | |||
top_level = await foo(1) # E: "await" outside function [top-level-await] | |||
crasher = [await foo(x) for x in [1, 2, 3]] # E: "await" outside function [top-level-await] | |||
crasher = [await foo(x) for x in [1, 2, 3]] # E: "await" outside coroutine ("async def") [await-not-async] \ |
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.
More explicit tests would be really helpful here. Consider at least checking all of the following in different scopes (module level, non-async function body and async function body at least; the former two should produce the same errors and async function should allow all of those):
(await x for x in xs) # OK
[await x for x in xs] # E
{await x for x in xs} # E
{0: await x for x in xs} # E
{await x: 0 for x in xs} # E
(x async for x in xs) # OK
[x async for x in xs] # E
{x async for x in xs} # E
{x: 0 async for x in xs} # E
(x for x in await xs) # E
[x for x in await xs] # E
{x for x in await xs} # E
{x: 0 for x in await xs} # E
There's already testAsyncForOutsideCoroutine
for the 2nd block, the 3rd one is probably not interesting (follows regular rules), but the 1st one isn't exercised yet.
for more information, see https://pre-commit.ci
Thank you for the review @sterliakov 🙏 I have now pushed the suggested changes |
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
@@ -6085,12 +6107,21 @@ def visit_set_comprehension(self, expr: SetComprehension) -> None: | |||
if not self.is_func_scope() or not self.function_stack[-1].is_coroutine: | |||
self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX) |
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.
There's now a minor inconsistency - serious=True
is passed everywhere except this call.
self.fail( | ||
message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, | ||
expr, | ||
code=codes.SYNTAX, |
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 don't really like the use of codes.SYNTAX
vs codes.AWAIT_NOT_ASYNC
here. Prior to this PR AWAIT_NOT_ASYNC
was only used once.
As far as I understand, the purpose of using a special code was to support "specific" environments: e.g. IPython allows top-level await
s, so it should be easy to disable such a check when checking IPython snippets.
I see two possible resolutions here:
- Declare that we only type-check true python code. Then
codes.AWAIT_NOT_ASYNC
can be safely removed altogether and replaced withcodes.SYNTAX
- Declare that we want to support such environments. Then all these checks become more complicated: when at module scope (note it isn't same as
not self.is_func_scope()
), producecodes.AWAIT_NOT_ASYNC
, otherwise producecodes.SYNTAX
. This can also be extracted into a helper likedef require_async_scope(self, message)
. This logic is close to whatvisit_await_expr
was doing before.
I somewhat prefer 2 as that isn't that much work to do, but can live with 1 either.
Disclaimer: please note I'm not a mypy
maintainer and just try to help sometimes. You'd better ask for a maintainer's opinion here: @brianschubert do you have any preference?
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.
OTOH 2 can also be misleading and deserves an explicit mention in the docs.
|
||
def bad() -> None: | ||
# These are always critical / syntax issues: | ||
y = [await foo(x) for x in [1, 2, 3]] # E: "await" outside coroutine ("async def") [await-not-async] | ||
async def good() -> None: | ||
y = [await foo(x) for x in [1, 2, 3]] # OK | ||
|
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 is an artefact of --update-data
- please remove
Fixes #18124
This PR proposes the introduction of two extra checks in order to error or any
await
orasync
within list comprehensions but not when inside aGeneratorExpression
.To achieve this we propose:
ListComprehension
we should also check for the existing of anawait
expression and error outside a courotineDictionaryComprehension
AwaitExpression
we add an extra check to verify if it is inside aGeneratorExpression
to not fail