Skip to content

Commit

Permalink
pythongh-93691: fix too broad source locations of for statement itera…
Browse files Browse the repository at this point in the history
…tors (python#120330)
  • Loading branch information
iritkatriel authored and mrahtz committed Jun 30, 2024
1 parent c7091e5 commit 0808b02
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 8 deletions.
1 change: 1 addition & 0 deletions Lib/test/test_compiler_codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def test_for_loop(self):
('GET_ITER', None, 1),
loop_lbl := self.Label(),
('FOR_ITER', exit_lbl := self.Label(), 1),
('NOP', None, 1, 1),
('STORE_NAME', 1, 1),
('LOAD_NAME', 2, 2),
('PUSH_NULL', None, 2),
Expand Down
46 changes: 46 additions & 0 deletions Lib/test/test_iter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import functools
import contextlib
import builtins
import traceback

# Test result of triple loop (too big to inline)
TRIPLETS = [(0, 0, 0), (0, 0, 1), (0, 0, 2),
Expand Down Expand Up @@ -1143,6 +1144,51 @@ def test_error_iter(self):
self.assertRaises(TypeError, iter, typ())
self.assertRaises(ZeroDivisionError, iter, BadIterableClass())

def test_exception_locations(self):
# The location of an exception raised from __init__ or
# __next__ should should be the iterator expression

class Iter:
def __init__(self, init_raises=False, next_raises=False):
if init_raises:
1/0
self.next_raises = next_raises

def __next__(self):
if self.next_raises:
1/0

def __iter__(self):
return self

def init_raises():
try:
for x in Iter(init_raises=True):
pass
except Exception as e:
return e

def next_raises():
try:
for x in Iter(next_raises=True):
pass
except Exception as e:
return e

for func, expected in [(init_raises, "Iter(init_raises=True)"),
(next_raises, "Iter(next_raises=True)"),
]:
with self.subTest(func):
exc = func()
f = traceback.extract_tb(exc.__traceback__)[0]
indent = 16
co = func.__code__
self.assertEqual(f.lineno, co.co_firstlineno + 2)
self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
expected)



if __name__ == "__main__":
unittest.main()
6 changes: 3 additions & 3 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -1650,15 +1650,15 @@ def func():
EXPECTED_EVENTS = [
(0, 'call'),
(2, 'line'),
(1, 'line'),
(-3, 'call'),
(-2, 'line'),
(-2, 'return'),
(4, 'line'),
(1, 'line'),
(4, 'line'),
(2, 'line'),
(-2, 'call'),
(-2, 'return'),
(1, 'return'),
(2, 'return'),
]

# C level events should be the same as expected and the same as Python level.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix source locations of instructions generated for the iterator of a for
statement.
9 changes: 4 additions & 5 deletions Programs/test_frozenmain.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -3025,11 +3025,18 @@ compiler_for(struct compiler *c, stmt_ty s)
RETURN_IF_ERROR(compiler_push_fblock(c, loc, FOR_LOOP, start, end, NULL));

VISIT(c, expr, s->v.For.iter);

loc = LOC(s->v.For.iter);
ADDOP(c, loc, GET_ITER);

USE_LABEL(c, start);
ADDOP_JUMP(c, loc, FOR_ITER, cleanup);

/* Add NOP to ensure correct line tracing of multiline for statements.
* It will be removed later if redundant.
*/
ADDOP(c, LOC(s->v.For.target), NOP);

USE_LABEL(c, body);
VISIT(c, expr, s->v.For.target);
VISIT_SEQ(c, stmt, s->v.For.body);
Expand Down

0 comments on commit 0808b02

Please sign in to comment.