@@ -300,10 +300,14 @@ def _analyze_ast(self) -> None:
300300 assert self ._ast_root is not None
301301 aaa = AstArcAnalyzer (self .filename , self ._ast_root , self .raw_statements , self ._multiline )
302302 aaa .analyze ()
303- self ._with_jump_fixers = aaa .with_jump_fixers ()
303+ arcs = aaa .arcs
304+ if env .PYBEHAVIOR .exit_through_with :
305+ self ._with_jump_fixers = aaa .with_jump_fixers ()
306+ if self ._with_jump_fixers :
307+ arcs = self .fix_with_jumps (arcs )
304308
305309 self ._all_arcs = set ()
306- for l1 , l2 in self . fix_with_jumps ( aaa . arcs ) :
310+ for l1 , l2 in arcs :
307311 fl1 = self .first_line (l1 )
308312 fl2 = self .first_line (l2 )
309313 if fl1 != fl2 :
@@ -312,20 +316,41 @@ def _analyze_ast(self) -> None:
312316 self ._missing_arc_fragments = aaa .missing_arc_fragments
313317
314318 def fix_with_jumps (self , arcs : Iterable [TArc ]) -> set [TArc ]:
315- """Adjust arcs to fix jumps leaving `with` statements."""
319+ """Adjust arcs to fix jumps leaving `with` statements.
320+
321+ Consider this code:
322+
323+ with open("/tmp/test", "w") as f1:
324+ a = 2
325+ b = 3
326+ print(4)
327+
328+ In 3.10+, we get traces for lines 1, 2, 3, 1, 4. But we want to present
329+ it to the user as if it had been 1, 2, 3, 4. The arc 3->1 should be
330+ replaced with 3->4, and 1->4 should be removed.
331+
332+ For this code, the fixers dict is {(3, 1): ((1, 4), (3, 4))}. The key
333+ is the actual measured arc from the end of the with block back to the
334+ start of the with-statement. The values are start_next (the with
335+ statement to the next statement after the with), and end_next (the end
336+ of the with-statement to the next statement after the with).
337+
338+ With nested with-statements, we have to trace through a few levels to
339+ correct a longer chain of arcs.
340+
341+ """
316342 to_remove = set ()
317343 to_add = set ()
318344 for arc in arcs :
319345 if arc in self ._with_jump_fixers :
320- start = arc [0 ]
346+ end0 = arc [0 ]
321347 to_remove .add (arc )
322- start_next , prev_next = self ._with_jump_fixers [arc ]
348+ start_next , end_next = self ._with_jump_fixers [arc ]
323349 while start_next in self ._with_jump_fixers :
324350 to_remove .add (start_next )
325- start_next , prev_next = self ._with_jump_fixers [start_next ]
326- to_remove .add (prev_next )
327- to_add .add ((start , prev_next [1 ]))
328- to_remove .add (arc )
351+ start_next , end_next = self ._with_jump_fixers [start_next ]
352+ to_remove .add (end_next )
353+ to_add .add ((end0 , end_next [1 ]))
329354 to_remove .add (start_next )
330355 arcs = (set (arcs ) | to_add ) - to_remove
331356 return arcs
@@ -700,15 +725,12 @@ def analyze(self) -> None:
700725 def with_jump_fixers (self ) -> dict [TArc , tuple [TArc , TArc ]]:
701726 """Get a dict with data for fixing jumps out of with statements.
702727
703- Returns a dict. The keys are arcs leaving a with statement by jumping
728+ Returns a dict. The keys are arcs leaving a with- statement by jumping
704729 back to its start. The values are pairs: first, the arc from the start
705730 to the next statement, then the arc that exits the with without going
706731 to the start.
707732
708733 """
709- if not env .PYBEHAVIOR .exit_through_with :
710- return {}
711-
712734 fixers = {}
713735 with_nexts = {
714736 arc
@@ -721,9 +743,9 @@ def with_jump_fixers(self) -> dict[TArc, tuple[TArc, TArc]]:
721743 continue
722744 assert len (nexts ) == 1 , f"Expected one arc, got { nexts } with { start = } "
723745 nxt = nexts .pop ()
724- prvs = {arc [0 ] for arc in self .with_exits if arc [1 ] == start }
725- for prv in prvs :
726- fixers [(prv , start )] = ((start , nxt ), (prv , nxt ))
746+ ends = {arc [0 ] for arc in self .with_exits if arc [1 ] == start }
747+ for end in ends :
748+ fixers [(end , start )] = ((start , nxt ), (end , nxt ))
727749 return fixers
728750
729751 # Code object dispatchers: _code_object__*
0 commit comments