8000 gh-99180: Remove traceback anchors in return and assign statements th… · python/cpython@bb342a0 · GitHub
[go: up one dir, main page]

Skip to content

Commit bb342a0

Browse files
committed
gh-99180: Remove traceback anchors in return and assign statements that cover all the displayed range
Signed-off-by: Pablo Galindo <pablogsal@gmail.com>
1 parent c27b09c commit bb342a0

File tree

2 files changed

+230
-27
lines changed

2 files changed

+230
-27
lines changed

Lib/test/test_traceback.py

Lines changed: 198 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,6 @@ def f_with_multiline():
675675
' ~~~~~~~~^^\n'
676676
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
677677
' return compile(code, "?", "exec")\n'
678-
' ~~~~~~~^^^^^^^^^^^^^^^^^^^\n'
679678
' File "?", line 7\n'
680679
' foo(a, z\n'
681680
' ^'
@@ -765,8 +764,8 @@ def f_with_binary_operator():
765764
def test_caret_for_binary_operators_with_spaces_and_parenthesis(self):
766765
def f_with_binary_operator():
767766
a = 1
768-
b = ""
769-
return ( a ) +b
767+
b = c = ""
768+
return ( a ) +b + c
770769

771770
lineno_f = f_with_binary_operator.__code__.co_firstlineno
772771
expected_error = (
@@ -775,7 +774,7 @@ def f_with_binary_operator():
775774
' callable()\n'
776775
' ~~~~~~~~^^\n'
777776
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
778-
' return ( a ) +b\n'
777+
' return ( a ) +b + c\n'
779778
' ~~~~~~~~~~^~\n'
780779
)
781780
result_lines = self.get_exception(f_with_binary_operator)
@@ -963,7 +962,7 @@ def f1(a):
963962
def f2(b):
964963
raise RuntimeError("fail")
965964
return f2
966-
return f1("x")("y")
965+
return f1("x")("y")("z")
967966

968967
lineno_f = f_with_call.__code__.co_firstlineno
969968
expected_error = (
@@ -972,7 +971,7 @@ def f2(b):
972971
' callable()\n'
973972
' ~~~~~~~~^^\n'
974973
f' File "{__file__}", line {lineno_f+5}, in f_with_call\n'
975-
' return f1("x")("y")\n'
974+
' return f1("x")("y")("z")\n'
976975
' ~~~~~~~^^^^^\n'
977976
f' File "{__file__}", line {lineno_f+3}, in f2\n'
978977
' raise RuntimeError("fail")\n'
@@ -1487,6 +1486,186 @@ def f():
14871486
' raise MemoryError()']
14881487
self.assertEqual(actual, expected)
14891488

