From 2742ad1d0a356837ce486373fb7b7586e92c9d8d Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 27 Aug 2024 17:36:12 +0200
Subject: [PATCH 1/6] add async121 control-flow-in-taskgroup
---
docs/changelog.rst | 4 ++
docs/rules.rst | 3 ++
flake8_async/visitors/visitors.py | 52 +++++++++++++++++++++++
tests/eval_files/async121.py | 66 +++++++++++++++++++++++++++++
tests/eval_files/async121_anyio.py | 67 ++++++++++++++++++++++++++++++
tests/test_flake8_async.py | 3 ++
6 files changed, 195 insertions(+)
create mode 100644 tests/eval_files/async121.py
create mode 100644 tests/eval_files/async121_anyio.py
diff --git a/docs/changelog.rst b/docs/changelog.rst
index daefaa6e..4338c748 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,6 +4,10 @@ Changelog
*[CalVer, YY.month.patch](https://calver.org/)*
+24.8.1
+======
+- Add :ref:`ASYNC121 ` control-flow-in-taskgroup
+
24.8.1
======
- Add config option ``transform-async-generator-decorators``, to list decorators which
diff --git a/docs/rules.rst b/docs/rules.rst
index 3eede308..757d7cce 100644
--- a/docs/rules.rst
+++ b/docs/rules.rst
@@ -83,6 +83,9 @@ _`ASYNC120` : await-in-except
This will not trigger when :ref:`ASYNC102 ` does, and if you don't care about losing non-cancelled exceptions you could disable this rule.
This is currently not able to detect asyncio shields.
+_`ASYNC121`: control-flow-in-taskgroup
+ `return`, `continue`, and `break` inside a :ref:`taskgroup_nursery` can lead to counterintuitive behaviour. Refactor the code to instead cancel the :ref:`cancel_scope` and place the statement outside of the TaskGroup/Nursery block. See `trio#1493 `.
+
Blocking sync calls in async functions
======================================
diff --git a/flake8_async/visitors/visitors.py b/flake8_async/visitors/visitors.py
index d6b1363b..f5ef4c1e 100644
--- a/flake8_async/visitors/visitors.py
+++ b/flake8_async/visitors/visitors.py
@@ -350,6 +350,58 @@ def visit_Yield(self, node: ast.Yield):
visit_Lambda = visit_AsyncFunctionDef
+@error_class
+class Visitor121(Flake8AsyncVisitor):
+ error_codes: Mapping[str, str] = {
+ "ASYNC121": (
+ "{0} in a {1} block behaves counterintuitively in several"
+ " situations. Refactor to have the {0} outside."
+ )
+ }
+
+ def __init__(self, *args: Any, **kwargs: Any):
+ super().__init__(*args, **kwargs)
+ self.unsafe_stack: list[str] = []
+
+ def visit_AsyncWith(self, node: ast.AsyncWith):
+ self.save_state(node, "unsafe_stack", copy=True)
+
+ for item in node.items:
+ if get_matching_call(item.context_expr, "open_nursery", base="trio"):
+ self.unsafe_stack.append("nursery")
+ elif get_matching_call(
+ item.context_expr, "create_task_group", base="anyio"
+ ):
+ self.unsafe_stack.append("task group")
+
+ def visit_While(self, node: ast.While | ast.For):
+ self.save_state(node, "unsafe_stack", copy=True)
+ self.unsafe_stack.append("loop")
+
+ visit_For = visit_While
+
+ def check_loop_flow(self, node: ast.Continue | ast.Break, statement: str) -> None:
+ # self.unsafe_stack should never be empty, but no reason not to avoid a crash
+ # for invalid code.
+ if self.unsafe_stack and self.unsafe_stack[-1] != "loop":
+ self.error(node, statement, self.unsafe_stack[-1])
+
+ def visit_Continue(self, node: ast.Continue) -> None:
+ self.check_loop_flow(node, "continue")
+
+ def visit_Break(self, node: ast.Break) -> None:
+ self.check_loop_flow(node, "break")
+
+ def visit_Return(self, node: ast.Return) -> None:
+ for unsafe_cm in "nursery", "task group":
+ if unsafe_cm in self.unsafe_stack:
+ self.error(node, "return", unsafe_cm)
+
+ def visit_FunctionDef(self, node: ast.FunctionDef):
+ self.save_state(node, "unsafe_stack", copy=True)
+ self.unsafe_stack = []
+
+
@error_class_cst
class Visitor300(Flake8AsyncVisitor_cst):
error_codes: Mapping[str, str] = {
diff --git a/tests/eval_files/async121.py b/tests/eval_files/async121.py
new file mode 100644
index 00000000..5986c8db
--- /dev/null
+++ b/tests/eval_files/async121.py
@@ -0,0 +1,66 @@
+# ASYNCIO_NO_ERROR # not a problem in asyncio
+# ANYIO_NO_ERROR # checked in async121_anyio.py
+
+import trio
+
+
+async def foo_return():
+ async with trio.open_nursery():
+ return # ASYNC121: 8, "return", "nursery"
+
+
+async def foo_return_nested():
+ async with trio.open_nursery():
+
+ def bar():
+ return # safe
+
+
+# continue
+async def foo_while_continue_safe():
+ async with trio.open_nursery():
+ while True:
+ continue # safe
+
+
+async def foo_while_continue_unsafe():
+ while True:
+ async with trio.open_nursery():
+ continue # ASYNC121: 12, "continue", "nursery"
+
+
+async def foo_for_continue_safe():
+ async with trio.open_nursery():
+ for _ in range(5):
+ continue # safe
+
+
+async def foo_for_continue_unsafe():
+ for _ in range(5):
+ async with trio.open_nursery():
+ continue # ASYNC121: 12, "continue", "nursery"
+
+
+# break
+async def foo_while_break_safe():
+ async with trio.open_nursery():
+ while True:
+ break # safe
+
+
+async def foo_while_break_unsafe():
+ while True:
+ async with trio.open_nursery():
+ break # ASYNC121: 12, "break", "nursery"
+
+
+async def foo_for_break_safe():
+ async with trio.open_nursery():
+ for _ in range(5):
+ break # safe
+
+
+async def foo_for_break_unsafe():
+ for _ in range(5):
+ async with trio.open_nursery():
+ break # ASYNC121: 12, "break", "nursery"
diff --git a/tests/eval_files/async121_anyio.py b/tests/eval_files/async121_anyio.py
new file mode 100644
index 00000000..01b5244d
--- /dev/null
+++ b/tests/eval_files/async121_anyio.py
@@ -0,0 +1,67 @@
+# ASYNCIO_NO_ERROR # not a problem in asyncio
+# TRIO_NO_ERROR # checked in async121.py
+# BASE_LIBRARY anyio
+
+import anyio
+
+
+async def foo_return():
+ async with anyio.create_task_group():
+ return # ASYNC121: 8, "return", "task group"
+
+
+async def foo_return_nested():
+ async with anyio.create_task_group():
+
+ def bar():
+ return # safe
+
+
+# continue
+async def foo_while_continue_safe():
+ async with anyio.create_task_group():
+ while True:
+ continue # safe
+
+
+async def foo_while_continue_unsafe():
+ while True:
+ async with anyio.create_task_group():
+ continue # ASYNC121: 12, "continue", "task group"
+
+
+async def foo_for_continue_safe():
+ async with anyio.create_task_group():
+ for _ in range(5):
+ continue # safe
+
+
+async def foo_for_continue_unsafe():
+ for _ in range(5):
+ async with anyio.create_task_group():
+ continue # ASYNC121: 12, "continue", "task group"
+
+
+# break
+async def foo_while_break_safe():
+ async with anyio.create_task_group():
+ while True:
+ break # safe
+
+
+async def foo_while_break_unsafe():
+ while True:
+ async with anyio.create_task_group():
+ break # ASYNC121: 12, "break", "task group"
+
+
+async def foo_for_break_safe():
+ async with anyio.create_task_group():
+ for _ in range(5):
+ break # safe
+
+
+async def foo_for_break_unsafe():
+ for _ in range(5):
+ async with anyio.create_task_group():
+ break # ASYNC121: 12, "break", "task group"
diff --git a/tests/test_flake8_async.py b/tests/test_flake8_async.py
index 910df8cd..8d9d94ca 100644
--- a/tests/test_flake8_async.py
+++ b/tests/test_flake8_async.py
@@ -478,6 +478,9 @@ def _parse_eval_file(
"ASYNC116",
"ASYNC117",
"ASYNC118",
+ # opening nurseries & taskgroups can only be done in async context, so ASYNC121
+ # doesn't check for it
+ "ASYNC121",
"ASYNC300",
"ASYNC912",
}
From 459c7483a5a64bd6bd2f78b19d0cddefbf2d9e56 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 27 Aug 2024 17:47:43 +0200
Subject: [PATCH 2/6] bump __version__
---
flake8_async/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flake8_async/__init__.py b/flake8_async/__init__.py
index 9e229e6f..8b253e8e 100644
--- a/flake8_async/__init__.py
+++ b/flake8_async/__init__.py
@@ -38,7 +38,7 @@
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
-__version__ = "24.8.1"
+__version__ = "24.8.2"
# taken from https://github.com/Zac-HD/shed
From ff22623356cde212a5332e3ebdd9df2fb7e338f9 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 27 Aug 2024 17:50:06 +0200
Subject: [PATCH 3/6] aaand fix the version # in the changelog
---
docs/changelog.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 4338c748..6678e14c 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,7 +4,7 @@ Changelog
*[CalVer, YY.month.patch](https://calver.org/)*
-24.8.1
+24.8.2
======
- Add :ref:`ASYNC121 ` control-flow-in-taskgroup
From 402993ea19f23d48d283285e46da1d4cb88f6e18 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 27 Aug 2024 18:08:18 +0200
Subject: [PATCH 4/6] reword the description after feedback from oremanj
---
docs/rules.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/rules.rst b/docs/rules.rst
index 757d7cce..9db42c1e 100644
--- a/docs/rules.rst
+++ b/docs/rules.rst
@@ -84,7 +84,7 @@ _`ASYNC120` : await-in-except
This is currently not able to detect asyncio shields.
_`ASYNC121`: control-flow-in-taskgroup
- `return`, `continue`, and `break` inside a :ref:`taskgroup_nursery` can lead to counterintuitive behaviour. Refactor the code to instead cancel the :ref:`cancel_scope` and place the statement outside of the TaskGroup/Nursery block. See `trio#1493 `.
+ `return`, `continue`, and `break` inside a :ref:`taskgroup_nursery` can lead to counterintuitive behaviour. Refactor the code to instead cancel the :ref:`cancel_scope` inside the TaskGroup/Nursery and place the statement outside of the TaskGroup/Nursery block. See `Trio issue #1493 `.
Blocking sync calls in async functions
From 3435f63fbc201fbaa93730b53391a3eb442ab77d Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 29 Aug 2024 15:04:41 +0200
Subject: [PATCH 5/6] enable rule for asyncio, add more details to rule
explanation. Extend tests to be more thorough with state management.
---
docs/rules.rst | 2 +-
flake8_async/visitors/visitors.py | 2 +-
tests/eval_files/async121.py | 43 ++++++++++++++---
tests/eval_files/async121_anyio.py | 2 +-
tests/eval_files/async121_asyncio.py | 69 ++++++++++++++++++++++++++++
5 files changed, 109 insertions(+), 9 deletions(-)
create mode 100644 tests/eval_files/async121_asyncio.py
diff --git a/docs/rules.rst b/docs/rules.rst
index 9db42c1e..64e6156e 100644
--- a/docs/rules.rst
+++ b/docs/rules.rst
@@ -84,7 +84,7 @@ _`ASYNC120` : await-in-except
This is currently not able to detect asyncio shields.
_`ASYNC121`: control-flow-in-taskgroup
- `return`, `continue`, and `break` inside a :ref:`taskgroup_nursery` can lead to counterintuitive behaviour. Refactor the code to instead cancel the :ref:`cancel_scope` inside the TaskGroup/Nursery and place the statement outside of the TaskGroup/Nursery block. See `Trio issue #1493 `.
+ `return`, `continue`, and `break` inside a :ref:`taskgroup_nursery` can lead to counterintuitive behaviour. Refactor the code to instead cancel the :ref:`cancel_scope` inside the TaskGroup/Nursery and place the statement outside of the TaskGroup/Nursery block. In asyncio a user might expect the statement to have an immediate effect, but it will wait for all tasks to finish before having an effect. See `Trio issue #1493 ` for further issues specific to trio/anyio.
Blocking sync calls in async functions
diff --git a/flake8_async/visitors/visitors.py b/flake8_async/visitors/visitors.py
index f5ef4c1e..a553a0a5 100644
--- a/flake8_async/visitors/visitors.py
+++ b/flake8_async/visitors/visitors.py
@@ -371,7 +371,7 @@ def visit_AsyncWith(self, node: ast.AsyncWith):
self.unsafe_stack.append("nursery")
elif get_matching_call(
item.context_expr, "create_task_group", base="anyio"
- ):
+ ) or get_matching_call(item.context_expr, "TaskGroup", base="asyncio"):
self.unsafe_stack.append("task group")
def visit_While(self, node: ast.While | ast.For):
diff --git a/tests/eval_files/async121.py b/tests/eval_files/async121.py
index 5986c8db..0beb8518 100644
--- a/tests/eval_files/async121.py
+++ b/tests/eval_files/async121.py
@@ -1,12 +1,21 @@
-# ASYNCIO_NO_ERROR # not a problem in asyncio
+# ASYNCIO_NO_ERROR # checked in async121_asyncio.py
# ANYIO_NO_ERROR # checked in async121_anyio.py
import trio
+def condition() -> bool:
+ return False
+
+
async def foo_return():
async with trio.open_nursery():
- return # ASYNC121: 8, "return", "nursery"
+ if condition():
+ return # ASYNC121: 12, "return", "nursery"
+ while condition():
+ return # ASYNC121: 12, "return", "nursery"
+
+ return # safe
async def foo_return_nested():
@@ -26,7 +35,9 @@ async def foo_while_continue_safe():
async def foo_while_continue_unsafe():
while True:
async with trio.open_nursery():
- continue # ASYNC121: 12, "continue", "nursery"
+ if condition():
+ continue # ASYNC121: 16, "continue", "nursery"
+ continue # safe
async def foo_for_continue_safe():
@@ -38,7 +49,9 @@ async def foo_for_continue_safe():
async def foo_for_continue_unsafe():
for _ in range(5):
async with trio.open_nursery():
- continue # ASYNC121: 12, "continue", "nursery"
+ if condition():
+ continue # ASYNC121: 16, "continue", "nursery"
+ continue # safe
# break
@@ -51,7 +64,9 @@ async def foo_while_break_safe():
async def foo_while_break_unsafe():
while True:
async with trio.open_nursery():
- break # ASYNC121: 12, "break", "nursery"
+ if condition():
+ break # ASYNC121: 16, "break", "nursery"
+ continue # safe
async def foo_for_break_safe():
@@ -63,4 +78,20 @@ async def foo_for_break_safe():
async def foo_for_break_unsafe():
for _ in range(5):
async with trio.open_nursery():
- break # ASYNC121: 12, "break", "nursery"
+ if condition():
+ break # ASYNC121: 16, "break", "nursery"
+ continue # safe
+
+
+# nested nursery
+async def foo_nested_nursery():
+ async with trio.open_nursery():
+ if condition():
+ return # ASYNC121: 12, "return", "nursery"
+ async with trio.open_nursery():
+ if condition():
+ return # ASYNC121: 16, "return", "nursery"
+ if condition():
+ return # ASYNC121: 12, "return", "nursery"
+ if condition():
+ return # safe
diff --git a/tests/eval_files/async121_anyio.py b/tests/eval_files/async121_anyio.py
index 01b5244d..2505e23c 100644
--- a/tests/eval_files/async121_anyio.py
+++ b/tests/eval_files/async121_anyio.py
@@ -1,4 +1,4 @@
-# ASYNCIO_NO_ERROR # not a problem in asyncio
+# ASYNCIO_NO_ERROR # checked in async121_asyncio.py
# TRIO_NO_ERROR # checked in async121.py
# BASE_LIBRARY anyio
diff --git a/tests/eval_files/async121_asyncio.py b/tests/eval_files/async121_asyncio.py
new file mode 100644
index 00000000..80b6126d
--- /dev/null
+++ b/tests/eval_files/async121_asyncio.py
@@ -0,0 +1,69 @@
+# ANYIO_NO_ERROR
+# TRIO_NO_ERROR # checked in async121.py
+# BASE_LIBRARY asyncio
+# TaskGroup was added in 3.11, we run type checking with 3.9
+# mypy: disable-error-code=attr-defined
+
+import asyncio
+
+
+async def foo_return():
+ async with asyncio.TaskGroup():
+ return # ASYNC121: 8, "return", "task group"
+
+
+async def foo_return_nested():
+ async with asyncio.TaskGroup():
+
+ def bar():
+ return # safe
+
+
+# continue
+async def foo_while_continue_safe():
+ async with asyncio.TaskGroup():
+ while True:
+ continue # safe
+
+
+async def foo_while_continue_unsafe():
+ while True:
+ async with asyncio.TaskGroup():
+ continue # ASYNC121: 12, "continue", "task group"
+
+
+async def foo_for_continue_safe():
+ async with asyncio.TaskGroup():
+ for _ in range(5):
+ continue # safe
+
+
+async def foo_for_continue_unsafe():
+ for _ in range(5):
+ async with asyncio.TaskGroup():
+ continue # ASYNC121: 12, "continue", "task group"
+
+
+# break
+async def foo_while_break_safe():
+ async with asyncio.TaskGroup():
+ while True:
+ break # safe
+
+
+async def foo_while_break_unsafe():
+ while True:
+ async with asyncio.TaskGroup():
+ break # ASYNC121: 12, "break", "task group"
+
+
+async def foo_for_break_safe():
+ async with asyncio.TaskGroup():
+ for _ in range(5):
+ break # safe
+
+
+async def foo_for_break_unsafe():
+ for _ in range(5):
+ async with asyncio.TaskGroup():
+ break # ASYNC121: 12, "break", "task group"
From ceadc3ddaf109f553df56c3e3498b2630bcb8c50 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 5 Sep 2024 10:40:56 +0200
Subject: [PATCH 6/6] also check AsyncFor, restructure tests
---
docs/changelog.rst | 2 +-
flake8_async/__init__.py | 2 +-
flake8_async/visitors/visitors.py | 3 +-
tests/eval_files/async121.py | 58 +++++++++++++-----------
tests/eval_files/async121_anyio.py | 67 +++++-----------------------
tests/eval_files/async121_asyncio.py | 67 +++++-----------------------
6 files changed, 59 insertions(+), 140 deletions(-)
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 6678e14c..cf750677 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,7 +4,7 @@ Changelog
*[CalVer, YY.month.patch](https://calver.org/)*
-24.8.2
+24.9.1
======
- Add :ref:`ASYNC121 ` control-flow-in-taskgroup
diff --git a/flake8_async/__init__.py b/flake8_async/__init__.py
index 8b253e8e..d4827f86 100644
--- a/flake8_async/__init__.py
+++ b/flake8_async/__init__.py
@@ -38,7 +38,7 @@
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
-__version__ = "24.8.2"
+__version__ = "24.9.1"
# taken from https://github.com/Zac-HD/shed
diff --git a/flake8_async/visitors/visitors.py b/flake8_async/visitors/visitors.py
index a553a0a5..6d9ca23f 100644
--- a/flake8_async/visitors/visitors.py
+++ b/flake8_async/visitors/visitors.py
@@ -374,11 +374,12 @@ def visit_AsyncWith(self, node: ast.AsyncWith):
) or get_matching_call(item.context_expr, "TaskGroup", base="asyncio"):
self.unsafe_stack.append("task group")
- def visit_While(self, node: ast.While | ast.For):
+ def visit_While(self, node: ast.While | ast.For | ast.AsyncFor):
self.save_state(node, "unsafe_stack", copy=True)
self.unsafe_stack.append("loop")
visit_For = visit_While
+ visit_AsyncFor = visit_While
def check_loop_flow(self, node: ast.Continue | ast.Break, statement: str) -> None:
# self.unsafe_stack should never be empty, but no reason not to avoid a crash
diff --git a/tests/eval_files/async121.py b/tests/eval_files/async121.py
index 0beb8518..78a6b6c4 100644
--- a/tests/eval_files/async121.py
+++ b/tests/eval_files/async121.py
@@ -2,12 +2,18 @@
# ANYIO_NO_ERROR # checked in async121_anyio.py
import trio
+from typing import Any
+# To avoid mypy unreachable-statement we wrap control flow calls in if statements
+# they should have zero effect on the visitor logic.
def condition() -> bool:
return False
+def bar() -> Any: ...
+
+
async def foo_return():
async with trio.open_nursery():
if condition():
@@ -25,59 +31,61 @@ def bar():
return # safe
-# continue
-async def foo_while_continue_safe():
+async def foo_while_safe():
async with trio.open_nursery():
while True:
+ if condition():
+ break # safe
+ if condition():
+ continue # safe
continue # safe
-async def foo_while_continue_unsafe():
+async def foo_while_unsafe():
while True:
async with trio.open_nursery():
if condition():
continue # ASYNC121: 16, "continue", "nursery"
- continue # safe
+ if condition():
+ break # ASYNC121: 16, "break", "nursery"
+ if condition():
+ continue # safe
+ break # safe
-async def foo_for_continue_safe():
+async def foo_for_safe():
async with trio.open_nursery():
for _ in range(5):
- continue # safe
+ if condition():
+ continue # safe
+ if condition():
+ break # safe
-async def foo_for_continue_unsafe():
+async def foo_for_unsafe():
for _ in range(5):
async with trio.open_nursery():
if condition():
continue # ASYNC121: 16, "continue", "nursery"
- continue # safe
-
-
-# break
-async def foo_while_break_safe():
- async with trio.open_nursery():
- while True:
- break # safe
-
-
-async def foo_while_break_unsafe():
- while True:
- async with trio.open_nursery():
if condition():
break # ASYNC121: 16, "break", "nursery"
continue # safe
-async def foo_for_break_safe():
+async def foo_async_for_safe():
async with trio.open_nursery():
- for _ in range(5):
- break # safe
+ async for _ in bar():
+ if condition():
+ continue # safe
+ if condition():
+ break # safe
-async def foo_for_break_unsafe():
- for _ in range(5):
+async def foo_async_for_unsafe():
+ async for _ in bar():
async with trio.open_nursery():
+ if condition():
+ continue # ASYNC121: 16, "continue", "nursery"
if condition():
break # ASYNC121: 16, "break", "nursery"
continue # safe
diff --git a/tests/eval_files/async121_anyio.py b/tests/eval_files/async121_anyio.py
index 2505e23c..12be0aed 100644
--- a/tests/eval_files/async121_anyio.py
+++ b/tests/eval_files/async121_anyio.py
@@ -5,63 +5,18 @@
import anyio
-async def foo_return():
- async with anyio.create_task_group():
- return # ASYNC121: 8, "return", "task group"
-
-
-async def foo_return_nested():
- async with anyio.create_task_group():
-
- def bar():
- return # safe
-
-
-# continue
-async def foo_while_continue_safe():
- async with anyio.create_task_group():
- while True:
- continue # safe
-
-
-async def foo_while_continue_unsafe():
- while True:
- async with anyio.create_task_group():
- continue # ASYNC121: 12, "continue", "task group"
-
-
-async def foo_for_continue_safe():
- async with anyio.create_task_group():
- for _ in range(5):
- continue # safe
+# To avoid mypy unreachable-statement we wrap control flow calls in if statements
+# they should have zero effect on the visitor logic.
+def condition() -> bool:
+ return False
-async def foo_for_continue_unsafe():
- for _ in range(5):
- async with anyio.create_task_group():
- continue # ASYNC121: 12, "continue", "task group"
-
-
-# break
-async def foo_while_break_safe():
- async with anyio.create_task_group():
- while True:
- break # safe
-
-
-async def foo_while_break_unsafe():
+# only tests that asyncio.TaskGroup is detected, main tests in async121.py
+async def foo_return():
while True:
async with anyio.create_task_group():
- break # ASYNC121: 12, "break", "task group"
-
-
-async def foo_for_break_safe():
- async with anyio.create_task_group():
- for _ in range(5):
- break # safe
-
-
-async def foo_for_break_unsafe():
- for _ in range(5):
- async with anyio.create_task_group():
- break # ASYNC121: 12, "break", "task group"
+ if condition():
+ continue # ASYNC121: 16, "continue", "task group"
+ if condition():
+ break # ASYNC121: 16, "break", "task group"
+ return # ASYNC121: 12, "return", "task group"
diff --git a/tests/eval_files/async121_asyncio.py b/tests/eval_files/async121_asyncio.py
index 80b6126d..e8b8e275 100644
--- a/tests/eval_files/async121_asyncio.py
+++ b/tests/eval_files/async121_asyncio.py
@@ -7,63 +7,18 @@
import asyncio
-async def foo_return():
- async with asyncio.TaskGroup():
- return # ASYNC121: 8, "return", "task group"
-
-
-async def foo_return_nested():
- async with asyncio.TaskGroup():
-
- def bar():
- return # safe
-
-
-# continue
-async def foo_while_continue_safe():
- async with asyncio.TaskGroup():
- while True:
- continue # safe
-
-
-async def foo_while_continue_unsafe():
- while True:
- async with asyncio.TaskGroup():
- continue # ASYNC121: 12, "continue", "task group"
-
-
-async def foo_for_continue_safe():
- async with asyncio.TaskGroup():
- for _ in range(5):
- continue # safe
+# To avoid mypy unreachable-statement we wrap control flow calls in if statements
+# they should have zero effect on the visitor logic.
+def condition() -> bool:
+ return False
-async def foo_for_continue_unsafe():
- for _ in range(5):
- async with asyncio.TaskGroup():
- continue # ASYNC121: 12, "continue", "task group"
-
-
-# break
-async def foo_while_break_safe():
- async with asyncio.TaskGroup():
- while True:
- break # safe
-
-
-async def foo_while_break_unsafe():
+# only tests that asyncio.TaskGroup is detected, main tests in async121.py
+async def foo_return():
while True:
async with asyncio.TaskGroup():
- break # ASYNC121: 12, "break", "task group"
-
-
-async def foo_for_break_safe():
- async with asyncio.TaskGroup():
- for _ in range(5):
- break # safe
-
-
-async def foo_for_break_unsafe():
- for _ in range(5):
- async with asyncio.TaskGroup():
- break # ASYNC121: 12, "break", "task group"
+ if condition():
+ continue # ASYNC121: 16, "continue", "task group"
+ if condition():
+ break # ASYNC121: 16, "break", "task group"
+ return # ASYNC121: 12, "return", "task group"