@@ -300,10 +300,14 @@ def _analyze_ast(self) -> None:
300
300
assert self ._ast_root is not None
301
301
aaa = AstArcAnalyzer (self .filename , self ._ast_root , self .raw_statements , self ._multiline )
302
302
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 )
304
308
305
309
self ._all_arcs = set ()
306
- for l1 , l2 in self . fix_with_jumps ( aaa . arcs ) :
310
+ for l1 , l2 in arcs :
307
311
fl1 = self .first_line (l1 )
308
312
fl2 = self .first_line (l2 )
309
313
if fl1 != fl2 :
@@ -312,20 +316,41 @@ def _analyze_ast(self) -> None:
312
316
self ._missing_arc_fragments = aaa .missing_arc_fragments
313
317
314
318
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
+ """
316
342
to_remove = set ()
317
343
to_add = set ()
318
344
for arc in arcs :
319
345
if arc in self ._with_jump_fixers :
320
- start = arc [0 ]
346
+ end0 = arc [0 ]
321
347
to_remove .add (arc )
322
- start_next , prev_next = self ._with_jump_fixers [arc ]
348
+ start_next , end_next = self ._with_jump_fixers [arc ]
323
349
while start_next in self ._with_jump_fixers :
324
350
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 ]))
329
354
to_remove .add (start_next )
330
355
arcs = (set (arcs ) | to_add ) - to_remove
331
356
return arcs
@@ -700,15 +725,12 @@ def analyze(self) -> None:
700
725
def with_jump_fixers (self ) -> dict [TArc , tuple [TArc , TArc ]]:
701
726
"""Get a dict with data for fixing jumps out of with statements.
702
727
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
704
729
back to its start. The values are pairs: first, the arc from the start
705
730
to the next statement, then the arc that exits the with without going
706
731
to the start.
707
732
708
733
"""
709
- if not env .PYBEHAVIOR .exit_through_with :
710
- return {}
711
-
712
734
fixers = {}
713
735
with_nexts = {
714
736
arc
@@ -721,9 +743,9 @@ def with_jump_fixers(self) -> dict[TArc, tuple[TArc, TArc]]:
721
743
continue
722
744
assert len (nexts ) == 1 , f"Expected one arc, got { nexts } with { start = } "
723
745
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 ))
727
749
return fixers
728
750
729
751
# Code object dispatchers: _code_object__*
0 commit comments