diff --git a/flake8_trio/visitors/visitor91x.py b/flake8_trio/visitors/visitor91x.py index 64f13199..4b838cf7 100644 --- a/flake8_trio/visitors/visitor91x.py +++ b/flake8_trio/visitors/visitor91x.py @@ -56,6 +56,9 @@ class LoopState: nodes_needing_checkpoints: list[cst.Return | cst.Yield] = field( default_factory=list ) + possibly_redundant_lowlevel_checkpoints: list[cst.BaseExpression] = field( + default_factory=list + ) def copy(self): return LoopState( @@ -66,6 +69,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(), ) @@ -214,6 +218,22 @@ def leave_Yield( leave_Return = leave_Yield # type: ignore +# class RemoveLowlevelCheckpoints(cst.CSTTransformer): +# def __init__(self, stmts_to_remove: set[cst.Await]): +# 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 +# +# # for some reason you can't just return RemovalSentinel from Await, so we have to +# # visit the possible wrappers and modify their bodies instead +# +# def leave_FunctionDef(self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef) -> cst.FunctionDef: +# new_body = [stmt for stmt in updated_node.body.body if stmt not in self.stmts_to_remove] +# return updated_node.with_changes(body=updated_node.body.with_changes(body=new_body)) + + @error_class_cst @disabled_by_default class Visitor91X(Flake8TrioVisitor_cst, CommonVisitors): @@ -226,6 +246,7 @@ class Visitor91X(Flake8TrioVisitor_cst, CommonVisitors): "{0} from async iterable with no guaranteed checkpoint since {1.name} " "on line {1.lineno}." ), + "TRIO912": "Redundant checkpoint with no effect on program execution.", } def __init__(self, *args: Any, **kwargs: Any): @@ -233,9 +254,19 @@ def __init__(self, *args: Any, **kwargs: Any): self.has_yield = False self.safe_decorator = False self.async_function = False - self.uncheckpointed_statements: set[Statement] = set() self.comp_unknown = False + self.uncheckpointed_statements: set[Statement] = set() + 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 | bool + ] = {} + self.lowlevel_checkpoint_updated_nodes: dict[cst.Await, cst.Await] = {} + self.loop_state = LoopState() self.try_state = TryState() @@ -258,6 +289,7 @@ def visit_FunctionDef(self, node: cst.FunctionDef) -> bool: "safe_decorator", "async_function", "uncheckpointed_statements", + "lowlevel_checkpoints", "loop_state", "try_state", copy=True, @@ -299,8 +331,31 @@ def leave_FunctionDef( indentedblock = updated_node.body.with_changes(body=new_body) updated_node = updated_node.with_changes(body=indentedblock) + res: cst.FunctionDef = updated_node + to_remove: set[cst.Await] = set() + for expr, value in self.lowlevel_checkpoints.items(): + if value != False: + self.error(expr, error_code="TRIO912") + if self.should_autofix(): + to_remove.add(self.lowlevel_checkpoint_updated_nodes.pop(expr)) + + if to_remove: + new_body = [] + for stmt in updated_node.body.body: + if not m.matches( + stmt, + m.SimpleStatementLine( + [m.Expr(m.MatchIfTrue(lambda x: x in to_remove))] + ), + ): + new_body.append(stmt) # type: ignore + assert new_body != updated_node.body.body + res = updated_node.with_changes( + body=updated_node.body.with_changes(body=new_body) + ) + self.restore_state(original_node) - return updated_node # noqa: R504 + 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 @@ -372,12 +427,48 @@ def error_91x( error_code="TRIO911" if self.has_yield else "TRIO910", ) + def is_lowlevel_checkpoint(self, node: cst.BaseExpression) -> bool: + # TODO: match against both libraries if both are imported + return m.matches( + node, + m.Call( + m.Attribute( + m.Attribute(m.Name(self.library[0]), m.Name("lowlevel")), + m.Name("checkpoint"), + ) + ), + ) + + def visit_Await(self, node: cst.Await) -> None: + # do a match against the awaited expr + # if that is trio.lowlevel.checkpoint, and uncheckpointed statements + # are empty, raise TRIO912. + if self.is_lowlevel_checkpoint(node.expression): + if not self.uncheckpointed_statements: + self.lowlevel_checkpoints[node] = True + elif self.uncheckpointed_statements == {ARTIFICIAL_STATEMENT}: + self.loop_state.possibly_redundant_lowlevel_checkpoints.append(node) + else: + self.lowlevel_checkpoints[node] = False + # if trio.lowlevel.checkpoint and *not* empty, take note of it in a special list. + elif not self.uncheckpointed_statements: + for expr, value in self.lowlevel_checkpoints.items(): + if value == False: + self.lowlevel_checkpoints[expr] = node + + # if this is not a trio.lowlevel.checkpoint, and there are no uncheckpointed statements, check if there is a lowlevel checkpoint in the special list. If so, raise a TRIO912 for it and remove it. + def leave_Await( self, original_node: cst.Await, updated_node: cst.Await ) -> cst.Await: # the expression being awaited is not checkpointed # so only set checkpoint after the await node + # TODO: dirty hack to get identity right, the logic in visit should maybe be + # moved/split into the leave + if original_node in self.lowlevel_checkpoints: + self.lowlevel_checkpoint_updated_nodes[original_node] = updated_node + # all nodes are now checkpointed self.uncheckpointed_statements = set() return updated_node @@ -494,6 +585,10 @@ def leave_Try(self, original_node: cst.Try, updated_node: cst.Try) -> cst.Try: self.restore_state(original_node) return updated_node + # 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 it's state from before. def leave_If_test(self, node: cst.If | cst.IfExp) -> None: if not self.async_function: return @@ -604,6 +699,11 @@ def leave_While_body(self, node: cst.For | cst.While): if not any_error: self.loop_state.nodes_needing_checkpoints = [] + # but lowlevel checkpoints are redundant + for expr in self.loop_state.possibly_redundant_lowlevel_checkpoints: + self.error(expr, error_code="TRIO912") + # self.possibly_redundant_lowlevel_checkpoints.clear() + # replace artificial statements in else with prebody uncheckpointed statements # non-artificial stmts before continue/break/at body end will already be in them for stmts in ( @@ -654,6 +754,12 @@ 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: + leave_For_orelse = leave_While_orelse def leave_While( diff --git a/tests/autofix_files/trio912.py b/tests/autofix_files/trio912.py new file mode 100644 index 00000000..f10485d8 --- /dev/null +++ b/tests/autofix_files/trio912.py @@ -0,0 +1,328 @@ +# AUTOFIX +# ARG --enable=TRIO910,TRIO911,TRIO912 +from typing import Any + +import trio +import trio.lowlevel + + +async def foo() -> Any: + await trio.lowlevel.checkpoint() + + +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() + + +# when entering an if statement, there's 3 possible states: +# there's uncheckpointed statements +# checkpointed by lowlevel +# checkpointed by non-lowlevel + + +# we need to determined 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 +# 3. checkpoint in at least some branches with non-lowlevel. + + +async def foo_if(): + if ...: + await trio.lowlevel.checkpoint() + else: + await foo() + + +async def foo_if_2(): + if ...: + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() + + +async def foo_if_3(): + await trio.lowlevel.checkpoint() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_if_4(): + if ...: + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() + + +async def foo_if_5(): + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await foo() + + +async def foo_if_0000(): + await trio.lowlevel.checkpoint() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_if_0001(): + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await foo() + + +async def foo_if_0010(): + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT + else: + await foo() + + +async def foo_if_0100(): + if ...: + await foo() + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT + + +async def foo_if_1000(): + await foo() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_if_1000_1(): + await foo() + yield + if ...: + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() + + +async def foo_if_1000_2(): + await foo() + if ...: + yield + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_if_1000_3(): + await foo() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + yield + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await trio.lowlevel.checkpoint() + + +async def foo_if_1000_4(): + await foo() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + yield + await trio.lowlevel.checkpoint() + + +async def foo_if_1000_5(): + await foo() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + yield + await trio.lowlevel.checkpoint() + + +async def foo_if_1000_6(): + await foo() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + yield + await trio.lowlevel.checkpoint() + + +async def foo_while_1(): + await trio.lowlevel.checkpoint() + while ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_while_2(): + while ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await foo() + + +async def foo_while_3(): + await trio.lowlevel.checkpoint() + while ...: + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 12 + elif ...: + await trio.lowlevel.checkpoint() # TRIO912: 12 + else: + await trio.lowlevel.checkpoint() # TRIO912: 12 + + +async def foo_while_4(): + await trio.lowlevel.checkpoint() # should be 912 + while ...: + if ...: + await foo() + # and these probably shouldn't be? + elif ...: + await trio.lowlevel.checkpoint() # TRIO912: 12 + else: + await trio.lowlevel.checkpoint() # TRIO912: 12 + + +async def foo_while_5(): + await trio.lowlevel.checkpoint() # should be TRIO912 + while ...: + await foo() + + +async def foo_while_6(): + await trio.lowlevel.checkpoint() # should error + while ...: + if ...: + await foo() + elif ...: + await foo() + else: + await foo() + + +async def foo_trio_1(): + await trio.lowlevel.checkpoint() + try: + await trio.lowlevel.checkpoint() # TRIO912: 8 + except: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_trio_2(): + try: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT + except: + await foo() + + +async def foo_trio_3(): + try: + await foo() + except: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT + + +async def foo_trio_4(): + try: + await foo() + except: + await foo() + + +async def foo_trio_5(): + await foo() + try: + await trio.lowlevel.checkpoint() # TRIO912: 8 + except: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_trio_6(): + await foo() + try: + await trio.lowlevel.checkpoint() # TRIO912: 8 + except: + await foo() + + +async def foo_trio_7(): + await foo() + try: + await foo() + except: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_trio_8(): + await foo() + try: + await foo() + except: + await foo() diff --git a/tests/autofix_files/trio912.py.diff b/tests/autofix_files/trio912.py.diff new file mode 100644 index 00000000..f0c14a92 --- /dev/null +++ b/tests/autofix_files/trio912.py.diff @@ -0,0 +1,209 @@ +--- ++++ +@@ x,17 x,14 @@ + + async def sequence_00(): + await trio.lowlevel.checkpoint() +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + + async def sequence_01(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 + await foo() + + + async def sequence_10(): + await foo() +- await trio.lowlevel.checkpoint() # TRIO912: 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() # TRIO912: 4 +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + + async def sequencing_001(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 +- await trio.lowlevel.checkpoint() # TRIO912: 4 + await foo() + + + async def sequencing_010(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 +- await foo() +- await trio.lowlevel.checkpoint() # TRIO912: 4 ++ await foo() + + + async def sequencing_011(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 + await foo() + await foo() + + + async def sequencing_100(): + await foo() +- await trio.lowlevel.checkpoint() # TRIO912: 4 +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + + async def sequencing_101(): + await foo() +- await trio.lowlevel.checkpoint() # TRIO912: 4 + await foo() + + + async def sequencing_110(): + await foo() + await foo() +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + + async def sequencing_111(): +@@ x,7 x,6 @@ + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + async def foo_if_5(): + if ...: +@@ x,10 x,8 @@ + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + async def foo_if_0001(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: +@@ x,20 x,16 @@ + await foo() + + async def foo_if_0010(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT + else: + await foo() +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + async def foo_if_0100(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 + if ...: + await foo() + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + async def foo_if_1000(): + await foo() +@@ x,7 x,6 @@ + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + async def foo_if_1000_1(): + await foo() +@@ x,7 x,6 @@ + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + async def foo_if_1000_2(): + await foo() +@@ x,7 x,6 @@ + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + async def foo_if_1000_3(): + await foo() +@@ x,7 x,6 @@ + else: + yield + await trio.lowlevel.checkpoint() +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + async def foo_if_1000_5(): + await foo() +@@ x,11 x,9 @@ + await trio.lowlevel.checkpoint() + while ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + + async def foo_while_2(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 + while ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await foo() +@@ x,8 x,6 @@ + await trio.lowlevel.checkpoint() # TRIO912: 12 + else: + await trio.lowlevel.checkpoint() # TRIO912: 12 +- +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + + async def foo_while_4(): +@@ x,16 x,12 @@ + else: + await trio.lowlevel.checkpoint() # TRIO912: 12 + +- await trio.lowlevel.checkpoint() # TRIO912: 4 +- + + + async def foo_while_5(): + await trio.lowlevel.checkpoint() # should be TRIO912 + while ...: + await foo() +- +- await trio.lowlevel.checkpoint() # TRIO912: 4 + + + async def foo_while_6(): +@@ x,8 x,6 @@ + else: + await foo() + +- await trio.lowlevel.checkpoint() # TRIO912: 4 +- + + async def foo_trio_1(): + await trio.lowlevel.checkpoint() +@@ x,7 x,6 @@ + + + async def foo_trio_2(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 + try: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT + except: +@@ x,7 x,6 @@ + + + async def foo_trio_3(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 + try: + await foo() + except: +@@ x,7 x,6 @@ + + + async def foo_trio_4(): +- await trio.lowlevel.checkpoint() # TRIO912: 4 + try: + await foo() + except: diff --git a/tests/eval_files/trio912.py b/tests/eval_files/trio912.py new file mode 100644 index 00000000..fbdc6d46 --- /dev/null +++ b/tests/eval_files/trio912.py @@ -0,0 +1,365 @@ +# AUTOFIX +# ARG --enable=TRIO910,TRIO911,TRIO912 +import trio +import trio.lowlevel +from typing import Any + + +async def foo() -> Any: + await trio.lowlevel.checkpoint() + + +async def sequence_00(): + await trio.lowlevel.checkpoint() + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def sequence_01(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + await foo() + + +async def sequence_10(): + await foo() + await trio.lowlevel.checkpoint() # TRIO912: 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() # TRIO912: 4 + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def sequencing_001(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + await trio.lowlevel.checkpoint() # TRIO912: 4 + await foo() + + +async def sequencing_010(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + await foo() + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def sequencing_011(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + await foo() + await foo() + + +async def sequencing_100(): + await foo() + await trio.lowlevel.checkpoint() # TRIO912: 4 + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def sequencing_101(): + await foo() + await trio.lowlevel.checkpoint() # TRIO912: 4 + await foo() + + +async def sequencing_110(): + await foo() + await foo() + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def sequencing_111(): + await foo() + await foo() + await foo() + + +# when entering an if statement, there's 3 possible states: +# there's uncheckpointed statements +# checkpointed by lowlevel +# checkpointed by non-lowlevel + + +# we need to determined 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 +# 3. checkpoint in at least some branches with non-lowlevel. + + +async def foo_if(): + if ...: + await trio.lowlevel.checkpoint() + else: + await foo() + + +async def foo_if_2(): + if ...: + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() + + +async def foo_if_3(): + await trio.lowlevel.checkpoint() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_if_4(): + if ...: + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_if_5(): + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await foo() + + +async def foo_if_0000(): + await trio.lowlevel.checkpoint() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_if_0001(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await foo() + + +async def foo_if_0010(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT + else: + await foo() + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_if_0100(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + if ...: + await foo() + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_if_1000(): + await foo() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_if_1000_1(): + await foo() + yield + if ...: + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_if_1000_2(): + await foo() + if ...: + yield + await trio.lowlevel.checkpoint() + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_if_1000_3(): + await foo() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + yield + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await trio.lowlevel.checkpoint() + + +async def foo_if_1000_4(): + await foo() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + yield + await trio.lowlevel.checkpoint() + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_if_1000_5(): + await foo() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + yield + await trio.lowlevel.checkpoint() + + +async def foo_if_1000_6(): + await foo() + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + else: + await trio.lowlevel.checkpoint() # TRIO912: 8 + yield + await trio.lowlevel.checkpoint() + + +async def foo_while_1(): + await trio.lowlevel.checkpoint() + while ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_while_2(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + while ...: + await trio.lowlevel.checkpoint() # TRIO912: 8 + await foo() + + +async def foo_while_3(): + await trio.lowlevel.checkpoint() + while ...: + if ...: + await trio.lowlevel.checkpoint() # TRIO912: 12 + elif ...: + await trio.lowlevel.checkpoint() # TRIO912: 12 + else: + await trio.lowlevel.checkpoint() # TRIO912: 12 + + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_while_4(): + await trio.lowlevel.checkpoint() # should be 912 + while ...: + if ...: + await foo() + # and these probably shouldn't be? + elif ...: + await trio.lowlevel.checkpoint() # TRIO912: 12 + else: + await trio.lowlevel.checkpoint() # TRIO912: 12 + + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_while_5(): + await trio.lowlevel.checkpoint() # should be TRIO912 + while ...: + await foo() + + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_while_6(): + await trio.lowlevel.checkpoint() # should error + while ...: + if ...: + await foo() + elif ...: + await foo() + else: + await foo() + + await trio.lowlevel.checkpoint() # TRIO912: 4 + + +async def foo_trio_1(): + await trio.lowlevel.checkpoint() + try: + await trio.lowlevel.checkpoint() # TRIO912: 8 + except: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_trio_2(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + try: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT + except: + await foo() + + +async def foo_trio_3(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + try: + await foo() + except: + await trio.lowlevel.checkpoint() # TRIO912: 8 # INCORRECT + + +async def foo_trio_4(): + await trio.lowlevel.checkpoint() # TRIO912: 4 + try: + await foo() + except: + await foo() + + +async def foo_trio_5(): + await foo() + try: + await trio.lowlevel.checkpoint() # TRIO912: 8 + except: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_trio_6(): + await foo() + try: + await trio.lowlevel.checkpoint() # TRIO912: 8 + except: + await foo() + + +async def foo_trio_7(): + await foo() + try: + await foo() + except: + await trio.lowlevel.checkpoint() # TRIO912: 8 + + +async def foo_trio_8(): + await foo() + try: + await foo() + except: + await foo()