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)