Skip to content

Commit 97b69db

Browse files
authored
gh-93691: fix too broad source locations of for statement iterators (#120330)
1 parent 755dab7 commit 97b69db

File tree

6 files changed

+63
-8
lines changed

6 files changed

+63
-8
lines changed

Lib/test/test_compiler_codegen.py

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def test_for_loop(self):
4949
('GET_ITER', None, 1),
5050
loop_lbl := self.Label(),
5151
('FOR_ITER', exit_lbl := self.Label(), 1),
52+
('NOP', None, 1, 1),
5253
('STORE_NAME', 1, 1),
5354
('LOAD_NAME', 2, 2),
5455
('PUSH_NULL', None, 2),

Lib/test/test_iter.py

+46
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import functools
1111
import contextlib
1212
import builtins
13+
import traceback
1314

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

1147+
def test_exception_locations(self):
1148+
# The location of an exception raised from __init__ or
1149+
# __next__ should should be the iterator expression
1150+
1151+
class Iter:
1152+
def __init__(self, init_raises=False, next_raises=False):
1153+
if init_raises:
1154+
1/0
1155+
self.next_raises = next_raises
1156+
1157+
def __next__(self):
1158+
if self.next_raises:
1159+
1/0
1160+
1161+
def __iter__(self):
1162+
return self
1163+
1164+
def init_raises():
1165+
try:
1166+
for x in Iter(init_raises=True):
1167+
pass
1168+
except Exception as e:
1169+
return e
1170+
1171+
def next_raises():
1172+
try:
1173+
for x in Iter(next_raises=True):
1174+
pass
1175+
except Exception as e:
1176+
return e
1177+
1178+
for func, expected in [(init_raises, "Iter(init_raises=True)"),
1179+
(next_raises, "Iter(next_raises=True)"),
1180+
]:
1181+
with self.subTest(func):
1182+
exc = func()
1183+
f = traceback.extract_tb(exc.__traceback__)[0]
1184+
indent = 16
1185+
co = func.__code__
1186+
self.assertEqual(f.lineno, co.co_firstlineno + 2)
1187+
self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
1188+
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
1189+
expected)
1190+
1191+
11461192

11471193
if __name__ == "__main__":
11481194
unittest.main()

Lib/test/test_sys_settrace.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1650,15 +1650,15 @@ def func():
16501650
EXPECTED_EVENTS = [
16511651
(0, 'call'),
16521652
(2, 'line'),
1653-
(1, 'line'),
16541653
(-3, 'call'),
16551654
(-2, 'line'),
16561655
(-2, 'return'),
1657-
(4, 'line'),
16581656
(1, 'line'),
1657+
(4, 'line'),
1658+
(2, 'line'),
16591659
(-2, 'call'),
16601660
(-2, 'return'),
1661-
(1, 'return'),
1661+
(2, 'return'),
16621662
]
16631663

16641664
# C level events should be the same as expected and the same as Python level.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix source locations of instructions generated for the iterator of a for
2+
statement.

Programs/test_frozenmain.h

+4-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/compile.c

+7
Original file line numberDiff line numberDiff line change
@@ -3025,11 +3025,18 @@ compiler_for(struct compiler *c, stmt_ty s)
30253025
RETURN_IF_ERROR(compiler_push_fblock(c, loc, FOR_LOOP, start, end, NULL));
30263026

30273027
VISIT(c, expr, s->v.For.iter);
3028+
3029+
loc = LOC(s->v.For.iter);
30283030
ADDOP(c, loc, GET_ITER);
30293031

30303032
USE_LABEL(c, start);
30313033
ADDOP_JUMP(c, loc, FOR_ITER, cleanup);
30323034

3035+
/* Add NOP to ensure correct line tracing of multiline for statements.
3036+
* It will be removed later if redundant.
3037+
*/
3038+
ADDOP(c, LOC(s->v.For.target), NOP);
3039+
30333040
USE_LABEL(c, body);
30343041
VISIT(c, expr, s->v.For.target);
30353042
VISIT_SEQ(c, stmt, s->v.For.body);

0 commit comments

Comments
 (0)