Skip to content

Commit 30ee953

Browse files
committed
pythongh-123142: Fix too wide source locations in tracebacks of exceptions from broken iterables in comprehensions
1 parent bffed80 commit 30ee953

File tree

5 files changed

+134
-2
lines changed

5 files changed

+134
-2
lines changed

Lib/test/test_dictcomps.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import traceback
12
import unittest
23

34
# For scope testing.
@@ -127,6 +128,48 @@ def test_star_expression(self):
127128
self.assertEqual({i: i*i for i in [*range(4)]}, expected)
128129
self.assertEqual({i: i*i for i in (*range(4),)}, expected)
129130

131+
def test_exception_locations(self):
132+
# The location of an exception raised from __init__ or
133+
# __next__ should should be the iterator expression
134+
135+
class Iter:
136+
def __init__(self, init_raises=False, next_raises=False):
137+
if init_raises:
138+
1/0
139+
self.next_raises = next_raises
140+
141+
def __next__(self):
142+
if self.next_raises:
143+
1/0
144+
145+
def __iter__(self):
146+
return self
147+
148+
def init_raises():
149+
try:
150+
{(x, x) for x in Iter(init_raises=True)}
151+
except Exception as e:
152+
return e
153+
154+
def next_raises():
155+
try:
156+
{(x, x) for x in Iter(next_raises=True)}
157+
except Exception as e:
158+
return e
159+
160+
for func, expected in [(init_raises, "Iter(init_raises=True)"),
161+
(next_raises, "Iter(next_raises=True)"),
162+
]:
163+
with self.subTest(func):
164+
exc = func()
165+
f = traceback.extract_tb(exc.__traceback__)[0]
166+
indent = 16
167+
co = func.__code__
168+
self.assertEqual(f.lineno, co.co_firstlineno + 2)
169+
self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
170+
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
171+
expected)
172+
130173

131174
if __name__ == "__main__":
132175
unittest.main()

Lib/test/test_listcomps.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import doctest
22
import textwrap
3+
import traceback
34
import types
45
import unittest
56

@@ -711,6 +712,48 @@ def test_multiple_comprehension_name_reuse(self):
711712
self._check_in_scopes(code, {"x": 2, "y": [3]}, ns={"x": 3}, scopes=["class"])
712713
self._check_in_scopes(code, {"x": 2, "y": [2]}, ns={"x": 3}, scopes=["function", "module"])
713714

715+
def test_exception_locations(self):
716+
# The location of an exception raised from __init__ or
717+
# __next__ should should be the iterator expression
718+
719+
class Iter:
720+
def __init__(self, init_raises=False, next_raises=False):
721+
if init_raises:
722+
1/0
723+
self.next_raises = next_raises
724+
725+
def __next__(self):
726+
if self.next_raises:
727+
1/0
728+
729+
def __iter__(self):
730+
return self
731+
732+
def init_raises():
733+
try:
734+
[x for x in Iter(init_raises=True)]
735+
except Exception as e:
736+
return e
737+
738+
def next_raises():
739+
try:
740+
[x for x in Iter(next_raises=True)]
741+
except Exception as e:
742+
return e
743+
744+
for func, expected in [(init_raises, "Iter(init_raises=True)"),
745+
(next_raises, "Iter(next_raises=True)"),
746+
]:
747+
with self.subTest(func):
748+
exc = func()
749+
f = traceback.extract_tb(exc.__traceback__)[0]
750+
indent = 16
751+
co = func.__code__
752+
self.assertEqual(f.lineno, co.co_firstlineno + 2)
753+
self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
754+
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
755+
expected)
756+
714757
__test__ = {'doctests' : doctests}
715758

716759
def load_tests(loader, tests, pattern):

Lib/test/test_setcomps.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import doctest
2+
import traceback
23
import unittest
34

45

@@ -148,6 +149,48 @@
148149
149150
"""
150151

152+
class SetComprehensionTest(unittest.TestCase):
153+
def test_exception_locations(self):
154+
# The location of an exception raised from __init__ or
155+
# __next__ should should be the iterator expression
156+
157+
class Iter:
158+
def __init__(self, init_raises=False, next_raises=False):
159+
if init_raises:
160+
1/0
161+
self.next_raises = next_raises
162+
163+
def __next__(self):
164+
if self.next_raises:
165+
1/0
166+
167+
def __iter__(self):
168+
return self
169+
170+
def init_raises():
171+
try:
172+
{x for x in Iter(init_raises=True)}
173+
except Exception as e:
174+
return e
175+
176+
def next_raises():
177+
try:
178+
{x for x in Iter(next_raises=True)}
179+
except Exception as e:
180+
return e
181+
182+
for func, expected in [(init_raises, "Iter(init_raises=True)"),
183+
(next_raises, "Iter(next_raises=True)"),
184+
]:
185+
with self.subTest(func):
186+
exc = func()
187+
f = traceback.extract_tb(exc.__traceback__)[0]
188+
indent = 16
189+
co = func.__code__
190+
self.assertEqual(f.lineno, co.co_firstlineno + 2)
191+
self.assertEqual(f.end_lineno, co.co_firstlineno + 2)
192+
self.assertEqual(f.line[f.colno - indent : f.end_colno - indent],
193+
expected)
151194

152195
__test__ = {'doctests' : doctests}
153196

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix too-wide source location in exception tracebacks coming from broken
2+
iterables in comprehensions.

Python/compile.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5174,14 +5174,15 @@ compiler_sync_comprehension_generator(struct compiler *c, location loc,
51745174
}
51755175
if (IS_LABEL(start)) {
51765176
VISIT(c, expr, gen->iter);
5177-
ADDOP(c, loc, GET_ITER);
5177+
ADDOP(c, LOC(gen->iter), GET_ITER);
51785178
}
51795179
}
51805180
}
5181+
51815182
if (IS_LABEL(start)) {
51825183
depth++;
51835184
USE_LABEL(c, start);
5184-
ADDOP_JUMP(c, loc, FOR_ITER, anchor);
5185+
ADDOP_JUMP(c, LOC(gen->iter), FOR_ITER, anchor);
51855186
}
51865187
VISIT(c, expr, gen->target);
51875188

0 commit comments

Comments
 (0)