Skip to content

Commit db5a8c2

Browse files
committed
eyyy stuff just works now
1 parent 9ec8639 commit db5a8c2

2 files changed

Lines changed: 29 additions & 24 deletions

File tree

flake8_async/visitors/visitors.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -254,22 +254,17 @@ def is_nursery_call(node: ast.expr) -> str | None:
254254
self.potential_errors[var].append(node)
255255

256256
def visit_AsyncWith(self, node: ast.AsyncWith | ast.With):
257-
self.visit_nodes(node.items)
258-
self.visit_nodes(node.body)
259-
for item in node.items:
260-
if (
261-
get_matching_call(item.context_expr, "open_nursery", base="trio")
262-
or get_matching_call(
263-
item.context_expr, "create_task_group", base="anyio"
264-
)
265-
or get_matching_call(item.context_expr, "TaskGroup", base="asyncio")
266-
) and isinstance(item.optional_vars, ast.Name):
267-
268-
self.potential_errors.pop(item.optional_vars.id, None)
257+
# Entirely skip any nurseries that doesn't have any yields in them.
258+
# This fixes an otherwise very thorny false alarm.
259+
# In the worst case this does mean we iterate over the body twice, but might
260+
# actually be a performance gain on average due to setting `novisit`
261+
if not any(isinstance(n, ast.Yield) for b in node.body for n in ast.walk(b)):
262+
self.novisit = True
263+
return
269264

270265
# open_nursery/create_task_group only works with AsyncWith, but in case somebody
271-
# is doing something very weird we'll be conservative and possibly clear
272-
# some potential errors
266+
# is doing something very weird we'll be conservative and possibly avoid
267+
# some potential false alarms
273268
visit_With = visit_AsyncWith
274269

275270

tests/eval_files/async113.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,10 @@ async def false_alarm():
150150
yield
151151

152152

153-
# this would ideally error, but not worth the extra logic
154153
@asynccontextmanager
155154
async def should_error():
156155
async with trio.open_nursery() as nursery:
157-
nursery.start_soon(my_startable)
156+
nursery.start_soon(my_startable) # ASYNC113: 8
158157
# overrides the nursery variable
159158
async with trio.open_nursery() as nursery:
160159
nursery.start_soon(my_startable)
@@ -169,14 +168,25 @@ async def foo_sync_with_closed():
169168
yield
170169

171170

172-
# this one is surprisingly hard to fix
173-
# the easiest way would be with a leave_AsyncFunctionDef, but that's only a thing in
174-
# cst visitors; and we can't do the standard thing of manually visiting children in
175-
# visit_AsyncFunctionDef because we need the interleaving of the utility visitor.
176-
# So I either need to convert the visitor to cst, including VisitorTypeTracker, or
177-
# add logic to Flake8AsyncRunner to also support leave_XXX.
178-
# The latter is probably not too bad but... a tomorrow problem
171+
# fixed by entirely skipping nurseries without yields in them
179172
class FalseAlarm:
180173
async def __aenter__(self):
181174
with trio.open_nursery() as nursery:
182-
nursery.start_soon(my_startable) # ASYNC113: 12
175+
nursery.start_soon(my_startable)
176+
177+
178+
@asynccontextmanager
179+
async def yield_before_start_soon():
180+
with trio.open_nursery() as bar:
181+
yield
182+
bar.start_soon(my_startable)
183+
184+
185+
# This was broken when visit_AsyncWith manually visited subnodes due to not
186+
# letting TypeTrackerVisitor interject.
187+
@asynccontextmanager
188+
async def nested():
189+
with trio.open_nursery() as foo:
190+
with trio.open_nursery() as bar:
191+
bar.start_soon(my_startable) # error: 12
192+
yield

0 commit comments

Comments
 (0)