diff --git a/docs/changelog.rst b/docs/changelog.rst
index 1064da0..3035456 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,6 +4,11 @@ Changelog
`CalVer, YY.month.patch `_
+25.8.1
+======
+- Added :ref:`ASYNC914 ` redundant-lowlevel-checkpoint.
+- :ref:`ASYNC910 `, :ref:`ASYNC911 ` and :ref:`ASYNC913 ` can now autofix asyncio code by inserting `asyncio.await(0)`.
+
25.7.1
======
- :ref:`ASYNC102 ` no longer triggered for asyncio due to different cancellation semantics it uses.
diff --git a/docs/rules.rst b/docs/rules.rst
index afc7943..7b4dd1c 100644
--- a/docs/rules.rst
+++ b/docs/rules.rst
@@ -220,6 +220,10 @@ _`ASYNC913` : indefinite-loop-no-guaranteed-checkpoint
An indefinite loop (e.g. ``while True``) has no guaranteed :ref:`checkpoint `. This could potentially cause a deadlock.
This will also error if there's no guaranteed :ref:`cancel point `, where even though it won't deadlock the loop might become an uncancelable dry-run loop.
+_`ASYNC914`: redundant-lowlevel-checkpoint
+ Warns on calls to :func:`trio.lowlevel.checkpoint`, :func:`anyio.lowlevel.checkpoint` and :func:`asyncio.sleep` that are not required to satisfy `ASYNC910`_, `ASYNC911`_ and `ASYNC912`_.
+ Excessive calls to the scheduler will impact performance, and bloat the code, but sometimes you do want to trigger checkpoints at specific points and can safely ignore this warning.
+
.. _autofix-support:
Autofix support
diff --git a/docs/usage.rst b/docs/usage.rst
index 4682799..96ea0e7 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -33,7 +33,7 @@ adding the following to your ``.pre-commit-config.yaml``:
minimum_pre_commit_version: '2.9.0'
repos:
- repo: https://github.com/python-trio/flake8-async
- rev: 25.7.1
+ rev: 25.8.1
hooks:
- id: flake8-async
# args: ["--enable=ASYNC100,ASYNC112", "--disable=", "--autofix=ASYNC"]
diff --git a/flake8_async/__init__.py b/flake8_async/__init__.py
index 89eb93d..f50ccf5 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__ = "25.7.1"
+__version__ = "25.8.1"
# taken from https://github.com/Zac-HD/shed
diff --git a/flake8_async/visitors/visitor91x.py b/flake8_async/visitors/visitor91x.py
index 433c375..bfd6c66 100644
--- a/flake8_async/visitors/visitor91x.py
+++ b/flake8_async/visitors/visitor91x.py
@@ -6,6 +6,7 @@
* ASYNC911 async-generator-no-checkpoint
* ASYNC912 cancel-scope-no-guaranteed-checkpoint
* ASYNC913 indefinite-loop-no-guaranteed-checkpoint
+* ASYNC914 redundant-lowlevel-checkpoint
Visitor124 contains
* ASYNC124 async-function-could-be-sync
@@ -178,6 +179,15 @@ class LoopState:
cst.Return | cst.Yield | ArtificialStatement
] = field(default_factory=list)
+ # fmt: off
+ # https://github.com/psf/black/issues/4733
+ possibly_redundant_lowlevel_checkpoints: list[ # pyright: ignore[reportUnknownVariableType]
+ cst.BaseExpression
+ ] = field(
+ default_factory=list
+ )
+ # fmt: on
+
def copy(self):
return LoopState(
infinite_loop=self.infinite_loop,
@@ -187,6 +197,7 @@ def copy(self):
uncheckpointed_before_break=self.uncheckpointed_before_break.copy(),
artificial_errors=self.artificial_errors.copy(),
nodes_needing_checkpoints=self.nodes_needing_checkpoints.copy(),
+ possibly_redundant_lowlevel_checkpoints=self.possibly_redundant_lowlevel_checkpoints.copy(),
)
@@ -233,10 +244,11 @@ def copy(self):
def checkpoint_statement(library: str) -> cst.SimpleStatementLine:
# logic before this should stop code from wanting to insert the non-existing
# asyncio.lowlevel.checkpoint
- assert library != "asyncio"
- return cst.SimpleStatementLine(
- [cst.Expr(cst.parse_expression(f"await {library}.lowlevel.checkpoint()"))]
- )
+ if library == "asyncio":
+ expr = "await asyncio.sleep(0)"
+ else:
+ expr = f"await {library}.lowlevel.checkpoint()"
+ return cst.SimpleStatementLine([cst.Expr(cst.parse_expression(expr))])
class CommonVisitors(cst.CSTTransformer, ABC):
@@ -256,6 +268,7 @@ def __init__(self):
self.explicitly_imported_library: dict[str, bool] = {
"trio": False,
"anyio": False,
+ "asyncio": False,
}
self.add_import: set[str] = set()
@@ -371,7 +384,7 @@ def leave_Yield(
leave_Return = leave_Yield # type: ignore
-disable_codes_by_default("ASYNC910", "ASYNC911", "ASYNC912", "ASYNC913")
+disable_codes_by_default("ASYNC910", "ASYNC911", "ASYNC912", "ASYNC913", "ASYNC914")
@dataclass
@@ -382,6 +395,37 @@ class ContextManager:
column: int | None = None
+class LowlevelCheckpointsRemover(cst.CSTTransformer):
+ def __init__(self, stmts_to_remove: set[cst.Await]):
+ super().__init__()
+ self.stmts_to_remove = stmts_to_remove
+
+ def leave_Await(
+ self, original_node: cst.Await, updated_node: cst.Await
+ ) -> cst.Await:
+ # return original node to preserve identity
+ return original_node
+
+ # we can return RemovalSentinel from Await, so instead need to remove the line.
+ # This would break the code if it's the only line in a block, but currently the logic
+ # would only ever trigger on multi-statement blocks so we don't need to worry.
+
+ def leave_SimpleStatementLine(
+ self,
+ original_node: cst.SimpleStatementLine,
+ updated_node: cst.SimpleStatementLine,
+ ) -> cst.SimpleStatementLine | cst.RemovalSentinel:
+ if (
+ len(original_node.body) == 1
+ and isinstance(original_node.body[0], cst.Expr)
+ and (stmt := original_node.body[0].value) in self.stmts_to_remove
+ ):
+ # type checker fails to infer type narrowing from membership check
+ self.stmts_to_remove.discard(stmt) # type: ignore[arg-type]
+ return cst.RemoveFromParent()
+ return original_node
+
+
@error_class_cst
class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
error_codes: Mapping[str, str] = {
@@ -402,6 +446,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
"{0}.{1} context contains no checkpoints, remove the context or add"
" `await {0}.lowlevel.checkpoint()`."
),
+ "ASYNC914": "Redundant checkpoint with no effect on program execution.",
}
def __init__(self, *args: Any, **kwargs: Any):
@@ -410,6 +455,15 @@ def __init__(self, *args: Any, **kwargs: Any):
self.async_function = False
self.uncheckpointed_statements: set[Statement] = set()
self.comp_unknown = False
+ self.checkpointed_by_lowlevel = False
+
+ # value == False, not redundant (or not determined to be redundant yet)
+ # value == True, there were no uncheckpointed statements when we encountered it
+ # value = expr/stmt, made redundant by the given expr/stmt
+ self.lowlevel_checkpoints: dict[
+ cst.Await, cst.BaseStatement | cst.BaseExpression | cst.Raise | bool
+ ] = {}
+ self.lowlevel_checkpoint_updated_nodes: dict[cst.Await, cst.Await] = {}
self.loop_state = LoopState()
self.try_state = TryState()
@@ -429,11 +483,7 @@ def should_autofix(self, node: cst.CSTNode, code: str | None = None) -> bool:
if code is None:
code = "ASYNC911" if self.has_yield else "ASYNC910"
- return (
- not self.noautofix
- and super().should_autofix(node, code)
- and self.library != ("asyncio",)
- )
+ return not self.noautofix and super().should_autofix(node, code)
def checkpoint_cancel_point(self) -> None:
for cm in reversed(self.has_checkpoint_stack):
@@ -508,6 +558,9 @@ def visit_FunctionDef(self, node: cst.FunctionDef) -> bool:
"has_yield",
"async_function",
"uncheckpointed_statements",
+ "lowlevel_checkpoints",
+ "checkpointed_by_lowlevel",
+ "lowlevel_checkpoint_updated_nodes",
# comp_unknown does not need to be saved
"loop_state",
"try_state",
@@ -570,10 +623,27 @@ def leave_FunctionDef(
if self.new_body is not None:
updated_node = updated_node.with_changes(body=self.new_body)
+
+ res: cst.FunctionDef = updated_node
+ to_remove: set[cst.Await] = set()
+ for expr, value in self.lowlevel_checkpoints.items():
+ if (
+ value is not False
+ and self.error(expr, error_code="ASYNC914")
+ and self.should_autofix(original_node, code="ASYNC914")
+ ):
+ to_remove.add(self.lowlevel_checkpoint_updated_nodes.pop(expr))
+
+ if to_remove:
+ remover = LowlevelCheckpointsRemover(to_remove)
+ visited = updated_node.visit(remover)
+ assert isinstance(visited, cst.FunctionDef)
+ res = visited
+
self.restore_state(original_node)
# reset self.new_body
self.new_body = None
- return updated_node
+ return res
# error if function exit/return/yields with uncheckpointed statements
# returns a bool indicating if any real (i.e. not artificial) errors were raised
@@ -651,18 +721,56 @@ def error_91x(
error_code="ASYNC911" if self.has_yield else "ASYNC910",
)
+ def is_lowlevel_checkpoint(self, node: cst.BaseExpression) -> bool:
+ return m.matches(
+ node,
+ m.Call(
+ m.Attribute(
+ m.Attribute(m.Name("trio") | m.Name("anyio"), m.Name("lowlevel")),
+ m.Name("checkpoint"),
+ )
+ )
+ | m.Call(
+ m.Attribute(m.Name("asyncio"), m.Name("sleep")), [m.Arg(m.Integer("0"))]
+ ),
+ )
+
def leave_Await(
- self, original_node: cst.Await, updated_node: cst.Await
+ self, original_node: cst.Await | cst.Raise, updated_node: cst.Await | cst.Raise
) -> cst.Await:
- # the expression being awaited is not checkpointed
- # so only set checkpoint after the await node
+ # The expression being awaited is not checkpointed
+ # so we place all logic in `leave_Await` instead of `visit_Await`.
+
+ # ASYNC914
+ # do a match against the awaited expr
+ # if that is trio.lowlevel.checkpoint, and uncheckpointed statements
+ # are empty, raise ASYNC914.
+ # To avoid false alarms we avoid marking the first checkpoint in
+ # a loop as redundant.
+ if (
+ isinstance(original_node, cst.Await)
+ and self.is_lowlevel_checkpoint(original_node.expression)
+ and self.uncheckpointed_statements != {ARTIFICIAL_STATEMENT}
+ ):
+ if not self.uncheckpointed_statements:
+ self.lowlevel_checkpoints[original_node] = True
+ else:
+ self.lowlevel_checkpoints[original_node] = False
+ # We need the original node to get the line/column when raising the error,
+ # but the updated node when removing it with autofixing.
+ self.lowlevel_checkpoint_updated_nodes[original_node] = cast(
+ "cst.Await", updated_node
+ )
+ elif not self.uncheckpointed_statements:
+ for expr, value in self.lowlevel_checkpoints.items():
+ if value is False:
+ self.lowlevel_checkpoints[expr] = original_node
- # all nodes are now checkpointed
self.checkpoint()
- return updated_node
+ return updated_node # type: ignore # raise/await blabla, see below
- # raising exception means we don't need to checkpoint so we can treat it as one
- # can't use TypeVar due to libcst's built-in type checking not supporting it
+ # Raising exception means we don't need to checkpoint so we can treat it as one.
+ # Can't use TypeVar due to libcst's built-in type checking not supporting it.
leave_Raise = leave_Await # type: ignore
def _is_exception_suppressing_context_manager(self, node: cst.With) -> bool:
@@ -854,16 +962,60 @@ def leave_Yield(
assert original_node.deep_equals(updated_node)
return original_node
+ def _merge_lowlevel_checkpoint_branch(
+ self,
+ node: (
+ cst.Try
+ | cst.TryStar
+ | cst.ExceptHandler
+ | cst.ExceptStarHandler
+ | cst.If
+ | cst.IfExp
+ | cst.MatchCase
+ | cst.For
+ | cst.While
+ ),
+ ) -> None:
+ # Value can be True, False, or a CSTNode.
+ # Checkpoints can be in one, or both, of the branches.
+ # In order for a statement to be in both branches with different values,
+ # imagine a statement before an `if`, that is made redundant within the `if`,
+ # and we then merge the if-entered and if-not-entered branches.
+ branch_cp = self.outer[node]["lowlevel_checkpoints"]
+ for cp, value in self.lowlevel_checkpoints.items():
+ old_value = branch_cp.get(cp, None)
+ if old_value is None or value is old_value:
+ continue
+ if False in (value, old_value):
+ # If checkpoint was made redundant in one branch, but not
+ # in another, it's not redundant.
+ self.lowlevel_checkpoints[cp] = False
+ elif True in (value, old_value):
+ # Nothing to checkpoint in one branch, but was made redundant
+ # by a specific statement in the other branch.
+ # Keep it as "nothing to checkpoint".
+ self.lowlevel_checkpoints[cp] = True
+ else:
+ raise AssertionError("logic error")
+ # Add checkpoints that were added in the other branch.
+ for cp, old_value in branch_cp.items():
+ if cp not in self.lowlevel_checkpoints:
+ self.lowlevel_checkpoints[cp] = old_value
+
# valid checkpoint if there's valid checkpoints (or raise) in:
# (try or else) and all excepts, or in finally
#
# try can jump into any except or into the finally* at any point during it's
# execution so we need to make sure except & finally can handle worst-case
# * unless there's a bare except / except BaseException - not implemented.
+
+ # ASYNC914 does not do sophisticated handling of finally/else, it simply views
+ # every single try/except/finally/else block as independent blocks we *might* enter.
+ # In practice this has the only effect that we
def visit_Try(self, node: cst.Try | cst.TryStar):
if not self.async_function:
return
- self.save_state(node, "try_state", copy=True)
+ self.save_state(node, "try_state", "lowlevel_checkpoints", copy=True)
# except & finally guaranteed to enter with checkpoint if checkpointed
# before try and no yield in try body.
self.try_state.body_uncheckpointed_statements = (
@@ -877,65 +1029,84 @@ def visit_Try(self, node: cst.Try | cst.TryStar):
)
def leave_Try_body(self, node: cst.Try | cst.TryStar):
+ if not self.async_function:
+ return
# save state at end of try for entering else
self.try_state.try_checkpoint = self.uncheckpointed_statements
# check that all except handlers checkpoint (await or most likely raise)
self.try_state.except_uncheckpointed_statements = set()
+ self._merge_lowlevel_checkpoint_branch(node)
+
def visit_ExceptHandler(self, node: cst.ExceptHandler | cst.ExceptStarHandler):
+ if not self.async_function:
+ return
# enter with worst case of try
self.uncheckpointed_statements = (
self.try_state.body_uncheckpointed_statements.copy()
)
+ self.save_state(node, "lowlevel_checkpoints", copy=True)
def leave_ExceptHandler(
self,
original_node: cst.ExceptHandler | cst.ExceptStarHandler,
updated_node: cst.ExceptHandler | cst.ExceptStarHandler,
) -> Any: # not worth creating a TypeVar to handle correctly
+ if not self.async_function:
+ return updated_node
self.try_state.except_uncheckpointed_statements.update(
self.uncheckpointed_statements
)
+ self._merge_lowlevel_checkpoint_branch(original_node)
return updated_node
def visit_Try_orelse(self, node: cst.Try | cst.TryStar):
+ if not self.async_function:
+ return
# check else
# if else runs it's after all of try, so restore state to back then
self.uncheckpointed_statements = self.try_state.try_checkpoint
def leave_Try_orelse(self, node: cst.Try | cst.TryStar):
+ if not self.async_function:
+ return
# checkpoint if else checkpoints, and all excepts checkpoint
self.uncheckpointed_statements.update(
self.try_state.except_uncheckpointed_statements
)
+ self._merge_lowlevel_checkpoint_branch(node)
def visit_Try_finalbody(self, node: cst.Try | cst.TryStar):
- if node.finalbody:
- self.try_state.added = (
- self.try_state.body_uncheckpointed_statements.difference(
- self.uncheckpointed_statements
- )
- )
- # if there's no bare except or except BaseException, we can jump into
- # finally from any point in try. But the exception will be reraised after
- # finally, so track what we add so it can be removed later.
- # (This is for catching return or yield in the finally, which is usually
- # very bad)
- if not any(
- h.type is None
- or (isinstance(h.type, cst.Name) and h.type.value == "BaseException")
- for h in node.handlers
- ):
- self.uncheckpointed_statements.update(self.try_state.added)
+ if not self.async_function or not node.finalbody:
+ return
+ self.try_state.added = self.try_state.body_uncheckpointed_statements.difference(
+ self.uncheckpointed_statements
+ )
+ # if there's no bare except or except BaseException, we can jump into
+ # finally from any point in try. But the exception will be reraised after
+ # finally, so track what we add so it can be removed later.
+ # (This is for catching return or yield in the finally, which is usually
+ # very bad)
+ if not any(
+ h.type is None
+ or (isinstance(h.type, cst.Name) and h.type.value == "BaseException")
+ for h in node.handlers
+ ):
+ self.uncheckpointed_statements.update(self.try_state.added)
def leave_Try_finalbody(self, node: cst.Try | cst.TryStar):
- if node.finalbody:
- self.uncheckpointed_statements.difference_update(self.try_state.added)
+ if not self.async_function or not node.finalbody:
+ return
+ self.uncheckpointed_statements.difference_update(self.try_state.added)
+ self._merge_lowlevel_checkpoint_branch(node)
def leave_Try(
self, original_node: cst.Try | cst.TryStar, updated_node: cst.Try | cst.TryStar
) -> cst.Try | cst.TryStar:
+ if not self.async_function:
+ return updated_node
+ del self.outer[original_node]["lowlevel_checkpoints"]
self.restore_state(original_node)
return updated_node
@@ -949,23 +1120,32 @@ def leave_Try(
visit_ExceptStarHandler = visit_ExceptHandler
leave_ExceptStarHandler = leave_ExceptHandler
+ def _swap(self, node: cst.CSTNode, name: str) -> None:
+ a = self.outer[node][name]
+ self.outer[node][name] = getattr(self, name)
+ setattr(self, name, a)
+
+ # if a previous lowlevel checkpoint is marked as redundant after all bodies, then
+ # it's redundant.
+ # If any body marks it as necessary, then it's necessary.
+ # Otherwise, it keeps its state from before.
def leave_If_test(self, node: cst.If | cst.IfExp) -> None:
if not self.async_function:
return
- self.save_state(node, "uncheckpointed_statements", copy=True)
+ self.save_state(
+ node, "uncheckpointed_statements", "lowlevel_checkpoints", copy=True
+ )
def leave_If_body(self, node: cst.If | cst.IfExp) -> None:
if not self.async_function:
return
- # restore state to after test, saving current state instead
- (
- self.uncheckpointed_statements,
- self.outer[node]["uncheckpointed_statements"],
- ) = (
- self.outer[node]["uncheckpointed_statements"],
- self.uncheckpointed_statements,
- )
+ # Restore state to after test, saving current state instead.
+ # This is importantly not executed after `else`.
+ # `elif` is represented by nested if/else, so we only need to handle the
+ # if/else case correctly.
+ self._swap(node, "uncheckpointed_statements")
+ self._swap(node, "lowlevel_checkpoints")
def leave_If(self, original_node: cst.If, updated_node: cst.If) -> cst.If:
if self.async_function:
@@ -973,6 +1153,7 @@ def leave_If(self, original_node: cst.If, updated_node: cst.If) -> cst.If:
self.uncheckpointed_statements.update(
self.outer[original_node]["uncheckpointed_statements"]
)
+ self._merge_lowlevel_checkpoint_branch(original_node)
return updated_node
# libcst calls attributes in the order they appear in the code, so we manually
@@ -1011,6 +1192,7 @@ def leave_MatchCase_guard(self, node: cst.MatchCase) -> None:
and (node.guard is None or not self.uncheckpointed_statements)
):
self.match_state.has_fallback = True
+ self.save_state(node, "lowlevel_checkpoints", copy=True)
def leave_MatchCase(
self, original_node: cst.MatchCase, updated_node: cst.MatchCase
@@ -1019,6 +1201,7 @@ def leave_MatchCase(
self.match_state.case_uncheckpointed_statements.update(
self.uncheckpointed_statements
)
+ self._merge_lowlevel_checkpoint_branch(original_node)
return updated_node
def leave_Match(
@@ -1039,6 +1222,7 @@ def visit_While(self, node: cst.While | cst.For):
self.save_state(
node,
"loop_state",
+ "lowlevel_checkpoints",
copy=True,
)
self.loop_state = LoopState()
@@ -1151,6 +1335,9 @@ def leave_While_body(self, node: cst.For | cst.While):
self.loop_state.uncheckpointed_before_continue
)
+ # ASYNC914
+ self._merge_lowlevel_checkpoint_branch(node)
+
leave_For_body = leave_While_body
def leave_While_orelse(self, node: cst.For | cst.While):
@@ -1171,6 +1358,17 @@ def leave_While_orelse(self, node: cst.For | cst.While):
# reset break & continue in case of nested loops
self.outer[node]["uncheckpointed_statements"] = self.uncheckpointed_statements
+ # TODO: if this loop always checkpoints
+ # e.g. from being an async for, or being guaranteed to run once, or other stuff.
+ # then we can warn about redundant checkpoints before the loop.
+ # ... except if the reason we always checkpoint is due to redundant checkpoints
+ # we're about to remove.... :thinking:
+
+ # ASYNC914
+ if node.orelse:
+ self._merge_lowlevel_checkpoint_branch(node)
+ del self.outer[node]["lowlevel_checkpoints"]
+
leave_For_orelse = leave_While_orelse
def leave_While(
@@ -1238,7 +1436,9 @@ def visit_Break(self, node: cst.Break):
def visit_BooleanOperation_right(self, node: cst.BooleanOperation):
if not self.async_function:
return
- self.save_state(node, "uncheckpointed_statements", copy=True)
+ self.save_state(
+ node, "uncheckpointed_statements", "lowlevel_checkpoints", copy=True
+ )
def leave_BooleanOperation_right(self, node: cst.BooleanOperation):
if not self.async_function:
@@ -1246,6 +1446,7 @@ def leave_BooleanOperation_right(self, node: cst.BooleanOperation):
self.uncheckpointed_statements.update(
self.outer[node]["uncheckpointed_statements"]
)
+ self.lowlevel_checkpoints = self.outer[node]["lowlevel_checkpoints"]
# comprehensions are simpler than loops, since they cannot contain yields
# or many other complicated statements, but their subfields are not in the order
@@ -1324,7 +1525,11 @@ def visit_Import(self, node: cst.Import):
"""
for alias in node.names:
if m.matches(
- alias, m.ImportAlias(name=m.Name("trio") | m.Name("anyio"), asname=None)
+ alias,
+ m.ImportAlias(
+ name=m.Name("trio") | m.Name("anyio") | m.Name("asyncio"),
+ asname=None,
+ ),
):
assert isinstance(alias.name.value, str)
self.explicitly_imported_library[alias.name.value] = True
diff --git a/tests/autofix_files/async124.py b/tests/autofix_files/async124.py
index e514bba..cdf6698 100644
--- a/tests/autofix_files/async124.py
+++ b/tests/autofix_files/async124.py
@@ -8,7 +8,6 @@
# not what the user wants though, so this would be a case in favor of making 910/911 not
# trigger when async124 does.
# AUTOFIX # all errors get "fixed" except for foo_fix_no_subfix in async124_no_autofix.py
-# ASYNCIO_NO_AUTOFIX
from typing import Any, overload
from pytest import fixture
import trio
diff --git a/tests/autofix_files/async124.py.diff b/tests/autofix_files/async124.py.diff
index 3b6f5b0..b26f014 100644
--- a/tests/autofix_files/async124.py.diff
+++ b/tests/autofix_files/async124.py.diff
@@ -1,7 +1,7 @@
---
+++
@@ x,6 x,7 @@
- # ASYNCIO_NO_AUTOFIX
+ # AUTOFIX # all errors get "fixed" except for foo_fix_no_subfix in async124_no_autofix.py
from typing import Any, overload
from pytest import fixture
+import trio
diff --git a/tests/autofix_files/async910.py b/tests/autofix_files/async910.py
index 4d97ec3..8795918 100644
--- a/tests/autofix_files/async910.py
+++ b/tests/autofix_files/async910.py
@@ -1,5 +1,4 @@
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
# mypy: disable-error-code="unreachable"
from __future__ import annotations
@@ -9,6 +8,7 @@
import pytest
import trio
+import trio.lowlevel
_ = ""
@@ -16,13 +16,13 @@
async def foo() -> Any:
- await foo()
+ await trio.lowlevel.checkpoint()
def bar() -> Any: ...
-# ARG --enable=ASYNC910,ASYNC911
+# ARG --enable=ASYNC910,ASYNC911,ASYNC914
# ARG --no-checkpoint-warning-decorator=custom_disabled_decorator
@@ -58,25 +58,25 @@ async def foo1(): # error: 0, "exit", Statement("function definition", lineno)
# If
async def foo_if_1(): # error: 0, "exit", Statement("function definition", lineno)
if _:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
async def foo_if_2():
if _:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_if_3():
- await foo()
+ await trio.lowlevel.checkpoint()
if _:
...
async def foo_if_4(): # safe
- await foo()
+ await trio.lowlevel.checkpoint()
if ...:
...
else:
@@ -85,17 +85,21 @@ async def foo_if_4(): # safe
# IfExp
async def foo_ifexp_1(): # safe
- print(await foo() if _ else await foo())
+ print(await trio.lowlevel.checkpoint() if _ else await trio.lowlevel.checkpoint())
async def foo_ifexp_2(): # error: 0, "exit", Statement("function definition", lineno)
- print(_ if False and await foo() else await foo())
+ print(
+ _
+ if False and await trio.lowlevel.checkpoint()
+ else await trio.lowlevel.checkpoint()
+ )
await trio.lowlevel.checkpoint()
# nested function definition
async def foo_func_1():
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_func_2(): # error: 4, "exit", Statement("function definition", lineno)
bar()
@@ -107,7 +111,7 @@ async def foo_func_2(): # error: 4, "exit", Statement("function definition", li
# fmt: off
async def foo_func_3(): # error: 0, "exit", Statement("function definition", lineno)
async def foo_func_4():
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
@@ -147,101 +151,101 @@ async def foo_overload_1(_: str): ...
async def foo_overload_1(_: bytes | str):
- await foo()
+ await trio.lowlevel.checkpoint()
# conditions
async def foo_condition_1(): # safe
- if await foo():
+ if await trio.lowlevel.checkpoint():
...
async def foo_condition_2(): # error: 0, "exit", Statement("function definition", lineno)
- if False and await foo():
+ if False and await trio.lowlevel.checkpoint():
...
await trio.lowlevel.checkpoint()
async def foo_condition_3(): # error: 0, "exit", Statement("function definition", lineno)
- if ... and await foo():
+ if ... and await trio.lowlevel.checkpoint():
...
await trio.lowlevel.checkpoint()
async def foo_condition_4(): # safe
- while await foo():
+ while await trio.lowlevel.checkpoint():
...
async def foo_condition_5(): # safe
- for i in await foo():
+ for i in await trio.lowlevel.checkpoint():
...
async def foo_condition_6(): # in theory error, but not worth parsing
- for i in (None, await foo()):
+ for i in (None, await trio.lowlevel.checkpoint()):
break
# loops
async def foo_while_1(): # error: 0, "exit", Statement("function definition", lineno)
while _:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
async def foo_while_2(): # now safe
while _:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_3(): # safe
- await foo()
+ await trio.lowlevel.checkpoint()
while _:
...
async def foo_while_4(): # error: 0, "exit", Statement("function definition", lineno)
while False:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
# for
async def foo_for_1(): # error: 0, "exit", Statement("function definition", lineno)
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
async def foo_for_2(): # now safe
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_break_1(): # safe
while bar():
- await foo()
+ await trio.lowlevel.checkpoint()
break
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_break_2(): # error: 0, "exit", Statement("function definition", lineno)
while bar():
break
else:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
async def foo_while_break_3(): # error: 0, "exit", Statement("function definition", lineno)
while bar():
- await foo()
+ await trio.lowlevel.checkpoint()
break
else:
...
@@ -258,22 +262,22 @@ async def foo_while_break_4(): # error: 0, "exit", Statement("function definiti
async def foo_while_continue_1(): # safe
while bar():
- await foo()
+ await trio.lowlevel.checkpoint()
continue
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_continue_2(): # safe
while bar():
continue
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_continue_3(): # error: 0, "exit", Statement("function definition", lineno)
while bar():
- await foo()
+ await trio.lowlevel.checkpoint()
continue
else:
...
@@ -307,7 +311,7 @@ async def foo_raise_1(): # safe
async def foo_raise_2(): # safe
if _:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
raise ValueError()
@@ -315,15 +319,27 @@ async def foo_raise_2(): # safe
# try
# safe only if (try or else) and all except bodies either await or raise
# if `foo()` raises a ValueError it's not checkpointed
-async def foo_try_1(): # error: 0, "exit", Statement("function definition", lineno)
+async def foo_try_1a(): # error: 0, "exit", Statement("function definition", lineno)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
...
except:
raise
else:
- await foo()
+ ...
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_try_1b(): # error: 0, "exit", Statement("function definition", lineno)
+ try:
+ ...
+ except ValueError:
+ ...
+ except:
+ raise
+ else:
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
@@ -335,14 +351,14 @@ async def foo_try_2(): # safe
except:
raise
finally:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_3(): # safe
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
- await foo()
+ await trio.lowlevel.checkpoint()
except:
raise
@@ -355,11 +371,11 @@ async def foo_try_4(): # safe
except:
raise
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_5(): # safe
- await foo()
+ await trio.lowlevel.checkpoint()
try:
pass
except:
@@ -380,13 +396,21 @@ async def foo_try_6(): # error: 0, "exit", Statement("function definition", lin
async def foo_try_7(): # safe
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
pass
+async def foo_try_8(): # error: 0, "exit", Statement("function definition", lineno)
+ try:
+ ...
+ except:
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
+
+
# https://github.com/python-trio/flake8-async/issues/45
async def to_queue(iter_func, queue):
async with iter_func() as it:
@@ -459,12 +483,12 @@ async def foo_return_2(): # safe
if _:
await trio.lowlevel.checkpoint()
return # error: 8, "return", Statement("function definition", lineno-2)
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_return_3(): # error: 0, "exit", Statement("function definition", lineno)
if _:
- await foo()
+ await trio.lowlevel.checkpoint()
return # safe
await trio.lowlevel.checkpoint()
@@ -472,35 +496,35 @@ async def foo_return_3(): # error: 0, "exit", Statement("function definition",
# loop over non-empty static collection
async def foo_loop_static():
for i in [1, 2, 3]:
- await foo()
+ await trio.lowlevel.checkpoint()
# also handle range with constants
async def foo_range_1():
for i in range(5):
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_range_2():
for i in range(5, 10):
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_range_3():
for i in range(10, 5, -1):
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_range_4(): # error: 0, "exit", Statement("function definition", lineno)
for i in range(10, 5):
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
# error on complex parameters
async def foo_range_5(): # error: 0, "exit", Statement("function definition", lineno)
for i in range(3 - 2):
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
@@ -512,7 +536,7 @@ async def f():
return
# If you delete this loop, no warning.
while bar():
- await foo()
+ await trio.lowlevel.checkpoint()
async def f1():
@@ -579,12 +603,12 @@ async def foo_test_return2():
async def foo_comprehension_1():
- [... for x in range(10) if await foo()]
+ [... for x in range(10) if await trio.lowlevel.checkpoint()]
# should error
async def foo_comprehension_2(): # error: 0, "exit", Statement("function definition", lineno)
- [await foo() for x in range(10) if bar()]
+ [await trio.lowlevel.checkpoint() for x in range(10) if bar()]
await trio.lowlevel.checkpoint()
@@ -600,7 +624,7 @@ async def foo_comprehension_3():
async def await_in_gen_target():
- (print(x) for x in await foo())
+ (print(x) for x in await trio.lowlevel.checkpoint())
async def await_everywhere_except_gen_target(): # error: 0, "exit", Statement("function definition", lineno)
@@ -624,7 +648,7 @@ async def fn_226(): # error: 0, "exit", Statement("function definition", lineno
# the await() is evaluated in the parent scope
async def foo_default_value_await():
async def bar( # error: 4, "exit", Statement("function definition", lineno)
- arg=await foo(),
+ arg=await trio.lowlevel.checkpoint(),
):
print()
await trio.lowlevel.checkpoint()
@@ -634,4 +658,4 @@ async def foo_nested_empty_async():
# this previously errored because leave_FunctionDef assumed a non-empty body
async def bar(): ...
- await foo()
+ await trio.lowlevel.checkpoint()
diff --git a/tests/autofix_files/async910.py.diff b/tests/autofix_files/async910.py.diff
index 3fb6131..d249c86 100644
--- a/tests/autofix_files/async910.py.diff
+++ b/tests/autofix_files/async910.py.diff
@@ -10,15 +10,15 @@
# If
async def foo_if_1(): # error: 0, "exit", Statement("function definition", lineno)
if _:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
async def foo_if_2():
@@ x,6 x,7 @@
-
- async def foo_ifexp_2(): # error: 0, "exit", Statement("function definition", lineno)
- print(_ if False and await foo() else await foo())
+ if False and await trio.lowlevel.checkpoint()
+ else await trio.lowlevel.checkpoint()
+ )
+ await trio.lowlevel.checkpoint()
@@ -34,7 +34,7 @@
@@ x,17 x,21 @@
async def foo_func_3(): # error: 0, "exit", Statement("function definition", lineno)
async def foo_func_4():
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
@@ -55,13 +55,13 @@
@@ x,11 x,13 @@
async def foo_condition_2(): # error: 0, "exit", Statement("function definition", lineno)
- if False and await foo():
+ if False and await trio.lowlevel.checkpoint():
...
+ await trio.lowlevel.checkpoint()
async def foo_condition_3(): # error: 0, "exit", Statement("function definition", lineno)
- if ... and await foo():
+ if ... and await trio.lowlevel.checkpoint():
...
+ await trio.lowlevel.checkpoint()
@@ -70,7 +70,7 @@
@@ x,6 x,7 @@
async def foo_while_1(): # error: 0, "exit", Statement("function definition", lineno)
while _:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
@@ -78,14 +78,14 @@
@@ x,12 x,14 @@
async def foo_while_4(): # error: 0, "exit", Statement("function definition", lineno)
while False:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
# for
async def foo_for_1(): # error: 0, "exit", Statement("function definition", lineno)
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
@@ -93,7 +93,7 @@
@@ x,6 x,7 @@
break
else:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
@@ -133,7 +133,15 @@
@@ x,6 x,7 @@
raise
else:
- await foo()
+ ...
++ await trio.lowlevel.checkpoint()
+
+
+ async def foo_try_1b(): # error: 0, "exit", Statement("function definition", lineno)
+@@ x,6 x,7 @@
+ raise
+ else:
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
@@ -146,6 +154,14 @@
async def foo_try_7(): # safe
+@@ x,6 x,7 @@
+ ...
+ except:
+ await trio.lowlevel.checkpoint()
++ await trio.lowlevel.checkpoint()
+
+
+ # https://github.com/python-trio/flake8-async/issues/45
@@ x,6 x,7 @@
await trio.sleep(0)
except:
@@ -172,11 +188,11 @@
if _:
+ await trio.lowlevel.checkpoint()
return # error: 8, "return", Statement("function definition", lineno-2)
- await foo()
+ await trio.lowlevel.checkpoint()
@@ x,6 x,7 @@
if _:
- await foo()
+ await trio.lowlevel.checkpoint()
return # safe
+ await trio.lowlevel.checkpoint()
@@ -185,14 +201,14 @@
@@ x,12 x,14 @@
async def foo_range_4(): # error: 0, "exit", Statement("function definition", lineno)
for i in range(10, 5):
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
# error on complex parameters
async def foo_range_5(): # error: 0, "exit", Statement("function definition", lineno)
for i in range(3 - 2):
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
@@ -200,7 +216,7 @@
@@ x,6 x,7 @@
# should error
async def foo_comprehension_2(): # error: 0, "exit", Statement("function definition", lineno)
- [await foo() for x in range(10) if bar()]
+ [await trio.lowlevel.checkpoint() for x in range(10) if bar()]
+ await trio.lowlevel.checkpoint()
@@ -222,7 +238,7 @@
# the await() is evaluated in the parent scope
@@ x,6 x,7 @@
- arg=await foo(),
+ arg=await trio.lowlevel.checkpoint(),
):
print()
+ await trio.lowlevel.checkpoint()
diff --git a/tests/autofix_files/async910_insert_library.py b/tests/autofix_files/async910_insert_library.py
index e65eb65..4c8e15b 100644
--- a/tests/autofix_files/async910_insert_library.py
+++ b/tests/autofix_files/async910_insert_library.py
@@ -1,7 +1,6 @@
# ensure that import gets added when adding checkpoints at end of function body
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
import trio
diff --git a/tests/autofix_files/async910_insert_library.py.diff b/tests/autofix_files/async910_insert_library.py.diff
index b032abb..55511a7 100644
--- a/tests/autofix_files/async910_insert_library.py.diff
+++ b/tests/autofix_files/async910_insert_library.py.diff
@@ -1,7 +1,7 @@
---
+++
@@ x,9 x,11 @@
- # ASYNCIO_NO_AUTOFIX
+ # AUTOFIX
+import trio
diff --git a/tests/autofix_files/async911.py b/tests/autofix_files/async911.py
index a91a322..974f78c 100644
--- a/tests/autofix_files/async911.py
+++ b/tests/autofix_files/async911.py
@@ -1,5 +1,4 @@
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
from typing import Any
import pytest
@@ -7,11 +6,11 @@
_: Any = ""
-# ARG --enable=ASYNC910,ASYNC911
+# ARG --enable=ASYNC910,ASYNC911,ASYNC914
async def foo() -> Any:
- await foo()
+ await trio.lowlevel.checkpoint()
def bar(*args) -> Any: ...
@@ -22,9 +21,9 @@ def condition() -> Any: ...
async def foo_yield_1():
- await foo()
+ await trio.lowlevel.checkpoint()
yield 5
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_yield_2():
@@ -32,11 +31,11 @@ async def foo_yield_2():
yield # error: 4, "yield", Statement("function definition", lineno-1)
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("yield", lineno-1)
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_yield_3(): # error: 0, "exit", Statement("yield", lineno+2)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
await trio.lowlevel.checkpoint()
@@ -58,16 +57,16 @@ async def foo_yield_return_1():
async def foo_yield_return_2():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
await trio.lowlevel.checkpoint()
return # error: 4, "return", Statement("yield", lineno-1)
async def foo_yield_return_3():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
return
@@ -116,7 +115,7 @@ async def foo_async_for_4(): # safe
# for
async def foo_for(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
for i in "":
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno)
@@ -125,7 +124,7 @@ async def foo_for(): # error: 0, "exit", Statement("yield", lineno+3)
async def foo_for_1(): # error: 0, "exit", Statement("function definition", lineno) # error: 0, "exit", Statement("yield", lineno+3)
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
await trio.lowlevel.checkpoint()
@@ -138,14 +137,14 @@ async def foo_while_1(): # error: 0, "exit", Statement("yield", lineno+5)
while foo():
...
else:
- await foo() # will always run
+ await trio.lowlevel.checkpoint() # will always run
yield # safe
await trio.lowlevel.checkpoint()
# simple yield-in-loop case
async def foo_while_2(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno)
@@ -155,7 +154,7 @@ async def foo_while_2(): # error: 0, "exit", Statement("yield", lineno+3)
# no checkpoint after yield if else is entered
async def foo_while_3(): # error: 0, "exit", Statement("yield", lineno+5)
while foo():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
else:
await trio.lowlevel.checkpoint()
@@ -165,7 +164,7 @@ async def foo_while_3(): # error: 0, "exit", Statement("yield", lineno+5)
# check that errors are suppressed in visit_While
async def foo_while_4(): # error: 0, "exit", Statement("yield", lineno+3) # error: 0, "exit", Statement("yield", lineno+5) # error: 0, "exit", Statement("yield", lineno+7)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno) # error: 8, "yield", Statement("yield", lineno+2) # error: 8, "yield", Statement("yield", lineno+4)
@@ -180,7 +179,7 @@ async def foo_while_4(): # error: 0, "exit", Statement("yield", lineno+3) # err
# check that state management is handled in for loops as well
async def foo_while_4_for(): # error: 0, "exit", Statement("yield", lineno+3) # error: 0, "exit", Statement("yield", lineno+5) # error: 0, "exit", Statement("yield", lineno+7)
- await foo()
+ await trio.lowlevel.checkpoint()
for i in bar():
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno) # error: 8, "yield", Statement("yield", lineno+2) # error: 8, "yield", Statement("yield", lineno+4)
@@ -195,7 +194,7 @@ async def foo_while_4_for(): # error: 0, "exit", Statement("yield", lineno+3) #
# check error suppression is reset
async def foo_while_5():
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno)
@@ -205,36 +204,36 @@ async def foo_nested_error(): # error: 8, "exit", Statement("yield", lineno+1)
yield # error: 12, "yield", Statement("function definition", lineno-1)
await trio.lowlevel.checkpoint()
- await foo()
+ await trio.lowlevel.checkpoint()
# --- while + continue ---
# no checkpoint on continue
async def foo_while_continue_1(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno)
if condition():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
# multiple continues
async def foo_while_continue_2(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno)
if foo():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
continue
while foo():
yield # safe
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
@@ -245,7 +244,7 @@ async def foo_while_break_1(): # error: 0, "exit", Statement("yield", lineno+6)
if condition():
break
else:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("function definition", lineno-6)
await trio.lowlevel.checkpoint()
@@ -253,23 +252,23 @@ async def foo_while_break_1(): # error: 0, "exit", Statement("yield", lineno+6)
# no checkpoint on break
async def foo_while_break_2(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield # safe
if condition():
break
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
# guaranteed if else and break
async def foo_while_break_3(): # error: 0, "exit", Statement("yield", lineno+7)
while foo():
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break # if it breaks, have checkpointed
else:
- await foo() # runs if 0-iter
+ await trio.lowlevel.checkpoint() # runs if 0-iter
yield # safe
await trio.lowlevel.checkpoint()
@@ -279,9 +278,9 @@ async def foo_while_break_4(): # error: 0, "exit", Statement("yield", lineno+7)
while foo():
if condition():
break
- await foo() # might not run
+ await trio.lowlevel.checkpoint() # might not run
else:
- await foo() # might not run
+ await trio.lowlevel.checkpoint() # might not run
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("function definition", lineno-7)
await trio.lowlevel.checkpoint()
@@ -289,17 +288,17 @@ async def foo_while_break_4(): # error: 0, "exit", Statement("yield", lineno+7)
# check break is reset on nested
async def foo_while_break_5(): # error: 0, "exit", Statement("yield", lineno+12)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield
if condition():
break
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield # safe
- await foo()
+ await trio.lowlevel.checkpoint()
yield # safe
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("yield", lineno-9)
await trio.lowlevel.checkpoint()
@@ -307,14 +306,14 @@ async def foo_while_break_5(): # error: 0, "exit", Statement("yield", lineno+12
# check multiple breaks
async def foo_while_break_6(): # error: 0, "exit", Statement("yield", lineno+11)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield
if condition():
break
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
await trio.lowlevel.checkpoint()
@@ -324,7 +323,7 @@ async def foo_while_break_6(): # error: 0, "exit", Statement("yield", lineno+11
async def foo_while_break_7(): # error: 0, "exit", Statement("function definition", lineno)# error: 0, "exit", Statement("yield", lineno+5)
while foo():
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
yield
@@ -334,13 +333,13 @@ async def foo_while_break_7(): # error: 0, "exit", Statement("function definiti
async def foo_while_endless_1():
while True:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
async def foo_while_endless_2(): # error: 0, "exit", Statement("function definition", lineno)# error: 0, "exit", Statement("yield", lineno+3)
while foo():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
await trio.lowlevel.checkpoint()
@@ -349,15 +348,15 @@ async def foo_while_endless_3():
while True:
...
yield # type: ignore[unreachable]
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_endless_4():
- await foo()
+ await trio.lowlevel.checkpoint()
while True:
yield
while True:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
@@ -374,13 +373,13 @@ async def foo_try_1(): # error: 0, "exit", Statement("function definition", lin
# no checkpoint after yield in ValueError
async def foo_try_2(): # error: 0, "exit", Statement("yield", lineno+5)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
await trio.lowlevel.checkpoint()
# try might not have checkpointed
yield # error: 8, "yield", Statement("function definition", lineno-5)
except:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
pass
await trio.lowlevel.checkpoint()
@@ -390,7 +389,7 @@ async def foo_try_3(): # error: 0, "exit", Statement("yield", lineno+6)
try:
...
except:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("function definition", lineno-6)
@@ -404,22 +403,22 @@ async def foo_try_4(): # safe
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("function definition", lineno-4)
finally:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_5():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
await trio.lowlevel.checkpoint()
# try might crash before checkpoint
yield # error: 8, "yield", Statement("function definition", lineno-5)
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_6(): # error: 0, "exit", Statement("yield", lineno+5)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
pass
await trio.lowlevel.checkpoint()
@@ -428,18 +427,18 @@ async def foo_try_6(): # error: 0, "exit", Statement("yield", lineno+5)
async def foo_try_7(): # error: 0, "exit", Statement("yield", lineno+17)
- await foo()
+ await trio.lowlevel.checkpoint()
try:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
except SyntaxError:
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno-7)
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
pass
# If the try raises an exception without checkpointing, and it's not caught
@@ -450,27 +449,38 @@ async def foo_try_7(): # error: 0, "exit", Statement("yield", lineno+17)
## safe only if (try or else) and all except bodies either await or raise
-## if foo() raises a ValueError it's not checkpointed, and may or may not yield
-async def foo_try_8(): # error: 0, "exit", Statement("function definition", lineno) # error: 0, "exit", Statement("yield", lineno+3)
+## if the await raises an exception it's not checkpointed, and may or may not yield
+async def foo_try_8a(): # error: 0, "exit", Statement("function definition", lineno) # error: 0, "exit", Statement("yield", lineno+3)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
except ValueError:
...
except:
raise
else:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_try_8b(): # error: 0, "exit", Statement("function definition", lineno) # error: 0, "exit", Statement("yield", lineno+3)
+ try:
+ await trio.lowlevel.checkpoint()
+ yield
+ await trio.lowlevel.checkpoint()
+ except ValueError:
+ ...
+ except:
+ raise
await trio.lowlevel.checkpoint()
# no checkpoint after yield in else
async def foo_try_9(): # error: 0, "exit", Statement("yield", lineno+6)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
yield
await trio.lowlevel.checkpoint()
@@ -479,44 +489,44 @@ async def foo_try_9(): # error: 0, "exit", Statement("yield", lineno+6)
# bare except means we'll jump to finally after full execution of either try or the except
async def foo_try_10():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_10_BaseException():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except BaseException:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
# not fully covering excepts
async def foo_try_10_exception():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("function definition", lineno-6)
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_10_no_except():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
await trio.lowlevel.checkpoint()
# try might crash before checkpoint
yield # error: 8, "yield", Statement("function definition", lineno-5)
- await foo()
+ await trio.lowlevel.checkpoint()
# if
@@ -524,15 +534,15 @@ async def foo_if_1():
if condition():
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("function definition", lineno-2)
- await foo()
+ await trio.lowlevel.checkpoint()
else:
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("function definition", lineno-5)
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_if_2(): # error: 0, "exit", Statement("yield", lineno+6)
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
...
else:
@@ -543,7 +553,7 @@ async def foo_if_2(): # error: 0, "exit", Statement("yield", lineno+6)
async def foo_if_3(): # error: 0, "exit", Statement("yield", lineno+6)
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
yield
else:
@@ -554,10 +564,10 @@ async def foo_if_3(): # error: 0, "exit", Statement("yield", lineno+6)
async def foo_if_4(): # error: 0, "exit", Statement("yield", lineno+7)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
if condition():
- await foo()
+ await trio.lowlevel.checkpoint()
else:
...
await trio.lowlevel.checkpoint()
@@ -566,10 +576,10 @@ async def foo_if_4(): # error: 0, "exit", Statement("yield", lineno+7)
async def foo_if_5(): # error: 0, "exit", Statement("yield", lineno+8)
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
yield
- await foo()
+ await trio.lowlevel.checkpoint()
else:
yield
...
@@ -579,12 +589,12 @@ async def foo_if_5(): # error: 0, "exit", Statement("yield", lineno+8)
async def foo_if_6(): # error: 0, "exit", Statement("yield", lineno+8)
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
yield
else:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
...
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("yield", lineno-5)
@@ -593,9 +603,9 @@ async def foo_if_6(): # error: 0, "exit", Statement("yield", lineno+8)
async def foo_if_7(): # error: 0, "exit", Statement("function definition", lineno)
if condition():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
@@ -603,9 +613,9 @@ async def foo_if_8(): # error: 0, "exit", Statement("function definition", line
if condition():
...
else:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
@@ -665,7 +675,7 @@ def foo_sync_7():
# nested function definition
async def foo_func_1():
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_func_2(): # error: 4, "exit", Statement("yield", lineno+1)
await trio.lowlevel.checkpoint()
@@ -677,23 +687,23 @@ async def foo_func_2(): # error: 4, "exit", Statement("yield", lineno+1)
# so we need to disable black
# fmt: off
async def foo_func_3(): # error: 0, "exit", Statement("yield", lineno+2)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
async def foo_func_4():
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
async def foo_func_5(): # error: 0, "exit", Statement("yield", lineno+2)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
def foo_func_6(): # safe
yield
async def foo_func_7():
- await foo()
+ await trio.lowlevel.checkpoint()
...
await trio.lowlevel.checkpoint()
# fmt: on
@@ -709,26 +719,26 @@ async def foo_boolops_1(): # error: 0, "exit", Stmt("yield", line+1)
async def foo_loop_static():
# break/else behaviour on guaranteed body execution
for _ in [1, 2, 3]:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in [1, 2, 3]:
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
else:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
yield
# continue
for _ in [1, 2, 3]:
if condition():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-7)
@@ -736,149 +746,149 @@ async def foo_loop_static():
for _ in [1, 2, 3]:
if condition():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
else:
await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Stmt("yield", line-8)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in [1, 2, 3]:
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
else:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
yield
# test different containers
for _ in (1, 2, 3):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in (foo(), foo()):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in ((),):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in "hello":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in b"hello":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in r"hello":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in {1, 2, 3}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in ():
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in {1: 2, 3: 4}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in " ".strip():
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in range(0):
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in (*range(0),):
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in (*(1, 2),):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in {**{}}:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in {**{}, **{}}:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in {**{1: 2}}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in (*range(0), *[1, 2, 3]):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in {**{}, **{1: 2}}: # type: ignore[arg-type]
- await foo()
+ await trio.lowlevel.checkpoint()
yield
x: Any = ...
for _ in (*x, *[1, 2, 3]):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in {**x, **{1: 2}}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in {}:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in """""":
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in [[], []][0]:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in [[], []].__getitem__(0):
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# not handled
for _ in list((1, 2)):
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
for _ in list():
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# while
while True:
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
yield
@@ -886,113 +896,113 @@ async def foo_loop_static():
while True:
if condition():
break
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-6)
while True:
if condition():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
yield
while False:
- await foo() # type: ignore[unreachable]
+ await trio.lowlevel.checkpoint() # type: ignore[unreachable]
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
while "hello":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
# false positive on containers
while [1, 2]:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
# will get caught by any number of linters, but trio911 will also complain
for _ in 5: # type: ignore
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
# range with constant arguments also handled, see more extensive tests in 910
for i in range(5):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(0x23):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(0b01):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(1 + 1): # not handled
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for i in range(None): # type: ignore
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for i in range(+3):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(-3.5): # type: ignore
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# duplicated from 910 to have all range tests in one place
for i in range(5, 10):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(10, 5, -1):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
# ~0 == -1
for i in range(10, 5, ~0):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
# length > sys.maxsize
for i in range(27670116110564327421):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(10, 5):
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# binary operations are not handled
for i in range(3 - 2):
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
for i in range(10**3):
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# nor nested unary operations
for i in range(--3):
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
- await foo()
+ await trio.lowlevel.checkpoint()
# don't warn on pytest.fixture
diff --git a/tests/autofix_files/async911.py.diff b/tests/autofix_files/async911.py.diff
index bf853b9..a188a22 100644
--- a/tests/autofix_files/async911.py.diff
+++ b/tests/autofix_files/async911.py.diff
@@ -8,11 +8,11 @@
yield # error: 4, "yield", Statement("function definition", lineno-1)
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("yield", lineno-1)
- await foo()
+ await trio.lowlevel.checkpoint()
@@ x,22 x,29 @@
async def foo_yield_3(): # error: 0, "exit", Statement("yield", lineno+2)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
+ await trio.lowlevel.checkpoint()
@@ -34,7 +34,7 @@
async def foo_yield_return_2():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
+ await trio.lowlevel.checkpoint()
return # error: 4, "return", Statement("yield", lineno-1)
@@ -66,7 +66,7 @@
async def foo_async_for_3(): # safe
@@ x,13 x,16 @@
async def foo_for(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
for i in "":
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno)
@@ -75,7 +75,7 @@
async def foo_for_1(): # error: 0, "exit", Statement("function definition", lineno) # error: 0, "exit", Statement("yield", lineno+3)
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
+ await trio.lowlevel.checkpoint()
@@ -83,14 +83,14 @@
# while
@@ x,13 x,16 @@
else:
- await foo() # will always run
+ await trio.lowlevel.checkpoint() # will always run
yield # safe
+ await trio.lowlevel.checkpoint()
# simple yield-in-loop case
async def foo_while_2(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno)
@@ -99,7 +99,7 @@
# no checkpoint after yield if else is entered
@@ x,39 x,52 @@
- await foo()
+ await trio.lowlevel.checkpoint()
yield
else:
+ await trio.lowlevel.checkpoint()
@@ -109,7 +109,7 @@
# check that errors are suppressed in visit_While
async def foo_while_4(): # error: 0, "exit", Statement("yield", lineno+3) # error: 0, "exit", Statement("yield", lineno+5) # error: 0, "exit", Statement("yield", lineno+7)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno) # error: 8, "yield", Statement("yield", lineno+2) # error: 8, "yield", Statement("yield", lineno+4)
@@ -124,7 +124,7 @@
# check that state management is handled in for loops as well
async def foo_while_4_for(): # error: 0, "exit", Statement("yield", lineno+3) # error: 0, "exit", Statement("yield", lineno+5) # error: 0, "exit", Statement("yield", lineno+7)
- await foo()
+ await trio.lowlevel.checkpoint()
for i in bar():
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno) # error: 8, "yield", Statement("yield", lineno+2) # error: 8, "yield", Statement("yield", lineno+4)
@@ -139,7 +139,7 @@
# check error suppression is reset
async def foo_while_5():
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno)
@@ -149,23 +149,23 @@
yield # error: 12, "yield", Statement("function definition", lineno-1)
+ await trio.lowlevel.checkpoint()
- await foo()
+ await trio.lowlevel.checkpoint()
@@ x,16 x,19 @@
async def foo_while_continue_1(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno)
if condition():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
# multiple continues
async def foo_while_continue_2(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno)
@@ -174,7 +174,7 @@
@@ x,6 x,7 @@
while foo():
yield # safe
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
@@ -182,7 +182,7 @@
@@ x,7 x,9 @@
break
else:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("function definition", lineno-6)
+ await trio.lowlevel.checkpoint()
@@ -192,23 +192,23 @@
@@ x,6 x,7 @@
if condition():
break
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
# guaranteed if else and break
@@ x,6 x,7 @@
else:
- await foo() # runs if 0-iter
+ await trio.lowlevel.checkpoint() # runs if 0-iter
yield # safe
+ await trio.lowlevel.checkpoint()
# break at non-guaranteed checkpoint
@@ x,7 x,9 @@
- await foo() # might not run
+ await trio.lowlevel.checkpoint() # might not run
else:
- await foo() # might not run
+ await trio.lowlevel.checkpoint() # might not run
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("function definition", lineno-7)
+ await trio.lowlevel.checkpoint()
@@ -216,9 +216,9 @@
# check break is reset on nested
@@ x,7 x,9 @@
- await foo()
+ await trio.lowlevel.checkpoint()
yield # safe
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("yield", lineno-9)
+ await trio.lowlevel.checkpoint()
@@ -226,7 +226,7 @@
# check multiple breaks
@@ x,7 x,9 @@
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
+ await trio.lowlevel.checkpoint()
@@ -245,7 +245,7 @@
async def foo_while_endless_1():
@@ x,6 x,7 @@
while foo():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
+ await trio.lowlevel.checkpoint()
@@ -265,13 +265,13 @@
# no checkpoint after yield in ValueError
@@ x,12 x,14 @@
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
+ await trio.lowlevel.checkpoint()
# try might not have checkpointed
yield # error: 8, "yield", Statement("function definition", lineno-5)
except:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
pass
+ await trio.lowlevel.checkpoint()
@@ -280,7 +280,7 @@
async def foo_try_3(): # error: 0, "exit", Statement("yield", lineno+6)
@@ x,13 x,16 @@
except:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("function definition", lineno-6)
@@ -294,17 +294,17 @@
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("function definition", lineno-4)
finally:
- await foo()
+ await trio.lowlevel.checkpoint()
@@ x,6 x,7 @@
try:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
+ await trio.lowlevel.checkpoint()
# try might crash before checkpoint
yield # error: 8, "yield", Statement("function definition", lineno-5)
- await foo()
+ await trio.lowlevel.checkpoint()
@@ x,7 x,9 @@
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
pass
+ await trio.lowlevel.checkpoint()
@@ -315,11 +315,11 @@
async def foo_try_7(): # error: 0, "exit", Statement("yield", lineno+17)
@@ x,6 x,7 @@
yield
- await foo()
+ await trio.lowlevel.checkpoint()
except SyntaxError:
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("yield", lineno-7)
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
@@ x,6 x,7 @@
# by any of the excepts, jumping straight to the finally.
@@ -332,13 +332,21 @@
@@ x,6 x,7 @@
raise
else:
- await foo()
+ await trio.lowlevel.checkpoint()
++ await trio.lowlevel.checkpoint()
+
+
+ async def foo_try_8b(): # error: 0, "exit", Statement("function definition", lineno) # error: 0, "exit", Statement("yield", lineno+3)
+@@ x,6 x,7 @@
+ ...
+ except:
+ raise
+ await trio.lowlevel.checkpoint()
# no checkpoint after yield in else
@@ x,6 x,7 @@
- await foo()
+ await trio.lowlevel.checkpoint()
else:
yield
+ await trio.lowlevel.checkpoint()
@@ -347,31 +355,31 @@
# bare except means we'll jump to finally after full execution of either try or the except
@@ x,6 x,7 @@
except ValueError:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("function definition", lineno-6)
- await foo()
+ await trio.lowlevel.checkpoint()
@@ x,6 x,7 @@
try:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
+ await trio.lowlevel.checkpoint()
# try might crash before checkpoint
yield # error: 8, "yield", Statement("function definition", lineno-5)
- await foo()
+ await trio.lowlevel.checkpoint()
@@ x,9 x,11 @@
# if
async def foo_if_1():
if condition():
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("function definition", lineno-2)
- await foo()
+ await trio.lowlevel.checkpoint()
else:
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Statement("function definition", lineno-5)
- await foo()
+ await trio.lowlevel.checkpoint()
@@ x,7 x,9 @@
...
@@ -394,7 +402,7 @@
async def foo_if_4(): # error: 0, "exit", Statement("yield", lineno+7)
@@ x,7 x,9 @@
- await foo()
+ await trio.lowlevel.checkpoint()
else:
...
+ await trio.lowlevel.checkpoint()
@@ -415,7 +423,7 @@
async def foo_if_6(): # error: 0, "exit", Statement("yield", lineno+8)
@@ x,7 x,9 @@
yield
- await foo()
+ await trio.lowlevel.checkpoint()
...
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("yield", lineno-5)
@@ -424,17 +432,17 @@
async def foo_if_7(): # error: 0, "exit", Statement("function definition", lineno)
@@ x,6 x,7 @@
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
async def foo_if_8(): # error: 0, "exit", Statement("function definition", lineno)
@@ x,21 x,25 @@
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
@@ -458,7 +466,7 @@
# normal function
@@ x,7 x,9 @@
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_func_2(): # error: 4, "exit", Statement("yield", lineno+1)
+ await trio.lowlevel.checkpoint()
@@ -470,14 +478,14 @@
@@ x,6 x,7 @@
async def foo_func_4():
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
async def foo_func_5(): # error: 0, "exit", Statement("yield", lineno+2)
@@ x,12 x,14 @@
async def foo_func_7():
- await foo()
+ await trio.lowlevel.checkpoint()
...
+ await trio.lowlevel.checkpoint()
# fmt: on
@@ -493,23 +501,23 @@
@@ x,6 x,7 @@
if condition():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-7)
# continue/else
@@ x,6 x,7 @@
continue
- await foo()
+ await trio.lowlevel.checkpoint()
else:
+ await trio.lowlevel.checkpoint()
yield # error: 8, "yield", Stmt("yield", line-8)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
@@ x,6 x,7 @@
for _ in ():
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
@@ -517,17 +525,17 @@
@@ x,14 x,17 @@
for _ in " ".strip():
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in range(0):
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in (*range(0),):
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
@@ -535,12 +543,12 @@
@@ x,10 x,12 @@
for _ in {**{}}:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in {**{}, **{}}:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
@@ -548,38 +556,38 @@
@@ x,31 x,38 @@
for _ in {}:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in """""":
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in [[], []][0]:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in [[], []].__getitem__(0):
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# not handled
for _ in list((1, 2)):
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
for _ in list():
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
@@ -587,7 +595,7 @@
@@ x,6 x,7 @@
if condition():
break
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-6)
@@ -595,7 +603,7 @@
@@ x,6 x,7 @@
while False:
- await foo() # type: ignore[unreachable]
+ await trio.lowlevel.checkpoint() # type: ignore[unreachable]
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
@@ -603,13 +611,13 @@
@@ x,11 x,13 @@
# false positive on containers
while [1, 2]:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
# will get caught by any number of linters, but trio911 will also complain
for _ in 5: # type: ignore
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
@@ -617,12 +625,12 @@
@@ x,10 x,12 @@
for i in range(1 + 1): # not handled
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for i in range(None): # type: ignore
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
@@ -630,7 +638,7 @@
@@ x,6 x,7 @@
for i in range(-3.5): # type: ignore
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
@@ -638,28 +646,28 @@
@@ x,20 x,24 @@
for i in range(10, 5):
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# binary operations are not handled
for i in range(3 - 2):
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
for i in range(10**3):
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# nor nested unary operations
for i in range(--3):
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
- await foo()
+ await trio.lowlevel.checkpoint()
@@ x,6 x,7 @@
# guaranteed iteration and await in value, but test is not guaranteed
diff --git a/tests/autofix_files/async911_insert_library.py b/tests/autofix_files/async911_insert_library.py
index 694127f..aded83b 100644
--- a/tests/autofix_files/async911_insert_library.py
+++ b/tests/autofix_files/async911_insert_library.py
@@ -1,7 +1,6 @@
# ensure that import gets added when adding checkpoints in loop body
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
import trio
diff --git a/tests/autofix_files/async911_insert_library.py.diff b/tests/autofix_files/async911_insert_library.py.diff
index 6f158ff..812e891 100644
--- a/tests/autofix_files/async911_insert_library.py.diff
+++ b/tests/autofix_files/async911_insert_library.py.diff
@@ -1,7 +1,7 @@
---
+++
@@ x,6 x,7 @@
- # ASYNCIO_NO_AUTOFIX
+ # AUTOFIX
+import trio
diff --git a/tests/autofix_files/async913.py b/tests/autofix_files/async913.py
index c9ad061..cfc28e1 100644
--- a/tests/autofix_files/async913.py
+++ b/tests/autofix_files/async913.py
@@ -1,9 +1,9 @@
-# ARG --enable=ASYNC910,ASYNC911,ASYNC913
+# ARG --enable=ASYNC910,ASYNC911,ASYNC913,ASYNC914
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
-
import trio
+
+
def condition() -> bool:
return False
@@ -16,13 +16,16 @@ async def foo():
async def foo2():
while True:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo3():
while True: # ASYNC913: 4
await trio.lowlevel.checkpoint()
if condition():
+ # This await would trigger ASYNC914 (if it was a lowlevel) after
+ # a checkpoint is inserted outside the if statement.
+ # TODO: Ideally we'd fix both in a single pass.
await foo()
@@ -60,7 +63,7 @@ async def foo_indef_and_910():
while True: # ASYNC913: 4
await trio.lowlevel.checkpoint()
if ...:
- await foo()
+ await foo() # would also trigger 914 if lowlevel
return
@@ -72,7 +75,7 @@ async def foo_indef_and_910_2():
async def foo_indef_and_911():
- await foo()
+ await trio.lowlevel.checkpoint()
while True: # ASYNC913: 4
await trio.lowlevel.checkpoint()
if condition():
@@ -83,7 +86,7 @@ async def foo_indef_and_911():
async def foo_indef_and_911_2():
- await foo()
+ await trio.lowlevel.checkpoint()
while True: # ASYNC913: 4
await trio.lowlevel.checkpoint()
while condition():
diff --git a/tests/autofix_files/async913.py.diff b/tests/autofix_files/async913.py.diff
index 51a4f6f..17aa278 100644
--- a/tests/autofix_files/async913.py.diff
+++ b/tests/autofix_files/async913.py.diff
@@ -1,13 +1,6 @@
---
+++
-@@ x,12 x,14 @@
- # ASYNCIO_NO_AUTOFIX
-
-
-+import trio
- def condition() -> bool:
- return False
-
+@@ x,6 x,7 @@
async def foo():
while True: # ASYNC913: 4
@@ -21,8 +14,8 @@
while True: # ASYNC913: 4
+ await trio.lowlevel.checkpoint()
if condition():
- await foo()
-
+ # This await would trigger ASYNC914 (if it was a lowlevel) after
+ # a checkpoint is inserted outside the if statement.
@@ x,19 x,23 @@
while True:
if condition():
@@ -53,7 +46,7 @@
while True: # ASYNC913: 4
+ await trio.lowlevel.checkpoint()
if ...:
- await foo()
+ await foo() # would also trigger 914 if lowlevel
return
@@ x,6 x,7 @@
@@ -65,7 +58,7 @@
@@ x,14 x,18 @@
async def foo_indef_and_911():
- await foo()
+ await trio.lowlevel.checkpoint()
while True: # ASYNC913: 4
+ await trio.lowlevel.checkpoint()
if condition():
@@ -76,7 +69,7 @@
async def foo_indef_and_911_2():
- await foo()
+ await trio.lowlevel.checkpoint()
while True: # ASYNC913: 4
+ await trio.lowlevel.checkpoint()
while condition():
diff --git a/tests/autofix_files/async914.py b/tests/autofix_files/async914.py
new file mode 100644
index 0000000..29d3967
--- /dev/null
+++ b/tests/autofix_files/async914.py
@@ -0,0 +1,434 @@
+# AUTOFIX
+# ARG --enable=ASYNC910,ASYNC911,ASYNC914
+import trio
+import trio.lowlevel
+from typing import Any
+
+
+def condition() -> bool:
+ return False
+
+
+async def foo() -> Any:
+ await trio.lowlevel.checkpoint()
+
+
+# branching logic
+async def foo_none_none():
+ if condition():
+ ...
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_none_false():
+ if condition():
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_none_true():
+ if condition():
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_none_obj():
+ if condition():
+ await foo()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_false_false():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ ...
+
+
+# foo_false_true(): # not possible, state can't change false<->true
+
+
+async def foo_false_obj():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ await foo()
+
+
+async def foo_true_true():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ ...
+
+
+async def foo_true_obj():
+ await trio.lowlevel.checkpoint() # false -> obj
+ if condition():
+ await foo()
+
+
+async def sequence_00():
+ await trio.lowlevel.checkpoint()
+
+
+async def sequence_01():
+ await foo()
+
+
+async def sequence_10():
+ await foo()
+
+
+async def sequence_11():
+ await foo()
+ await foo()
+
+
+# all permutations of 3
+async def sequencing_000():
+ await trio.lowlevel.checkpoint()
+
+
+async def sequencing_001():
+ await foo()
+
+
+async def sequencing_010():
+ await foo()
+
+
+async def sequencing_011():
+ await foo()
+ await foo()
+
+
+async def sequencing_100():
+ await foo()
+
+
+async def sequencing_101():
+ await foo()
+ await foo()
+
+
+async def sequencing_110():
+ await foo()
+ await foo()
+
+
+async def sequencing_111():
+ await foo()
+ await foo()
+ await foo()
+
+
+async def foo_raise():
+ raise
+
+
+# when entering an if statement, there's 3 possible states:
+# there's uncheckpointed statements
+# checkpointed by lowlevel
+# checkpointed by non-lowlevel
+
+
+# we need to determine whether to treat the if statement as a lowlevel checkpoint,
+# a non-lowlevel checkpoint, or not checkpointing. Both w/r/t statements before it, and
+# separately w/r/t statements after it.
+# and we also need to handle redundant checkpoints within bodies of it.
+
+# the if statement can:
+
+# 1. not checkpoint at all, easy
+# 2. checkpoint in all branches with lowlevel, in which case they can all be removed
+# TODO: this is not handled
+# 3. checkpoint in at least some branches with non-lowlevel.
+
+
+async def foo_if():
+ if condition():
+ await trio.lowlevel.checkpoint()
+ else:
+ await foo()
+
+
+async def foo_if_2():
+ if condition():
+ await trio.lowlevel.checkpoint()
+ else:
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_if_3():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ pass
+ else:
+ pass
+
+
+async def foo_if_4():
+ if condition():
+ await trio.lowlevel.checkpoint()
+ else:
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_if_5():
+ if condition():
+ pass
+ else:
+ pass
+ await foo()
+
+
+async def foo_if_6():
+ # lowlevel checkpoints made redundant within the same block will warn
+ if condition():
+ await foo()
+ else:
+ await foo()
+
+
+async def foo_if_7():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ await foo()
+
+
+async def foo_if_0000():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ pass
+ else:
+ pass
+
+
+async def foo_if_0001():
+ if condition():
+ pass
+ else:
+ pass
+ await foo()
+
+
+async def foo_if_0010(): # not ideal
+ await trio.lowlevel.checkpoint()
+ if condition():
+ pass
+ else:
+ await foo()
+
+
+async def foo_if_0010_2(): # not ideal
+ await trio.lowlevel.checkpoint()
+ if condition():
+ pass
+ else:
+ await foo()
+
+
+async def foo_if_0100():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ await foo()
+ else:
+ pass
+
+
+async def foo_if_1000():
+ await foo()
+ if condition():
+ pass
+ else:
+ pass
+
+
+async def foo_if_1000_1():
+ await foo()
+ yield
+ if condition():
+ await trio.lowlevel.checkpoint()
+ else:
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_if_1000_2():
+ await foo()
+ if condition():
+ yield
+ await trio.lowlevel.checkpoint()
+ else:
+ pass
+
+
+async def foo_if_1000_3():
+ await foo()
+ if condition():
+ yield
+ else:
+ pass
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_if_1000_4():
+ await foo()
+ if condition():
+ pass
+ else:
+ yield
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_if_1000_5():
+ await foo()
+ if condition():
+ pass
+ else:
+ yield
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_if_1000_6():
+ await foo()
+ if condition():
+ pass
+ else:
+ pass
+ yield
+ await trio.lowlevel.checkpoint()
+
+
+# Current logic is very conservative, treating the artificial statement injected
+# at the start of the loop as something that needs checkpointing.
+# This probably isn't a bad thing, as it's not unusual to want to checkpoint in loops
+# to let the scheduler run (even if it's not detected as infinite by us).
+async def foo_while_1():
+ await trio.lowlevel.checkpoint()
+ while condition():
+ await trio.lowlevel.checkpoint() # ignored
+
+
+async def foo_while_2():
+ await trio.lowlevel.checkpoint() # but this should probably error
+ while True:
+ await foo()
+
+
+async def foo_while_3():
+ await trio.lowlevel.checkpoint()
+ while condition():
+ await foo()
+
+
+async def foo_for_1():
+ for i in range(3):
+ await trio.lowlevel.checkpoint()
+ else:
+ await foo()
+
+
+async def foo_try_1():
+ await trio.lowlevel.checkpoint()
+ try:
+ pass
+ except:
+ pass
+
+
+async def foo_try_2():
+ await trio.lowlevel.checkpoint()
+ try:
+ pass
+ except:
+ await foo()
+
+
+async def foo_try_3():
+ await trio.lowlevel.checkpoint()
+ try:
+ await foo()
+ except:
+ pass
+
+
+async def foo_try_4():
+ await trio.lowlevel.checkpoint()
+ try:
+ await foo()
+ except:
+ await foo()
+
+
+async def foo_try_5():
+ await foo()
+ try:
+ pass
+ except:
+ pass
+
+
+async def foo_try_6():
+ await foo()
+ try:
+ pass
+ except:
+ await foo()
+
+
+async def foo_try_7():
+ await foo()
+ try:
+ await foo()
+ except:
+ pass
+
+
+async def foo_try_8():
+ await foo()
+ try:
+ await foo()
+ except:
+ await foo()
+
+
+async def foo_try_9():
+ try:
+ pass
+ except:
+ await foo()
+ else:
+ await foo()
+
+
+async def foo_try_10():
+ try:
+ await trio.lowlevel.checkpoint()
+ finally:
+ await foo()
+
+
+async def foo_try_11():
+ try:
+ await trio.lowlevel.checkpoint()
+ except:
+ await foo()
+
+
+async def foo_try_12():
+ try:
+ pass
+ except:
+ ...
+ else:
+ await foo()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_try_13():
+ try:
+ ...
+ except ValueError:
+ ...
+ except:
+ raise
+ finally:
+ await trio.lowlevel.checkpoint()
diff --git a/tests/autofix_files/async914.py.diff b/tests/autofix_files/async914.py.diff
new file mode 100644
index 0000000..47dbfc3
--- /dev/null
+++ b/tests/autofix_files/async914.py.diff
@@ -0,0 +1,363 @@
+---
++++
+@@ x,13 x,11 @@
+ async def foo_none_true():
+ if condition():
+ await trio.lowlevel.checkpoint()
+- await trio.lowlevel.checkpoint() # ASYNC914: 8 # true
+ await trio.lowlevel.checkpoint()
+
+
+ async def foo_none_obj():
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8 # obj
+ await foo()
+ await trio.lowlevel.checkpoint()
+
+@@ x,31 x,26 @@
+
+ async def foo_true_true():
+ await trio.lowlevel.checkpoint()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+ if condition():
+ ...
+
+
+ async def foo_true_obj():
+ await trio.lowlevel.checkpoint() # false -> obj
+- await trio.lowlevel.checkpoint() # ASYNC914: 4 # true -> obj
+ if condition():
+ await foo()
+
+
+ async def sequence_00():
+ await trio.lowlevel.checkpoint()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+ async def sequence_01():
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await foo()
+
+
+ async def sequence_10():
+ await foo()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+ async def sequence_11():
+@@ x,44 x,33 @@
+ # all permutations of 3
+ async def sequencing_000():
+ await trio.lowlevel.checkpoint()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+ async def sequencing_001():
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await foo()
+
+
+ async def sequencing_010():
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+- await foo()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
++ await foo()
+
+
+ async def sequencing_011():
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await foo()
+ await foo()
+
+
+ async def sequencing_100():
+ await foo()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+ async def sequencing_101():
+ await foo()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await foo()
+
+
+ async def sequencing_110():
+ await foo()
+ await foo()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+ async def sequencing_111():
+@@ x,7 x,6 @@
+
+
+ async def foo_raise():
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+ raise
+
+
+@@ x,9 x,9 @@
+ async def foo_if_3():
+ await trio.lowlevel.checkpoint()
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- else:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
++ else:
++ pass
+
+
+ async def foo_if_4():
+@@ x,25 x,22 @@
+ await trio.lowlevel.checkpoint()
+ else:
+ await trio.lowlevel.checkpoint()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+ async def foo_if_5():
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- else:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
++ else:
++ pass
+ await foo()
+
+
+ async def foo_if_6():
+ # lowlevel checkpoints made redundant within the same block will warn
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- await foo()
+- else:
+- await foo()
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ await foo()
++ else:
++ await foo()
+
+
+ async def foo_if_7():
+@@ x,34 x,31 @@
+ async def foo_if_0000():
+ await trio.lowlevel.checkpoint()
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- else:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
++ pass
++ else:
++ pass
+
+
+ async def foo_if_0001():
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+- if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- else:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ if condition():
++ pass
++ else:
++ pass
+ await foo()
+
+
+ async def foo_if_0010(): # not ideal
+ await trio.lowlevel.checkpoint()
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- else:
+- await foo()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
++ pass
++ else:
++ await foo()
+
+
+ async def foo_if_0010_2(): # not ideal
+ await trio.lowlevel.checkpoint()
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ else:
+ await foo()
+
+@@ x,17 x,15 @@
+ if condition():
+ await foo()
+ else:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
++ pass
+
+
+ async def foo_if_1000():
+ await foo()
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- else:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
++ pass
++ else:
++ pass
+
+
+ async def foo_if_1000_1():
+@@ x,7 x,6 @@
+ await trio.lowlevel.checkpoint()
+ else:
+ await trio.lowlevel.checkpoint()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+ async def foo_if_1000_2():
+@@ x,36 x,32 @@
+ yield
+ await trio.lowlevel.checkpoint()
+ else:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
++ pass
+
+
+ async def foo_if_1000_3():
+ await foo()
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+ yield
+ else:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ await trio.lowlevel.checkpoint()
+
+
+ async def foo_if_1000_4():
+ await foo()
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ else:
+ yield
+ await trio.lowlevel.checkpoint()
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+ async def foo_if_1000_5():
+ await foo()
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- else:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
++ else:
+ yield
+ await trio.lowlevel.checkpoint()
+
+@@ x,9 x,9 @@
+ async def foo_if_1000_6():
+ await foo()
+ if condition():
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- else:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
++ else:
++ pass
+ yield
+ await trio.lowlevel.checkpoint()
+
+@@ x,8 x,6 @@
+ await trio.lowlevel.checkpoint()
+ while condition():
+ await trio.lowlevel.checkpoint() # ignored
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+ async def foo_while_2():
+@@ x,15 x,15 @@
+ async def foo_try_1():
+ await trio.lowlevel.checkpoint()
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- except:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
++ except:
++ pass
+
+
+ async def foo_try_2():
+ await trio.lowlevel.checkpoint()
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ except:
+ await foo()
+
+@@ x,7 x,7 @@
+ try:
+ await foo()
+ except:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+
+
+ async def foo_try_4():
+@@ x,15 x,15 @@
+ async def foo_try_5():
+ await foo()
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- except:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
++ except:
++ pass
+
+
+ async def foo_try_6():
+ await foo()
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ except:
+ await foo()
+
+@@ x,7 x,7 @@
+ try:
+ await foo()
+ except:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+
+
+ async def foo_try_8():
+@@ x,7 x,7 @@
+
+ async def foo_try_9():
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ except:
+ await foo()
+ else:
+@@ x,7 x,7 @@
+
+ async def foo_try_12():
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ except:
+ ...
+ else:
diff --git a/tests/autofix_files/async91x_autofix.py b/tests/autofix_files/async91x_autofix.py
index 35fe6ff..9408078 100644
--- a/tests/autofix_files/async91x_autofix.py
+++ b/tests/autofix_files/async91x_autofix.py
@@ -1,6 +1,4 @@
# AUTOFIX
-# asyncio will raise the same errors, but does not have autofix available
-# ASYNCIO_NO_AUTOFIX
from __future__ import annotations
"""Docstring for file
@@ -8,9 +6,10 @@
So we make sure that import is added after it.
"""
# isort: skip_file
-# ARG --enable=ASYNC910,ASYNC911
+# ARG --enable=ASYNC910,ASYNC911,ASYNC914
from typing import Any
+
import trio
@@ -18,7 +17,7 @@ def bar() -> Any: ...
async def foo() -> Any:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo1(): # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ -52,25 +51,25 @@ async def foo_if():
async def foo_while():
- await foo()
+ await trio.lowlevel.checkpoint()
while True:
await trio.lowlevel.checkpoint()
yield # ASYNC911: 8, "yield", Statement("yield", lineno)
async def foo_while2():
- await foo()
+ await trio.lowlevel.checkpoint()
while True:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while3():
- await foo()
+ await trio.lowlevel.checkpoint()
while True:
if bar():
return
- await foo()
+ await trio.lowlevel.checkpoint()
# check that multiple checkpoints don't get inserted
@@ -106,7 +105,7 @@ async def foo_while_nested_func():
async def bar():
while bar():
...
- await foo()
+ await trio.lowlevel.checkpoint()
# Code coverage: visitors run when inside a sync function that has an async function.
@@ -140,7 +139,7 @@ async def livelocks():
async def no_checkpoint(): # ASYNC910: 0, "exit", Statement("function definition", lineno)
while bar():
try:
- await foo("1") # type: ignore[call-arg]
+ await trio.lowlevel.checkpoint("1") # type: ignore[call-arg]
except TypeError:
...
await trio.lowlevel.checkpoint()
diff --git a/tests/autofix_files/async91x_autofix.py.diff b/tests/autofix_files/async91x_autofix.py.diff
index e6e6625..e51dbd0 100644
--- a/tests/autofix_files/async91x_autofix.py.diff
+++ b/tests/autofix_files/async91x_autofix.py.diff
@@ -1,13 +1,5 @@
---
+++
-@@ x,6 x,7 @@
- # ARG --enable=ASYNC910,ASYNC911
-
- from typing import Any
-+import trio
-
-
- def bar() -> Any: ...
@@ x,30 x,38 @@
async def foo1(): # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ -41,7 +33,7 @@
async def foo_while():
- await foo()
+ await trio.lowlevel.checkpoint()
while True:
+ await trio.lowlevel.checkpoint()
yield # ASYNC911: 8, "yield", Statement("yield", lineno)
@@ -79,7 +71,7 @@
async def bar():
@@ x,3 x,4 @@
- await foo("1") # type: ignore[call-arg]
+ await trio.lowlevel.checkpoint("1") # type: ignore[call-arg]
except TypeError:
...
+ await trio.lowlevel.checkpoint()
diff --git a/tests/autofix_files/async91x_py310.py b/tests/autofix_files/async91x_py310.py
index 2f691d0..1530837 100644
--- a/tests/autofix_files/async91x_py310.py
+++ b/tests/autofix_files/async91x_py310.py
@@ -1,14 +1,13 @@
-# ARG --enable=ASYNC910,ASYNC911,ASYNC913
+# ARG --enable=ASYNC910,ASYNC911,ASYNC913,ASYNC914
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
import trio
-async def foo(): ...
+async def foo() -> None: ...
async def match_subject() -> None:
- match await foo():
+ match await trio.lowlevel.checkpoint():
case False:
pass
@@ -20,7 +19,7 @@ async def match_not_all_cases() -> ( # ASYNC910: 0, "exit", Statement("function
case 1:
...
case _:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
@@ -29,11 +28,11 @@ async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function d
):
match foo():
case 1:
- await foo()
+ await trio.lowlevel.checkpoint()
case 2:
- await foo()
+ await trio.lowlevel.checkpoint()
case _ if True:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
@@ -42,41 +41,41 @@ async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("fu
):
match foo():
case 1:
- await foo()
+ await trio.lowlevel.checkpoint()
case 2:
- await foo()
+ await trio.lowlevel.checkpoint()
case _ if foo():
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
async def match_all_cases() -> None:
match foo():
case 1:
- await foo()
+ await trio.lowlevel.checkpoint()
case 2:
- await foo()
+ await trio.lowlevel.checkpoint()
case _:
- await foo()
+ await trio.lowlevel.checkpoint()
async def match_fallback_await_in_guard() -> None:
# The case guard is only executed if the pattern matches, so we can mostly treat
# it as part of the body, except for a special case for fallback+checkpointing guard.
match foo():
- case 1 if await foo():
+ case 1 if await trio.lowlevel.checkpoint():
...
- case _ if await foo():
+ case _ if await trio.lowlevel.checkpoint():
...
async def match_checkpoint_guard() -> None:
# The above pattern is quite cursed, but this seems fairly reasonable to do.
match foo():
- case 1 if await foo():
+ case 1 if await trio.lowlevel.checkpoint():
...
case _:
- await foo()
+ await trio.lowlevel.checkpoint()
async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ -85,6 +84,6 @@ async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Stat
match foo():
case 1:
...
- case _ if await foo():
+ case _ if await trio.lowlevel.checkpoint():
...
await trio.lowlevel.checkpoint()
diff --git a/tests/autofix_files/async91x_py310.py.diff b/tests/autofix_files/async91x_py310.py.diff
index 47c84f3..f9d8cc4 100644
--- a/tests/autofix_files/async91x_py310.py.diff
+++ b/tests/autofix_files/async91x_py310.py.diff
@@ -3,29 +3,29 @@
@@ x,6 x,7 @@
...
case _:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ x,6 x,7 @@
- await foo()
+ await trio.lowlevel.checkpoint()
case _ if True:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ x,6 x,7 @@
- await foo()
+ await trio.lowlevel.checkpoint()
case _ if foo():
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
async def match_all_cases() -> None:
@@ x,3 x,4 @@
...
- case _ if await foo():
+ case _ if await trio.lowlevel.checkpoint():
...
+ await trio.lowlevel.checkpoint()
diff --git a/tests/autofix_files/async91x_py311.py b/tests/autofix_files/async91x_py311.py
index a588964..c7aa664 100644
--- a/tests/autofix_files/async91x_py311.py
+++ b/tests/autofix_files/async91x_py311.py
@@ -7,24 +7,20 @@
async912 handled in separate file
"""
-# ARG --enable=ASYNC910,ASYNC911,ASYNC913
+# ARG --enable=ASYNC910,ASYNC911,ASYNC913,ASYNC914
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
import trio
-async def foo(): ...
-
-
async def foo_try_except_star_1(): # ASYNC910: 0, "exit", Statement("function definition", lineno)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except* ValueError:
...
except* RuntimeError:
raise
else:
- await foo()
+ pass
await trio.lowlevel.checkpoint()
@@ -34,12 +30,12 @@ async def foo_try_except_star_2(): # safe
except* ValueError:
...
finally:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_except_star_3(): # safe
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except* ValueError:
raise
@@ -47,9 +43,9 @@ async def foo_try_except_star_3(): # safe
# Multiple except* handlers - should all guarantee checkpoint/raise
async def foo_try_except_star_4():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except* ValueError:
- await foo()
+ await trio.lowlevel.checkpoint()
except* TypeError:
raise
except* Exception:
@@ -62,7 +58,7 @@ async def try_else_no_raise_in_except(): # ASYNC910: 0, "exit", Statement("func
except* ValueError:
...
else:
- await foo()
+ await trio.lowlevel.checkpoint()
await trio.lowlevel.checkpoint()
@@ -72,12 +68,12 @@ async def try_else_raise_in_except():
except* ValueError:
raise
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def check_async911(): # ASYNC911: 0, "exit", Statement("yield", lineno+7)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except* ValueError:
...
except* RuntimeError:
@@ -91,7 +87,118 @@ async def check_async913():
while True: # ASYNC913: 4
await trio.lowlevel.checkpoint()
try:
+ # If this was lowlevel it'd be marked as ASYNC914 after we insert a checkpoint
+ # before the try. TODO: Ideally it'd be fixed in the same pass.
await foo()
except* ValueError:
# Missing checkpoint
...
+
+
+# ASYNC914
+async def foo_try_1():
+ await trio.lowlevel.checkpoint()
+ try:
+ pass
+ except* BaseException:
+ pass
+
+
+async def foo_try_2():
+ await trio.lowlevel.checkpoint()
+ try:
+ pass
+ except* BaseException:
+ await foo()
+
+
+async def foo_try_3():
+ await trio.lowlevel.checkpoint()
+ try:
+ await foo()
+ except* BaseException:
+ pass
+
+
+async def foo_try_4():
+ await trio.lowlevel.checkpoint()
+ try:
+ await foo()
+ except* BaseException:
+ await foo()
+
+
+async def foo_try_5():
+ await foo()
+ try:
+ pass
+ except* BaseException:
+ pass
+
+
+async def foo_try_6():
+ await foo()
+ try:
+ pass
+ except* BaseException:
+ await foo()
+
+
+async def foo_try_7():
+ await foo()
+ try:
+ await foo()
+ except* BaseException:
+ pass
+
+
+async def foo_try_8():
+ await foo()
+ try:
+ await foo()
+ except* BaseException:
+ await foo()
+
+
+async def foo_try_9():
+ try:
+ pass
+ except* BaseException:
+ await foo()
+ else:
+ await foo()
+
+
+async def foo_try_10():
+ try:
+ await trio.lowlevel.checkpoint()
+ finally:
+ await foo()
+
+
+async def foo_try_11():
+ try:
+ await trio.lowlevel.checkpoint()
+ except* BaseException:
+ await foo()
+
+
+async def foo_try_12():
+ try:
+ pass
+ except* BaseException:
+ ...
+ else:
+ await foo()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_try_13():
+ try:
+ ...
+ except* ValueError:
+ ...
+ except* BaseException:
+ raise
+ finally:
+ await trio.lowlevel.checkpoint()
diff --git a/tests/autofix_files/async91x_py311.py.diff b/tests/autofix_files/async91x_py311.py.diff
index d45305b..7730036 100644
--- a/tests/autofix_files/async91x_py311.py.diff
+++ b/tests/autofix_files/async91x_py311.py.diff
@@ -1,9 +1,11 @@
---
+++
-@@ x,6 x,7 @@
+@@ x,7 x,8 @@
+ except* RuntimeError:
raise
else:
- await foo()
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ await trio.lowlevel.checkpoint()
@@ -11,7 +13,7 @@
@@ x,6 x,7 @@
...
else:
- await foo()
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
@@ -29,5 +31,81 @@
while True: # ASYNC913: 4
+ await trio.lowlevel.checkpoint()
try:
- await foo()
- except* ValueError:
+ # If this was lowlevel it'd be marked as ASYNC914 after we insert a checkpoint
+ # before the try. TODO: Ideally it'd be fixed in the same pass.
+@@ x,15 x,15 @@
+ async def foo_try_1():
+ await trio.lowlevel.checkpoint()
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- except* BaseException:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
++ except* BaseException:
++ pass
+
+
+ async def foo_try_2():
+ await trio.lowlevel.checkpoint()
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ except* BaseException:
+ await foo()
+
+@@ x,7 x,7 @@
+ try:
+ await foo()
+ except* BaseException:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+
+
+ async def foo_try_4():
+@@ x,15 x,15 @@
+ async def foo_try_5():
+ await foo()
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
+- except* BaseException:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
++ except* BaseException:
++ pass
+
+
+ async def foo_try_6():
+ await foo()
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ except* BaseException:
+ await foo()
+
+@@ x,7 x,7 @@
+ try:
+ await foo()
+ except* BaseException:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+
+
+ async def foo_try_8():
+@@ x,7 x,7 @@
+
+ async def foo_try_9():
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ except* BaseException:
+ await foo()
+ else:
+@@ x,7 x,7 @@
+
+ async def foo_try_12():
+ try:
+- await trio.lowlevel.checkpoint() # ASYNC914: 8
++ pass
+ except* BaseException:
+ ...
+ else:
diff --git a/tests/autofix_files/exception_suppress_context_manager.py b/tests/autofix_files/exception_suppress_context_manager.py
index 0704da2..8f43f32 100644
--- a/tests/autofix_files/exception_suppress_context_manager.py
+++ b/tests/autofix_files/exception_suppress_context_manager.py
@@ -2,7 +2,6 @@
# ARG --enable=ASYNC910,ASYNC911
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
# 912 is tested in eval_files/async912.py to avoid problems with autofix/asyncio
diff --git a/tests/autofix_files/exception_suppress_context_manager_import_star.py b/tests/autofix_files/exception_suppress_context_manager_import_star.py
index a2989b3..efa51d2 100644
--- a/tests/autofix_files/exception_suppress_context_manager_import_star.py
+++ b/tests/autofix_files/exception_suppress_context_manager_import_star.py
@@ -2,7 +2,6 @@
# see exception_suppress_context_manager.py for main testing
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
from contextlib import *
import trio
diff --git a/tests/autofix_files/exception_suppress_context_manager_import_star.py.diff b/tests/autofix_files/exception_suppress_context_manager_import_star.py.diff
index 5a03e58..6bfcbb2 100644
--- a/tests/autofix_files/exception_suppress_context_manager_import_star.py.diff
+++ b/tests/autofix_files/exception_suppress_context_manager_import_star.py.diff
@@ -1,7 +1,7 @@
---
+++
@@ x,8 x,10 @@
- # ASYNCIO_NO_AUTOFIX
+ # AUTOFIX
from contextlib import *
+import trio
diff --git a/tests/autofix_files/noqa_testing.py b/tests/autofix_files/noqa_testing.py
index b55942c..ae7cbd1 100644
--- a/tests/autofix_files/noqa_testing.py
+++ b/tests/autofix_files/noqa_testing.py
@@ -1,7 +1,6 @@
# TODO: When was this file added? Why?
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
# ARG --enable=ASYNC911
import trio
diff --git a/tests/eval_files/async124.py b/tests/eval_files/async124.py
index 058f0f3..4d3c602 100644
--- a/tests/eval_files/async124.py
+++ b/tests/eval_files/async124.py
@@ -8,7 +8,6 @@
# not what the user wants though, so this would be a case in favor of making 910/911 not
# trigger when async124 does.
# AUTOFIX # all errors get "fixed" except for foo_fix_no_subfix in async124_no_autofix.py
-# ASYNCIO_NO_AUTOFIX
from typing import Any, overload
from pytest import fixture
diff --git a/tests/eval_files/async910.py b/tests/eval_files/async910.py
index f8d7680..d22eb39 100644
--- a/tests/eval_files/async910.py
+++ b/tests/eval_files/async910.py
@@ -1,5 +1,4 @@
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
# mypy: disable-error-code="unreachable"
from __future__ import annotations
@@ -9,6 +8,7 @@
import pytest
import trio
+import trio.lowlevel
_ = ""
@@ -16,13 +16,13 @@
async def foo() -> Any:
- await foo()
+ await trio.lowlevel.checkpoint()
def bar() -> Any: ...
-# ARG --enable=ASYNC910,ASYNC911
+# ARG --enable=ASYNC910,ASYNC911,ASYNC914
# ARG --no-checkpoint-warning-decorator=custom_disabled_decorator
@@ -57,24 +57,24 @@ async def foo1(): # error: 0, "exit", Statement("function definition", lineno)
# If
async def foo_if_1(): # error: 0, "exit", Statement("function definition", lineno)
if _:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_if_2():
if _:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_if_3():
- await foo()
+ await trio.lowlevel.checkpoint()
if _:
...
async def foo_if_4(): # safe
- await foo()
+ await trio.lowlevel.checkpoint()
if ...:
...
else:
@@ -83,16 +83,20 @@ async def foo_if_4(): # safe
# IfExp
async def foo_ifexp_1(): # safe
- print(await foo() if _ else await foo())
+ print(await trio.lowlevel.checkpoint() if _ else await trio.lowlevel.checkpoint())
async def foo_ifexp_2(): # error: 0, "exit", Statement("function definition", lineno)
- print(_ if False and await foo() else await foo())
+ print(
+ _
+ if False and await trio.lowlevel.checkpoint()
+ else await trio.lowlevel.checkpoint()
+ )
# nested function definition
async def foo_func_1():
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_func_2(): # error: 4, "exit", Statement("function definition", lineno)
bar()
@@ -103,7 +107,7 @@ async def foo_func_2(): # error: 4, "exit", Statement("function definition", li
# fmt: off
async def foo_func_3(): # error: 0, "exit", Statement("function definition", lineno)
async def foo_func_4():
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_func_5(): # error: 0, "exit", Statement("function definition", lineno)
@@ -139,95 +143,95 @@ async def foo_overload_1(_: str): ...
async def foo_overload_1(_: bytes | str):
- await foo()
+ await trio.lowlevel.checkpoint()
# conditions
async def foo_condition_1(): # safe
- if await foo():
+ if await trio.lowlevel.checkpoint():
...
async def foo_condition_2(): # error: 0, "exit", Statement("function definition", lineno)
- if False and await foo():
+ if False and await trio.lowlevel.checkpoint():
...
async def foo_condition_3(): # error: 0, "exit", Statement("function definition", lineno)
- if ... and await foo():
+ if ... and await trio.lowlevel.checkpoint():
...
async def foo_condition_4(): # safe
- while await foo():
+ while await trio.lowlevel.checkpoint():
...
async def foo_condition_5(): # safe
- for i in await foo():
+ for i in await trio.lowlevel.checkpoint():
...
async def foo_condition_6(): # in theory error, but not worth parsing
- for i in (None, await foo()):
+ for i in (None, await trio.lowlevel.checkpoint()):
break
# loops
async def foo_while_1(): # error: 0, "exit", Statement("function definition", lineno)
while _:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_2(): # now safe
while _:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_3(): # safe
- await foo()
+ await trio.lowlevel.checkpoint()
while _:
...
async def foo_while_4(): # error: 0, "exit", Statement("function definition", lineno)
while False:
- await foo()
+ await trio.lowlevel.checkpoint()
# for
async def foo_for_1(): # error: 0, "exit", Statement("function definition", lineno)
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_for_2(): # now safe
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_break_1(): # safe
while bar():
- await foo()
+ await trio.lowlevel.checkpoint()
break
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_break_2(): # error: 0, "exit", Statement("function definition", lineno)
while bar():
break
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_break_3(): # error: 0, "exit", Statement("function definition", lineno)
while bar():
- await foo()
+ await trio.lowlevel.checkpoint()
break
else:
...
@@ -242,22 +246,22 @@ async def foo_while_break_4(): # error: 0, "exit", Statement("function definiti
async def foo_while_continue_1(): # safe
while bar():
- await foo()
+ await trio.lowlevel.checkpoint()
continue
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_continue_2(): # safe
while bar():
continue
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_continue_3(): # error: 0, "exit", Statement("function definition", lineno)
while bar():
- await foo()
+ await trio.lowlevel.checkpoint()
continue
else:
...
@@ -289,7 +293,7 @@ async def foo_raise_1(): # safe
async def foo_raise_2(): # safe
if _:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
raise ValueError()
@@ -297,15 +301,26 @@ async def foo_raise_2(): # safe
# try
# safe only if (try or else) and all except bodies either await or raise
# if `foo()` raises a ValueError it's not checkpointed
-async def foo_try_1(): # error: 0, "exit", Statement("function definition", lineno)
+async def foo_try_1a(): # error: 0, "exit", Statement("function definition", lineno)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
...
except:
raise
else:
- await foo()
+ ...
+
+
+async def foo_try_1b(): # error: 0, "exit", Statement("function definition", lineno)
+ try:
+ ...
+ except ValueError:
+ ...
+ except:
+ raise
+ else:
+ await trio.lowlevel.checkpoint()
async def foo_try_2(): # safe
@@ -316,14 +331,14 @@ async def foo_try_2(): # safe
except:
raise
finally:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_3(): # safe
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
- await foo()
+ await trio.lowlevel.checkpoint()
except:
raise
@@ -336,11 +351,11 @@ async def foo_try_4(): # safe
except:
raise
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_5(): # safe
- await foo()
+ await trio.lowlevel.checkpoint()
try:
pass
except:
@@ -360,13 +375,20 @@ async def foo_try_6(): # error: 0, "exit", Statement("function definition", lin
async def foo_try_7(): # safe
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
pass
+async def foo_try_8(): # error: 0, "exit", Statement("function definition", lineno)
+ try:
+ ...
+ except:
+ await trio.lowlevel.checkpoint()
+
+
# https://github.com/python-trio/flake8-async/issues/45
async def to_queue(iter_func, queue):
async with iter_func() as it:
@@ -435,46 +457,46 @@ async def foo_return_1():
async def foo_return_2(): # safe
if _:
return # error: 8, "return", Statement("function definition", lineno-2)
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_return_3(): # error: 0, "exit", Statement("function definition", lineno)
if _:
- await foo()
+ await trio.lowlevel.checkpoint()
return # safe
# loop over non-empty static collection
async def foo_loop_static():
for i in [1, 2, 3]:
- await foo()
+ await trio.lowlevel.checkpoint()
# also handle range with constants
async def foo_range_1():
for i in range(5):
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_range_2():
for i in range(5, 10):
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_range_3():
for i in range(10, 5, -1):
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_range_4(): # error: 0, "exit", Statement("function definition", lineno)
for i in range(10, 5):
- await foo()
+ await trio.lowlevel.checkpoint()
# error on complex parameters
async def foo_range_5(): # error: 0, "exit", Statement("function definition", lineno)
for i in range(3 - 2):
- await foo()
+ await trio.lowlevel.checkpoint()
# https://github.com/python-trio/flake8-async/issues/47
@@ -485,7 +507,7 @@ async def f():
return
# If you delete this loop, no warning.
while bar():
- await foo()
+ await trio.lowlevel.checkpoint()
async def f1():
@@ -552,12 +574,12 @@ async def foo_test_return2():
async def foo_comprehension_1():
- [... for x in range(10) if await foo()]
+ [... for x in range(10) if await trio.lowlevel.checkpoint()]
# should error
async def foo_comprehension_2(): # error: 0, "exit", Statement("function definition", lineno)
- [await foo() for x in range(10) if bar()]
+ [await trio.lowlevel.checkpoint() for x in range(10) if bar()]
async def foo_comprehension_3():
@@ -572,7 +594,7 @@ async def foo_comprehension_3():
async def await_in_gen_target():
- (print(x) for x in await foo())
+ (print(x) for x in await trio.lowlevel.checkpoint())
async def await_everywhere_except_gen_target(): # error: 0, "exit", Statement("function definition", lineno)
@@ -594,7 +616,7 @@ async def fn_226(): # error: 0, "exit", Statement("function definition", lineno
# the await() is evaluated in the parent scope
async def foo_default_value_await():
async def bar( # error: 4, "exit", Statement("function definition", lineno)
- arg=await foo(),
+ arg=await trio.lowlevel.checkpoint(),
):
print()
@@ -603,4 +625,4 @@ async def foo_nested_empty_async():
# this previously errored because leave_FunctionDef assumed a non-empty body
async def bar(): ...
- await foo()
+ await trio.lowlevel.checkpoint()
diff --git a/tests/eval_files/async910_insert_library.py b/tests/eval_files/async910_insert_library.py
index f4149b5..1e3fe7d 100644
--- a/tests/eval_files/async910_insert_library.py
+++ b/tests/eval_files/async910_insert_library.py
@@ -1,7 +1,6 @@
# ensure that import gets added when adding checkpoints at end of function body
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
def condition() -> bool:
diff --git a/tests/eval_files/async911.py b/tests/eval_files/async911.py
index b6d256d..0c061a3 100644
--- a/tests/eval_files/async911.py
+++ b/tests/eval_files/async911.py
@@ -1,5 +1,4 @@
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
from typing import Any
import pytest
@@ -7,11 +6,11 @@
_: Any = ""
-# ARG --enable=ASYNC910,ASYNC911
+# ARG --enable=ASYNC910,ASYNC911,ASYNC914
async def foo() -> Any:
- await foo()
+ await trio.lowlevel.checkpoint()
def bar(*args) -> Any: ...
@@ -22,19 +21,19 @@ def condition() -> Any: ...
async def foo_yield_1():
- await foo()
+ await trio.lowlevel.checkpoint()
yield 5
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_yield_2():
yield # error: 4, "yield", Statement("function definition", lineno-1)
yield # error: 4, "yield", Statement("yield", lineno-1)
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_yield_3(): # error: 0, "exit", Statement("yield", lineno+2)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
@@ -50,15 +49,15 @@ async def foo_yield_return_1():
async def foo_yield_return_2():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
return # error: 4, "return", Statement("yield", lineno-1)
async def foo_yield_return_3():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
return
@@ -104,14 +103,14 @@ async def foo_async_for_4(): # safe
# for
async def foo_for(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
for i in "":
yield # error: 8, "yield", Statement("yield", lineno)
async def foo_for_1(): # error: 0, "exit", Statement("function definition", lineno) # error: 0, "exit", Statement("yield", lineno+3)
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
@@ -123,13 +122,13 @@ async def foo_while_1(): # error: 0, "exit", Statement("yield", lineno+5)
while foo():
...
else:
- await foo() # will always run
+ await trio.lowlevel.checkpoint() # will always run
yield # safe
# simple yield-in-loop case
async def foo_while_2(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield # error: 8, "yield", Statement("yield", lineno)
@@ -137,7 +136,7 @@ async def foo_while_2(): # error: 0, "exit", Statement("yield", lineno+3)
# no checkpoint after yield if else is entered
async def foo_while_3(): # error: 0, "exit", Statement("yield", lineno+5)
while foo():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
else:
yield # error: 8, "yield", Statement("yield", lineno-2) # error: 8, "yield", Statement("function definition", lineno-5)
@@ -145,7 +144,7 @@ async def foo_while_3(): # error: 0, "exit", Statement("yield", lineno+5)
# check that errors are suppressed in visit_While
async def foo_while_4(): # error: 0, "exit", Statement("yield", lineno+3) # error: 0, "exit", Statement("yield", lineno+5) # error: 0, "exit", Statement("yield", lineno+7)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield # error: 8, "yield", Statement("yield", lineno) # error: 8, "yield", Statement("yield", lineno+2) # error: 8, "yield", Statement("yield", lineno+4)
while foo():
@@ -156,7 +155,7 @@ async def foo_while_4(): # error: 0, "exit", Statement("yield", lineno+3) # err
# check that state management is handled in for loops as well
async def foo_while_4_for(): # error: 0, "exit", Statement("yield", lineno+3) # error: 0, "exit", Statement("yield", lineno+5) # error: 0, "exit", Statement("yield", lineno+7)
- await foo()
+ await trio.lowlevel.checkpoint()
for i in bar():
yield # error: 8, "yield", Statement("yield", lineno) # error: 8, "yield", Statement("yield", lineno+2) # error: 8, "yield", Statement("yield", lineno+4)
for i in bar():
@@ -167,40 +166,40 @@ async def foo_while_4_for(): # error: 0, "exit", Statement("yield", lineno+3) #
# check error suppression is reset
async def foo_while_5():
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield # error: 8, "yield", Statement("yield", lineno)
async def foo_nested_error(): # error: 8, "exit", Statement("yield", lineno+1)
yield # error: 12, "yield", Statement("function definition", lineno-1)
- await foo()
+ await trio.lowlevel.checkpoint()
# --- while + continue ---
# no checkpoint on continue
async def foo_while_continue_1(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield # error: 8, "yield", Statement("yield", lineno)
if condition():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
# multiple continues
async def foo_while_continue_2(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield # error: 8, "yield", Statement("yield", lineno)
if foo():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
continue
while foo():
yield # safe
- await foo()
+ await trio.lowlevel.checkpoint()
# --- while + break ---
@@ -210,28 +209,28 @@ async def foo_while_break_1(): # error: 0, "exit", Statement("yield", lineno+6)
if condition():
break
else:
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("function definition", lineno-6)
# no checkpoint on break
async def foo_while_break_2(): # error: 0, "exit", Statement("yield", lineno+3)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield # safe
if condition():
break
- await foo()
+ await trio.lowlevel.checkpoint()
# guaranteed if else and break
async def foo_while_break_3(): # error: 0, "exit", Statement("yield", lineno+7)
while foo():
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break # if it breaks, have checkpointed
else:
- await foo() # runs if 0-iter
+ await trio.lowlevel.checkpoint() # runs if 0-iter
yield # safe
@@ -240,38 +239,38 @@ async def foo_while_break_4(): # error: 0, "exit", Statement("yield", lineno+7)
while foo():
if condition():
break
- await foo() # might not run
+ await trio.lowlevel.checkpoint() # might not run
else:
- await foo() # might not run
+ await trio.lowlevel.checkpoint() # might not run
yield # error: 4, "yield", Statement("function definition", lineno-7)
# check break is reset on nested
async def foo_while_break_5(): # error: 0, "exit", Statement("yield", lineno+12)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield
if condition():
break
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield # safe
- await foo()
+ await trio.lowlevel.checkpoint()
yield # safe
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Statement("yield", lineno-9)
# check multiple breaks
async def foo_while_break_6(): # error: 0, "exit", Statement("yield", lineno+11)
- await foo()
+ await trio.lowlevel.checkpoint()
while foo():
yield
if condition():
break
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
yield # error: 4, "yield", Statement("yield", lineno-8)
@@ -279,7 +278,7 @@ async def foo_while_break_6(): # error: 0, "exit", Statement("yield", lineno+11
async def foo_while_break_7(): # error: 0, "exit", Statement("function definition", lineno)# error: 0, "exit", Statement("yield", lineno+5)
while foo():
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
yield
@@ -288,13 +287,13 @@ async def foo_while_break_7(): # error: 0, "exit", Statement("function definiti
async def foo_while_endless_1():
while True:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
async def foo_while_endless_2(): # error: 0, "exit", Statement("function definition", lineno)# error: 0, "exit", Statement("yield", lineno+3)
while foo():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
@@ -302,15 +301,15 @@ async def foo_while_endless_3():
while True:
...
yield # type: ignore[unreachable]
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while_endless_4():
- await foo()
+ await trio.lowlevel.checkpoint()
while True:
yield
while True:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
@@ -325,12 +324,12 @@ async def foo_try_1(): # error: 0, "exit", Statement("function definition", lin
# no checkpoint after yield in ValueError
async def foo_try_2(): # error: 0, "exit", Statement("yield", lineno+5)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
# try might not have checkpointed
yield # error: 8, "yield", Statement("function definition", lineno-5)
except:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
pass
@@ -339,7 +338,7 @@ async def foo_try_3(): # error: 0, "exit", Statement("yield", lineno+6)
try:
...
except:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
yield # error: 8, "yield", Statement("function definition", lineno-6)
@@ -350,38 +349,38 @@ async def foo_try_4(): # safe
except:
yield # error: 8, "yield", Statement("function definition", lineno-4)
finally:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_5():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
# try might crash before checkpoint
yield # error: 8, "yield", Statement("function definition", lineno-5)
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_6(): # error: 0, "exit", Statement("yield", lineno+5)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
pass
yield # error: 4, "yield", Statement("function definition", lineno-5)
async def foo_try_7(): # error: 0, "exit", Statement("yield", lineno+17)
- await foo()
+ await trio.lowlevel.checkpoint()
try:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
except SyntaxError:
yield # error: 8, "yield", Statement("yield", lineno-7)
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
pass
# If the try raises an exception without checkpointing, and it's not caught
@@ -391,26 +390,36 @@ async def foo_try_7(): # error: 0, "exit", Statement("yield", lineno+17)
## safe only if (try or else) and all except bodies either await or raise
-## if foo() raises a ValueError it's not checkpointed, and may or may not yield
-async def foo_try_8(): # error: 0, "exit", Statement("function definition", lineno) # error: 0, "exit", Statement("yield", lineno+3)
+## if the await raises an exception it's not checkpointed, and may or may not yield
+async def foo_try_8a(): # error: 0, "exit", Statement("function definition", lineno) # error: 0, "exit", Statement("yield", lineno+3)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
except ValueError:
...
except:
raise
else:
- await foo()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_try_8b(): # error: 0, "exit", Statement("function definition", lineno) # error: 0, "exit", Statement("yield", lineno+3)
+ try:
+ await trio.lowlevel.checkpoint()
+ yield
+ await trio.lowlevel.checkpoint()
+ except ValueError:
+ ...
+ except:
+ raise
# no checkpoint after yield in else
async def foo_try_9(): # error: 0, "exit", Statement("yield", lineno+6)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
yield
@@ -418,56 +427,56 @@ async def foo_try_9(): # error: 0, "exit", Statement("yield", lineno+6)
# bare except means we'll jump to finally after full execution of either try or the except
async def foo_try_10():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_10_BaseException():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except BaseException:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
# not fully covering excepts
async def foo_try_10_exception():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except ValueError:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
yield # error: 8, "yield", Statement("function definition", lineno-6)
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_10_no_except():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
finally:
# try might crash before checkpoint
yield # error: 8, "yield", Statement("function definition", lineno-5)
- await foo()
+ await trio.lowlevel.checkpoint()
# if
async def foo_if_1():
if condition():
yield # error: 8, "yield", Statement("function definition", lineno-2)
- await foo()
+ await trio.lowlevel.checkpoint()
else:
yield # error: 8, "yield", Statement("function definition", lineno-5)
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_if_2(): # error: 0, "exit", Statement("yield", lineno+6)
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
...
else:
@@ -476,7 +485,7 @@ async def foo_if_2(): # error: 0, "exit", Statement("yield", lineno+6)
async def foo_if_3(): # error: 0, "exit", Statement("yield", lineno+6)
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
yield
else:
@@ -485,20 +494,20 @@ async def foo_if_3(): # error: 0, "exit", Statement("yield", lineno+6)
async def foo_if_4(): # error: 0, "exit", Statement("yield", lineno+7)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
if condition():
- await foo()
+ await trio.lowlevel.checkpoint()
else:
...
yield # error: 4, "yield", Statement("yield", lineno-5)
async def foo_if_5(): # error: 0, "exit", Statement("yield", lineno+8)
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
yield
- await foo()
+ await trio.lowlevel.checkpoint()
else:
yield
...
@@ -506,30 +515,30 @@ async def foo_if_5(): # error: 0, "exit", Statement("yield", lineno+8)
async def foo_if_6(): # error: 0, "exit", Statement("yield", lineno+8)
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
yield
else:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
...
yield # error: 4, "yield", Statement("yield", lineno-5)
async def foo_if_7(): # error: 0, "exit", Statement("function definition", lineno)
if condition():
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_if_8(): # error: 0, "exit", Statement("function definition", lineno)
if condition():
...
else:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
- await foo()
+ await trio.lowlevel.checkpoint()
# IfExp
@@ -585,7 +594,7 @@ def foo_sync_7():
# nested function definition
async def foo_func_1():
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_func_2(): # error: 4, "exit", Statement("yield", lineno+1)
yield # error: 8, "yield", Statement("function definition", lineno-1)
@@ -595,22 +604,22 @@ async def foo_func_2(): # error: 4, "exit", Statement("yield", lineno+1)
# so we need to disable black
# fmt: off
async def foo_func_3(): # error: 0, "exit", Statement("yield", lineno+2)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
async def foo_func_4():
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_func_5(): # error: 0, "exit", Statement("yield", lineno+2)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
def foo_func_6(): # safe
yield
async def foo_func_7():
- await foo()
+ await trio.lowlevel.checkpoint()
...
# fmt: on
@@ -624,161 +633,161 @@ async def foo_boolops_1(): # error: 0, "exit", Stmt("yield", line+1)
async def foo_loop_static():
# break/else behaviour on guaranteed body execution
for _ in [1, 2, 3]:
- await foo()
+ await trio.lowlevel.checkpoint()
else:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in [1, 2, 3]:
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
else:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
yield
# continue
for _ in [1, 2, 3]:
if condition():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-7)
# continue/else
for _ in [1, 2, 3]:
if condition():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
else:
yield # error: 8, "yield", Stmt("yield", line-8)
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in [1, 2, 3]:
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
else:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
yield
# test different containers
for _ in (1, 2, 3):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in (foo(), foo()):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in ((),):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in "hello":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in b"hello":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in r"hello":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in {1, 2, 3}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in ():
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in {1: 2, 3: 4}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in " ".strip():
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in range(0):
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in (*range(0),):
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in (*(1, 2),):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in {**{}}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in {**{}, **{}}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in {**{1: 2}}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in (*range(0), *[1, 2, 3]):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in {**{}, **{1: 2}}: # type: ignore[arg-type]
- await foo()
+ await trio.lowlevel.checkpoint()
yield
x: Any = ...
for _ in (*x, *[1, 2, 3]):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in {**x, **{1: 2}}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for _ in {}:
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in "":
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in """""":
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in [[], []][0]:
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for _ in [[], []].__getitem__(0):
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# not handled
for _ in list((1, 2)):
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
for _ in list():
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# while
while True:
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
yield
@@ -786,102 +795,102 @@ async def foo_loop_static():
while True:
if condition():
break
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-6)
while True:
if condition():
continue
- await foo()
+ await trio.lowlevel.checkpoint()
if condition():
break
yield
while False:
- await foo() # type: ignore[unreachable]
+ await trio.lowlevel.checkpoint() # type: ignore[unreachable]
yield # error: 4, "yield", Stmt("yield", line-4)
while "hello":
- await foo()
+ await trio.lowlevel.checkpoint()
yield
# false positive on containers
while [1, 2]:
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
# will get caught by any number of linters, but trio911 will also complain
for _ in 5: # type: ignore
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
# range with constant arguments also handled, see more extensive tests in 910
for i in range(5):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(0x23):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(0b01):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(1 + 1): # not handled
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for i in range(None): # type: ignore
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
for i in range(+3):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(-3.5): # type: ignore
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# duplicated from 910 to have all range tests in one place
for i in range(5, 10):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(10, 5, -1):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
# ~0 == -1
for i in range(10, 5, ~0):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
# length > sys.maxsize
for i in range(27670116110564327421):
- await foo()
+ await trio.lowlevel.checkpoint()
yield
for i in range(10, 5):
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# binary operations are not handled
for i in range(3 - 2):
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
for i in range(10**3):
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-4)
# nor nested unary operations
for i in range(--3):
- await foo()
+ await trio.lowlevel.checkpoint()
yield # error: 4, "yield", Stmt("yield", line-5)
- await foo()
+ await trio.lowlevel.checkpoint()
# don't warn on pytest.fixture
diff --git a/tests/eval_files/async911_insert_library.py b/tests/eval_files/async911_insert_library.py
index e187579..2a327ee 100644
--- a/tests/eval_files/async911_insert_library.py
+++ b/tests/eval_files/async911_insert_library.py
@@ -1,7 +1,6 @@
# ensure that import gets added when adding checkpoints in loop body
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
def condition() -> bool:
diff --git a/tests/eval_files/async913.py b/tests/eval_files/async913.py
index a063e01..b1cfbc6 100644
--- a/tests/eval_files/async913.py
+++ b/tests/eval_files/async913.py
@@ -1,6 +1,7 @@
-# ARG --enable=ASYNC910,ASYNC911,ASYNC913
+# ARG --enable=ASYNC910,ASYNC911,ASYNC913,ASYNC914
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
+
+import trio
def condition() -> bool:
@@ -14,12 +15,15 @@ async def foo():
async def foo2():
while True:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo3():
while True: # ASYNC913: 4
if condition():
+ # This await would trigger ASYNC914 (if it was a lowlevel) after
+ # a checkpoint is inserted outside the if statement.
+ # TODO: Ideally we'd fix both in a single pass.
await foo()
@@ -52,7 +56,7 @@ async def foo_conditional_nested():
async def foo_indef_and_910():
while True: # ASYNC913: 4
if ...:
- await foo()
+ await foo() # would also trigger 914 if lowlevel
return
@@ -63,7 +67,7 @@ async def foo_indef_and_910_2():
async def foo_indef_and_911():
- await foo()
+ await trio.lowlevel.checkpoint()
while True: # ASYNC913: 4
if condition():
yield # ASYNC911: 12, "yield", Stmt("yield", line) # ASYNC911: 12, "yield", Stmt("yield", line+2)
@@ -72,7 +76,7 @@ async def foo_indef_and_911():
async def foo_indef_and_911_2():
- await foo()
+ await trio.lowlevel.checkpoint()
while True: # ASYNC913: 4
while condition():
yield # ASYNC911: 12, "yield", Stmt("yield", line)
diff --git a/tests/eval_files/async914.py b/tests/eval_files/async914.py
new file mode 100644
index 0000000..f9fbbe7
--- /dev/null
+++ b/tests/eval_files/async914.py
@@ -0,0 +1,468 @@
+# AUTOFIX
+# ARG --enable=ASYNC910,ASYNC911,ASYNC914
+import trio
+import trio.lowlevel
+from typing import Any
+
+
+def condition() -> bool:
+ return False
+
+
+async def foo() -> Any:
+ await trio.lowlevel.checkpoint()
+
+
+# branching logic
+async def foo_none_none():
+ if condition():
+ ...
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_none_false():
+ if condition():
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_none_true():
+ if condition():
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint() # ASYNC914: 8 # true
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_none_obj():
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8 # obj
+ await foo()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_false_false():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ ...
+
+
+# foo_false_true(): # not possible, state can't change false<->true
+
+
+async def foo_false_obj():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ await foo()
+
+
+async def foo_true_true():
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ if condition():
+ ...
+
+
+async def foo_true_obj():
+ await trio.lowlevel.checkpoint() # false -> obj
+ await trio.lowlevel.checkpoint() # ASYNC914: 4 # true -> obj
+ if condition():
+ await foo()
+
+
+async def sequence_00():
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def sequence_01():
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await foo()
+
+
+async def sequence_10():
+ await foo()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def sequence_11():
+ await foo()
+ await foo()
+
+
+# all permutations of 3
+async def sequencing_000():
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def sequencing_001():
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await foo()
+
+
+async def sequencing_010():
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await foo()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def sequencing_011():
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await foo()
+ await foo()
+
+
+async def sequencing_100():
+ await foo()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def sequencing_101():
+ await foo()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ await foo()
+
+
+async def sequencing_110():
+ await foo()
+ await foo()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def sequencing_111():
+ await foo()
+ await foo()
+ await foo()
+
+
+async def foo_raise():
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ raise
+
+
+# when entering an if statement, there's 3 possible states:
+# there's uncheckpointed statements
+# checkpointed by lowlevel
+# checkpointed by non-lowlevel
+
+
+# we need to determine whether to treat the if statement as a lowlevel checkpoint,
+# a non-lowlevel checkpoint, or not checkpointing. Both w/r/t statements before it, and
+# separately w/r/t statements after it.
+# and we also need to handle redundant checkpoints within bodies of it.
+
+# the if statement can:
+
+# 1. not checkpoint at all, easy
+# 2. checkpoint in all branches with lowlevel, in which case they can all be removed
+# TODO: this is not handled
+# 3. checkpoint in at least some branches with non-lowlevel.
+
+
+async def foo_if():
+ if condition():
+ await trio.lowlevel.checkpoint()
+ else:
+ await foo()
+
+
+async def foo_if_2():
+ if condition():
+ await trio.lowlevel.checkpoint()
+ else:
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_if_3():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ else:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+
+
+async def foo_if_4():
+ if condition():
+ await trio.lowlevel.checkpoint()
+ else:
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def foo_if_5():
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ else:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ await foo()
+
+
+async def foo_if_6():
+ # lowlevel checkpoints made redundant within the same block will warn
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ await foo()
+ else:
+ await foo()
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+
+
+async def foo_if_7():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ await foo()
+
+
+async def foo_if_0000():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ else:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def foo_if_0001():
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ else:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ await foo()
+
+
+async def foo_if_0010(): # not ideal
+ await trio.lowlevel.checkpoint()
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ else:
+ await foo()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def foo_if_0010_2(): # not ideal
+ await trio.lowlevel.checkpoint()
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ else:
+ await foo()
+
+
+async def foo_if_0100():
+ await trio.lowlevel.checkpoint()
+ if condition():
+ await foo()
+ else:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def foo_if_1000():
+ await foo()
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ else:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def foo_if_1000_1():
+ await foo()
+ yield
+ if condition():
+ await trio.lowlevel.checkpoint()
+ else:
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def foo_if_1000_2():
+ await foo()
+ if condition():
+ yield
+ await trio.lowlevel.checkpoint()
+ else:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def foo_if_1000_3():
+ await foo()
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ yield
+ else:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_if_1000_4():
+ await foo()
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ else:
+ yield
+ await trio.lowlevel.checkpoint()
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def foo_if_1000_5():
+ await foo()
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ else:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ yield
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_if_1000_6():
+ await foo()
+ if condition():
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ else:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ yield
+ await trio.lowlevel.checkpoint()
+
+
+# Current logic is very conservative, treating the artificial statement injected
+# at the start of the loop as something that needs checkpointing.
+# This probably isn't a bad thing, as it's not unusual to want to checkpoint in loops
+# to let the scheduler run (even if it's not detected as infinite by us).
+async def foo_while_1():
+ await trio.lowlevel.checkpoint()
+ while condition():
+ await trio.lowlevel.checkpoint() # ignored
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ await trio.lowlevel.checkpoint() # ASYNC914: 4
+
+
+async def foo_while_2():
+ await trio.lowlevel.checkpoint() # but this should probably error
+ while True:
+ await foo()
+
+
+async def foo_while_3():
+ await trio.lowlevel.checkpoint()
+ while condition():
+ await foo()
+
+
+async def foo_for_1():
+ for i in range(3):
+ await trio.lowlevel.checkpoint()
+ else:
+ await foo()
+
+
+async def foo_try_1():
+ await trio.lowlevel.checkpoint()
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+
+
+async def foo_try_2():
+ await trio.lowlevel.checkpoint()
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except:
+ await foo()
+
+
+async def foo_try_3():
+ await trio.lowlevel.checkpoint()
+ try:
+ await foo()
+ except:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+
+
+async def foo_try_4():
+ await trio.lowlevel.checkpoint()
+ try:
+ await foo()
+ except:
+ await foo()
+
+
+async def foo_try_5():
+ await foo()
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+
+
+async def foo_try_6():
+ await foo()
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except:
+ await foo()
+
+
+async def foo_try_7():
+ await foo()
+ try:
+ await foo()
+ except:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+
+
+async def foo_try_8():
+ await foo()
+ try:
+ await foo()
+ except:
+ await foo()
+
+
+async def foo_try_9():
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except:
+ await foo()
+ else:
+ await foo()
+
+
+async def foo_try_10():
+ try:
+ await trio.lowlevel.checkpoint()
+ finally:
+ await foo()
+
+
+async def foo_try_11():
+ try:
+ await trio.lowlevel.checkpoint()
+ except:
+ await foo()
+
+
+async def foo_try_12():
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except:
+ ...
+ else:
+ await foo()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_try_13():
+ try:
+ ...
+ except ValueError:
+ ...
+ except:
+ raise
+ finally:
+ await trio.lowlevel.checkpoint()
diff --git a/tests/eval_files/async91x_autofix.py b/tests/eval_files/async91x_autofix.py
index 7ce0a35..430048e 100644
--- a/tests/eval_files/async91x_autofix.py
+++ b/tests/eval_files/async91x_autofix.py
@@ -1,6 +1,4 @@
# AUTOFIX
-# asyncio will raise the same errors, but does not have autofix available
-# ASYNCIO_NO_AUTOFIX
from __future__ import annotations
"""Docstring for file
@@ -8,16 +6,18 @@
So we make sure that import is added after it.
"""
# isort: skip_file
-# ARG --enable=ASYNC910,ASYNC911
+# ARG --enable=ASYNC910,ASYNC911,ASYNC914
from typing import Any
+import trio
+
def bar() -> Any: ...
async def foo() -> Any:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo1(): # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ -44,24 +44,24 @@ async def foo_if():
async def foo_while():
- await foo()
+ await trio.lowlevel.checkpoint()
while True:
yield # ASYNC911: 8, "yield", Statement("yield", lineno)
async def foo_while2():
- await foo()
+ await trio.lowlevel.checkpoint()
while True:
yield
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_while3():
- await foo()
+ await trio.lowlevel.checkpoint()
while True:
if bar():
return
- await foo()
+ await trio.lowlevel.checkpoint()
# check that multiple checkpoints don't get inserted
@@ -91,7 +91,7 @@ async def foo_while_nested_func():
async def bar():
while bar():
...
- await foo()
+ await trio.lowlevel.checkpoint()
# Code coverage: visitors run when inside a sync function that has an async function.
@@ -125,6 +125,6 @@ async def livelocks():
async def no_checkpoint(): # ASYNC910: 0, "exit", Statement("function definition", lineno)
while bar():
try:
- await foo("1") # type: ignore[call-arg]
+ await trio.lowlevel.checkpoint("1") # type: ignore[call-arg]
except TypeError:
...
diff --git a/tests/eval_files/async91x_py310.py b/tests/eval_files/async91x_py310.py
index 9367fac..3783f90 100644
--- a/tests/eval_files/async91x_py310.py
+++ b/tests/eval_files/async91x_py310.py
@@ -1,14 +1,13 @@
-# ARG --enable=ASYNC910,ASYNC911,ASYNC913
+# ARG --enable=ASYNC910,ASYNC911,ASYNC913,ASYNC914
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
import trio
-async def foo(): ...
+async def foo() -> None: ...
async def match_subject() -> None:
- match await foo():
+ match await trio.lowlevel.checkpoint():
case False:
pass
@@ -20,7 +19,7 @@ async def match_not_all_cases() -> ( # ASYNC910: 0, "exit", Statement("function
case 1:
...
case _:
- await foo()
+ await trio.lowlevel.checkpoint()
async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ -28,11 +27,11 @@ async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function d
):
match foo():
case 1:
- await foo()
+ await trio.lowlevel.checkpoint()
case 2:
- await foo()
+ await trio.lowlevel.checkpoint()
case _ if True:
- await foo()
+ await trio.lowlevel.checkpoint()
async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ -40,40 +39,40 @@ async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("fu
):
match foo():
case 1:
- await foo()
+ await trio.lowlevel.checkpoint()
case 2:
- await foo()
+ await trio.lowlevel.checkpoint()
case _ if foo():
- await foo()
+ await trio.lowlevel.checkpoint()
async def match_all_cases() -> None:
match foo():
case 1:
- await foo()
+ await trio.lowlevel.checkpoint()
case 2:
- await foo()
+ await trio.lowlevel.checkpoint()
case _:
- await foo()
+ await trio.lowlevel.checkpoint()
async def match_fallback_await_in_guard() -> None:
# The case guard is only executed if the pattern matches, so we can mostly treat
# it as part of the body, except for a special case for fallback+checkpointing guard.
match foo():
- case 1 if await foo():
+ case 1 if await trio.lowlevel.checkpoint():
...
- case _ if await foo():
+ case _ if await trio.lowlevel.checkpoint():
...
async def match_checkpoint_guard() -> None:
# The above pattern is quite cursed, but this seems fairly reasonable to do.
match foo():
- case 1 if await foo():
+ case 1 if await trio.lowlevel.checkpoint():
...
case _:
- await foo()
+ await trio.lowlevel.checkpoint()
async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ -82,5 +81,5 @@ async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Stat
match foo():
case 1:
...
- case _ if await foo():
+ case _ if await trio.lowlevel.checkpoint():
...
diff --git a/tests/eval_files/async91x_py311.py b/tests/eval_files/async91x_py311.py
index 97c0e35..faede9a 100644
--- a/tests/eval_files/async91x_py311.py
+++ b/tests/eval_files/async91x_py311.py
@@ -7,24 +7,20 @@
async912 handled in separate file
"""
-# ARG --enable=ASYNC910,ASYNC911,ASYNC913
+# ARG --enable=ASYNC910,ASYNC911,ASYNC913,ASYNC914
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
import trio
-async def foo(): ...
-
-
async def foo_try_except_star_1(): # ASYNC910: 0, "exit", Statement("function definition", lineno)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except* ValueError:
...
except* RuntimeError:
raise
else:
- await foo()
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
async def foo_try_except_star_2(): # safe
@@ -33,12 +29,12 @@ async def foo_try_except_star_2(): # safe
except* ValueError:
...
finally:
- await foo()
+ await trio.lowlevel.checkpoint()
async def foo_try_except_star_3(): # safe
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except* ValueError:
raise
@@ -46,9 +42,9 @@ async def foo_try_except_star_3(): # safe
# Multiple except* handlers - should all guarantee checkpoint/raise
async def foo_try_except_star_4():
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except* ValueError:
- await foo()
+ await trio.lowlevel.checkpoint()
except* TypeError:
raise
except* Exception:
@@ -61,7 +57,7 @@ async def try_else_no_raise_in_except(): # ASYNC910: 0, "exit", Statement("func
except* ValueError:
...
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def try_else_raise_in_except():
@@ -70,12 +66,12 @@ async def try_else_raise_in_except():
except* ValueError:
raise
else:
- await foo()
+ await trio.lowlevel.checkpoint()
async def check_async911(): # ASYNC911: 0, "exit", Statement("yield", lineno+7)
try:
- await foo()
+ await trio.lowlevel.checkpoint()
except* ValueError:
...
except* RuntimeError:
@@ -86,7 +82,118 @@ async def check_async911(): # ASYNC911: 0, "exit", Statement("yield", lineno+7)
async def check_async913():
while True: # ASYNC913: 4
try:
+ # If this was lowlevel it'd be marked as ASYNC914 after we insert a checkpoint
+ # before the try. TODO: Ideally it'd be fixed in the same pass.
await foo()
except* ValueError:
# Missing checkpoint
...
+
+
+# ASYNC914
+async def foo_try_1():
+ await trio.lowlevel.checkpoint()
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except* BaseException:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+
+
+async def foo_try_2():
+ await trio.lowlevel.checkpoint()
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except* BaseException:
+ await foo()
+
+
+async def foo_try_3():
+ await trio.lowlevel.checkpoint()
+ try:
+ await foo()
+ except* BaseException:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+
+
+async def foo_try_4():
+ await trio.lowlevel.checkpoint()
+ try:
+ await foo()
+ except* BaseException:
+ await foo()
+
+
+async def foo_try_5():
+ await foo()
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except* BaseException:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+
+
+async def foo_try_6():
+ await foo()
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except* BaseException:
+ await foo()
+
+
+async def foo_try_7():
+ await foo()
+ try:
+ await foo()
+ except* BaseException:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+
+
+async def foo_try_8():
+ await foo()
+ try:
+ await foo()
+ except* BaseException:
+ await foo()
+
+
+async def foo_try_9():
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except* BaseException:
+ await foo()
+ else:
+ await foo()
+
+
+async def foo_try_10():
+ try:
+ await trio.lowlevel.checkpoint()
+ finally:
+ await foo()
+
+
+async def foo_try_11():
+ try:
+ await trio.lowlevel.checkpoint()
+ except* BaseException:
+ await foo()
+
+
+async def foo_try_12():
+ try:
+ await trio.lowlevel.checkpoint() # ASYNC914: 8
+ except* BaseException:
+ ...
+ else:
+ await foo()
+ await trio.lowlevel.checkpoint()
+
+
+async def foo_try_13():
+ try:
+ ...
+ except* ValueError:
+ ...
+ except* BaseException:
+ raise
+ finally:
+ await trio.lowlevel.checkpoint()
diff --git a/tests/eval_files/exception_suppress_context_manager.py b/tests/eval_files/exception_suppress_context_manager.py
index 4b809d7..2ede48e 100644
--- a/tests/eval_files/exception_suppress_context_manager.py
+++ b/tests/eval_files/exception_suppress_context_manager.py
@@ -2,7 +2,6 @@
# ARG --enable=ASYNC910,ASYNC911
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
# 912 is tested in eval_files/async912.py to avoid problems with autofix/asyncio
diff --git a/tests/eval_files/exception_suppress_context_manager_import_star.py b/tests/eval_files/exception_suppress_context_manager_import_star.py
index 2a33a7e..72fafa6 100644
--- a/tests/eval_files/exception_suppress_context_manager_import_star.py
+++ b/tests/eval_files/exception_suppress_context_manager_import_star.py
@@ -2,7 +2,6 @@
# see exception_suppress_context_manager.py for main testing
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
from contextlib import *
diff --git a/tests/eval_files/noqa_testing.py b/tests/eval_files/noqa_testing.py
index 1a1a344..1233d72 100644
--- a/tests/eval_files/noqa_testing.py
+++ b/tests/eval_files/noqa_testing.py
@@ -1,7 +1,6 @@
# TODO: When was this file added? Why?
# AUTOFIX
-# ASYNCIO_NO_AUTOFIX
# ARG --enable=ASYNC911
import trio
diff --git a/tests/test_flake8_async.py b/tests/test_flake8_async.py
index 771f59d..389d9ba 100644
--- a/tests/test_flake8_async.py
+++ b/tests/test_flake8_async.py
@@ -128,6 +128,14 @@ def replace_str(string: str, original: str, new: str) -> str:
from_ = anyio_
to_ = trio_
string = replace_str(string, from_, to_)
+ elif original == "trio" and new == "asyncio":
+ string = replace_str(
+ string, r"trio.lowlevel.checkpoint\(\)", "asyncio.sleep(0)"
+ )
+ elif original == "asyncio" and new == "trio":
+ string = replace_str(
+ string, r"asyncio.sleep\(0\)", r"trio.lowlevel.checkpoint\(\)"
+ )
return replace_str(string, original, new)