1489+
def test_anchors_for_simple_return_statements_are_eluded(self):
1490+
def g():
1491+
1/0
1492+
1493+
def f():
1494+
return g()
1495+
1496+
result_lines = self.get_exception(f)
1497+
expected = ['Traceback (most recent call last):',
1498+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
1499+
" callable()",
1500+
" ~~~~~~~~^^",
1501+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1502+
" return g()",
1503+
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1504+
" 1/0",
1505+
" ~^~"
1506+
]
1507+
self.assertEqual(result_lines, expected)
1508+
1509+
def g():
1510+
1/0
1511+
1512+
def f():
1513+
return g() + 1
1514+
1515+
result_lines = self.get_exception(f)
1516+
expected = ['Traceback (most recent call last):',
1517+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
1518+
" callable()",
1519+
" ~~~~~~~~^^",
1520+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1521+
" return g() + 1",
1522+
" ~^^",
1523+
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1524+
" 1/0",
1525+
" ~^~"
1526+
]
1527+
self.assertEqual(result_lines, expected)
1528+
1529+
def g(*args):
1530+
1/0
1531+
1532+
def f():
1533+
return g(1,
1534+
2, 4,
1535+
5)
1536+
1537+
result_lines = self.get_exception(f)
1538+
expected = [ 10000 9;Traceback (most recent call last):',
1539+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
1540+
" callable()",
1541+
" ~~~~~~~~^^",
1542+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1543+
" return g(1,",
1544+
" 2, 4,",
1545+
" 5)",
1546+
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1547+
" 1/0",
1548+
" ~^~"
1549+
]
1550+
self.assertEqual(result_lines, expected)
1551+
1552+
def g(*args):
1553+
1/0
1554+
1555+
def f():
1556+
return g(1,
1557+
2, 4,
1558+
5) + 1
1559+
1560+
result_lines = self.get_exception(f)
1561+
expected = ['Traceback (most recent call last):',
1562+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
1563+
" callable()",
1564+
" ~~~~~~~~^^",
1565+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1566+
" return g(1,",
1567+
" ~^^^",
1568+
" 2, 4,",
1569+
" ^^^^^",
1570+
" 5) + 1",
1571+
" ^^",
1572+
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1573+
" 1/0",
1574+
" ~^~"
1575+
]
1576+
self.assertEqual(result_lines, expected)
1577+
1578+
1579+
1580+
def test_anchors_for_simple_assign_statements_are_eluded(self):
1581+
def g():
1582+
1/0
1583+
1584+
def f():
1585+
x = g()
1586+
1587+
result_lines = self.get_exception(f)
1588+
expected = ['Traceback (most recent call last):',
1589+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
1590+
" callable()",
1591+
" ~~~~~~~~^^",
1592+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1593+
" x = g()",
1594+
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1595+
" 1/0",
1596+
" ~^~"
1597+
]
1598+
self.assertEqual(result_lines, expected)
1599+
1600+
def g(*args):
1601+
1/0
1602+
1603+
def f():
1604+
x = g(1,
1605+
2, 3,
1606+
4)
1607+
1608+
result_lines = self.get_exception(f)
1609+
expected = ['Traceback (most recent call last):',
1610+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
1611+
" callable()",
1612+
" ~~~~~~~~^^",
1613+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1614+
" x = g(1,",
1615+
" 2, 3,",
1616+
" 4)",
1617+
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1618+
" 1/0",
1619+
" ~^~"
1620+
]
1621+
self.assertEqual(result_lines, expected)
1622+
1623+
def g():
1624+
1/0
1625+
1626+
def f():
1627+
x = y = g()
1628+
1629+
result_lines = self.get_exception(f)
1630+
expected = ['Traceback (most recent call last):',
1631+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
1632+
" callable()",
1633+
" ~~~~~~~~^^",
1634+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1635+
" x = y = g()",
1636+
" ~^^",
1637+
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1638+
" 1/0",
1639+
" ~^~"
1640+
]
1641+
self.assertEqual(result_lines, expected)
1642+
1643+
def g(*args):
1644+
1/0
1645+
1646+
def f():
1647+
x = y = g(1,
1648+
2, 3,
1649+
4)
1650+
1651+
result_lines = self.get_exception(f)
1652+
expected = ['Traceback (most recent call last):',
1653+
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
1654+
" callable()",
1655+
" ~~~~~~~~^^",
1656+
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
1657+
" x = y = g(1,",
1658+
" ~^^^",
1659+
" 2, 3,",
1660+
" ^^^^^",
1661+
" 4)",
1662+
" ^^",
1663+
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
1664+
" 1/0",
1665+
" ~^~"
1666+
]
1667+
self.assertEqual(result_lines, expected)
1668+
14901669

