-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
fix: refactor sem analysis of the await-not-async on generators and list comprehension #18152
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
base: master
Are you sure you want to change the base?
Changes from 7 commits
d3b2d9d
ed2937a
c97bce7
ada4720
43fc65a
e3004a1
e7ef1f6
0c82fb9
8e8298e
0fee8d6
95c5730
48148ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -228,6 +228,7 @@ | |
| set_callable_name as set_callable_name, | ||
| ) | ||
| from mypy.semanal_typeddict import TypedDictAnalyzer | ||
| from mypy.traverser import has_await_expression | ||
| from mypy.tvar_scope import TypeVarLikeScope | ||
| from mypy.typeanal import ( | ||
| SELF_TYPE_NAMES, | ||
|
|
@@ -6076,7 +6077,20 @@ def visit_type_application(self, expr: TypeApplication) -> None: | |
| def visit_list_comprehension(self, expr: ListComprehension) -> None: | ||
| if any(expr.generator.is_async): | ||
| 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) | ||
| self.fail( | ||
| message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, | ||
| expr, | ||
| code=codes.SYNTAX, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really like the use of As far as I understand, the purpose of using a special code was to support "specific" environments: e.g. IPython allows top-level I see two possible resolutions here:
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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for the review and explanation, I understand your point so thank you for clarifying it. Will then wait for any guidance on how to proceed on any of these two options. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is an important enough problem to change our stance on allowing top-level awaits, so you're probably safer to go ahead with option 2 for now. A bare
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think 2 is better, and there's precedent for switching around error codes with missing return statements. I also don't think anyone with authority is going to provide an opinion... def f() -> str: # E: Missing return statement [empty-body]
pass
def g() -> str: # E: Missing return statement [return]
if True:
pass |
||
| serious=True, | ||
| ) | ||
| elif has_await_expression(expr): | ||
| if not self.is_func_scope() or not self.function_stack[-1].is_coroutine: | ||
| self.fail( | ||
| message_registry.AWAIT_WITH_OUTSIDE_COROUTINE, | ||
| expr, | ||
| code=codes.AWAIT_NOT_ASYNC, | ||
| serious=True, | ||
| ) | ||
|
|
||
| expr.generator.accept(self) | ||
|
|
||
josetapadas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
@@ -6090,7 +6104,12 @@ def visit_set_comprehension(self, expr: SetComprehension) -> None: | |
| def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> None: | ||
josetapadas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if any(expr.is_async): | ||
| 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) | ||
| self.fail( | ||
| message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, | ||
| expr, | ||
| code=codes.SYNTAX, | ||
| serious=True, | ||
| ) | ||
|
|
||
| with self.enter(expr): | ||
| self.analyze_comp_for(expr) | ||
|
|
@@ -6167,9 +6186,12 @@ def visit_await_expr(self, expr: AwaitExpr) -> None: | |
| # This is not a blocker, because some enviroments (like ipython) | ||
| # support top level awaits. | ||
| self.fail('"await" outside function', expr, serious=True, code=codes.TOP_LEVEL_AWAIT) | ||
| elif not self.function_stack[-1].is_coroutine: | ||
| elif ( | ||
| not self.function_stack[-1].is_coroutine | ||
| and self.scope_stack[-1] != SCOPE_COMPREHENSION | ||
| ): | ||
| self.fail( | ||
| '"await" outside coroutine ("async def")', | ||
| message_registry.AWAIT_WITH_OUTSIDE_COROUTINE, | ||
| expr, | ||
| serious=True, | ||
| code=codes.AWAIT_NOT_ASYNC, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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] \ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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} # EThere's already |
||
| # E: "await" outside function [top-level-await] | ||
|
|
||
| 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 | ||
|
|
||
josetapadas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| [builtins fixtures/async_await.pyi] | ||
| [typing fixtures/typing-async.pyi] | ||
|
|
||
|
|
@@ -1080,3 +1084,13 @@ class Launcher(P): | |
|
|
||
| [builtins fixtures/async_await.pyi] | ||
| [typing fixtures/typing-async.pyi] | ||
|
|
||
| [case testAwaitInsideGeneratorExpr] | ||
| def foo(): | ||
| yield 0 | ||
|
|
||
| def bar(): | ||
| (await x for x in foo()) | ||
|
|
||
| [builtins fixtures/async_await.pyi] | ||
| [typing fixtures/typing-async.pyi] | ||
Uh oh!
There was an error while loading. Please reload this page.