From 5e44a59b34ca700a00d5a3d6489d886f8969563e Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 28 Apr 2023 11:37:02 +0200
Subject: [PATCH] WIP TRIO912 unnecessary checkpoints
---
flake8_trio/visitors/visitor91x.py | 110 ++++++++-
tests/autofix_files/trio912.py | 328 +++++++++++++++++++++++++
tests/autofix_files/trio912.py.diff | 209 ++++++++++++++++
tests/eval_files/trio912.py | 365 ++++++++++++++++++++++++++++
4 files changed, 1010 insertions(+), 2 deletions(-)
create mode 100644 tests/autofix_files/trio912.py
create mode 100644 tests/autofix_files/trio912.py.diff
create mode 100644 tests/eval_files/trio912.py
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()