14911670
@requires_debug_ranges()
14921671
class PurePythonTracebackErrorCaretTests(
@@ -1681,7 +1860,7 @@ def f():
16811860
# Check a known (limited) number of recursive invocations
16821861
def g(count=10):
16831862
if count:
1684-
return g(count-1)
1863+
return g(count-1) + 1
16851864
raise ValueError
16861865

16871866
with captured_output("stderr") as stderr_g:
@@ -1695,13 +1874,13 @@ def g(count=10):
16951874
lineno_g = g.__code__.co_firstlineno
16961875
result_g = (
16971876
f' File "{__file__}", line {lineno_g+2}, in g\n'
1698-
' return g(count-1)\n'
1877+
' return g(count-1) + 1\n'
16991878
' ~^^^^^^^^^\n'
17001879
f' File "{__file__}", line {lineno_g+2}, in g\n'
1701-
' return g(count-1)\n'
1880+
' return g(count-1) + 1\n'
17021881
' ~^^^^^^^^^\n'
17031882
f' File "{__file__}", line {lineno_g+2}, in g\n'
1704-
' return g(count-1)\n'
1883+
' return g(count-1) + 1\n'
17051884
' ~^^^^^^^^^\n'
17061885
' [Previous line repeated 7 more times]\n'
17071886
f' File "{__file__}", line {lineno_g+3}, in g\n'
@@ -1740,13 +1919,10 @@ def h(count=10):
17401919
' ~^^\n'
17411920
f' File "{__file__}", line {lineno_h+2}, in h\n'
17421921
' return h(count-1)\n'
1743-
' ~^^^^^^^^^\n'
17441922
f' File "{__file__}", line {lineno_h+2}, in h\n'
17451923
' return h(count-1)\n'
1746-
' ~^^^^^^^^^\n'
17471924
f' File "{__file__}", line {lineno_h+2}, in h\n'
17481925
' return h(count-1)\n'
1749-
' ~^^^^^^^^^\n'
17501926
' [Previous line repeated 7 more times]\n'
17511927
f' File "{__file__}", line {lineno_h+3}, in h\n'
17521928
' g()\n'
@@ -1766,21 +1942,21 @@ def h(count=10):
17661942
self.fail("no error raised")
17671943
result_g = (
17681944
f' File "{__file__}", line {lineno_g+2}, in g\n'
1769-
' return g(count-1)\n'
1945+
' return g(count-1) + 1\n'
17701946
' ~^^^^^^^^^\n'
17711947
f' File "{__file__}", line {lineno_g+2}, in g\n'
1772-
' return g(count-1)\n'
1948+
' return g(count-1) + 1\n'
17731949
' ~^^^^^^^^^\n'
17741950
f' File "{__file__}", line {lineno_g+2}, in g\n'
1775-
' return g(count-1)\n'
1951+
' return g(count-1) + 1\n'
17761952
' ~^^^^^^^^^\n'
17771953
f' File "{__file__}", line {lineno_g+3}, in g\n'
17781954
' raise ValueError\n'
17791955
'ValueError\n'
17801956
)
17811957
tb_line = (
17821958
'Traceback (most recent call last):\n'
1783-
f' File "{__file__}", line {lineno_g+80}, in _check_recursive_traceback_display\n'
1959+
f' File "{__file__}", line {lineno_g+77}, in _check_recursive_traceback_display\n'
17841960
' g(traceback._RECURSIVE_CUTOFF)\n'
17851961
' ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
17861962
)
@@ -1798,13 +1974,13 @@ def h(count=10):
17981974
self.fail("no error raised")
17991975
result_g = (
18001976
f' File "{__file__}", line {lineno_g+2}, in g\n'
1801-
' return g(count-1)\n'
1977+
' return g(count-1) + 1\n'
18021978
' ~^^^^^^^^^\n'
18031979
f' File "{__file__}", line {lineno_g+2}, in g\n'
1804-
' return g(count-1)\n'
1980+
' return g(count-1) + 1\n'
18051981
' ~^^^^^^^^^\n'
18061982
f' File "{__file__}", line {lineno_g+2}, in g\n'
1807-
' return g(count-1)\n'
1983+
' return g(count-1) + 1\n'
18081984
' ~^^^^^^^^^\n'
18091985
' [Previous line repeated 1 more time]\n'
18101986
f' File "{__file__}", line {lineno_g+3}, in g\n'
@@ -1813,14 +1989,14 @@ def h(count=10):
18131989
)
18141990
tb_line = (
18151991
'Traceback (most recent call last):\n'
1816-
f' File "{__file__}", line {lineno_g+112}, in _check_recursive_traceback_display\n'
1992+
f' File "{__file__}", line {lineno_g+109}, in _check_recursive_traceback_display\n'
18171993
' g(traceback._RECURSIVE_CUTOFF + 1)\n'
18181994
' ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
18191995
)
18201996
expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
18211997
actual = stderr_g.getvalue().splitlines()
18221998
self.assertEqual(actual, expected)
1823-
1999+
18242000
@requires_debug_ranges()
18252001
def test_recursive_traceback(self):
18262002
if self.DEBUG_RANGES:

Lib/traceback.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -547,13 +547,10 @@ def format_frame_summary(self, frame_summary):
547547

548548
# attempt to parse for anchors
549549
anchors = None
550+
show_carets = False
550551
with suppress(Exception):
551552
anchors = _extract_caret_anchors_from_line_segment(segment)
552-
553-
# only use carets if there are anchors or the carets do not span all lines
554-
show_carets = False
555-
if anchors or all_lines[0][:start_offset].lstrip() or all_lines[-1][end_offset:].rstrip():
556-
show_carets = True
553+
show_carets = self.should_show_carets(start_offset, end_offset, all_lines, anchors)
557554

558555
result = []
559556

@@ -643,6 +640,36 @@ def output_line(lineno):
643640

644641
return ''.join(row)
645642

643+
def should_show_carets(self, start_offset, end_offset, all_lines, anchors):
644+
with suppress(SyntaxError, ImportError):
645+
import ast
646+
tree = ast.parse('\n'.join(all_lines))
647+
statement = tree.body[0]
648+
value = None
649+
def _spawns_full_line(value):
650+
return (
651+
value.lineno == 1
652+
and value.end_lineno == len(all_lines)
653+
and value.col_offset == start_offset
654+
and value.end_col_offset == end_offset
655+
)
656+
match statement:
657+
case ast.Return(value=ast.Call()):
658+
value = statement.value
659+
case ast.Assign(value=ast.Call()):
660+
if (
661+
len(statement.targets) == 1 and
662+
isinstance(statement.targets[0], ast.Name)
663+
):
664+
value = statement.value
665+
if value is not None and _spawns_full_line(value):
666+
return False
667+
if anchors:
668+
return True
669+
if all_lines[0][:start_offset].lstrip() or all_lines[-1][end_offset:].rstrip():
670+
return True
671+
return False
672+
646673
def format(self):
647674
"""Format the stack ready for printing.
648675

0 commit comments

Comments
 (0)
0