Skip to content

Commit

Permalink
pythongh-99180: Remove traceback anchors in return and assign stateme…
Browse files Browse the repository at this point in the history
…nts that cover all the displayed range

Signed-off-by: Pablo Galindo <pablogsal@gmail.com>
  • Loading branch information
pablogsal committed Dec 3, 2023
1 parent c27b09c commit d4cf9af
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 27 deletions.
148 changes: 126 additions & 22 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,6 @@ def f_with_multiline():
' ~~~~~~~~^^\n'
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
' return compile(code, "?", "exec")\n'
' ~~~~~~~^^^^^^^^^^^^^^^^^^^\n'
' File "?", line 7\n'
' foo(a, z\n'
' ^'
Expand Down Expand Up @@ -765,8 +764,8 @@ def f_with_binary_operator():
def test_caret_for_binary_operators_with_spaces_and_parenthesis(self):
def f_with_binary_operator():
a = 1
b = ""
return ( a ) +b
b = c = ""
return ( a ) +b + c

lineno_f = f_with_binary_operator.__code__.co_firstlineno
expected_error = (
Expand All @@ -775,7 +774,7 @@ def f_with_binary_operator():
' callable()\n'
' ~~~~~~~~^^\n'
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
' return ( a ) +b\n'
' return ( a ) +b + c\n'
' ~~~~~~~~~~^~\n'
)
result_lines = self.get_exception(f_with_binary_operator)
Expand Down Expand Up @@ -963,7 +962,7 @@ def f1(a):
def f2(b):
raise RuntimeError("fail")
return f2
return f1("x")("y")
return f1("x")("y")("z")

lineno_f = f_with_call.__code__.co_firstlineno
expected_error = (
Expand All @@ -972,7 +971,7 @@ def f2(b):
' callable()\n'
' ~~~~~~~~^^\n'
f' File "{__file__}", line {lineno_f+5}, in f_with_call\n'
' return f1("x")("y")\n'
' return f1("x")("y")("z")\n'
' ~~~~~~~^^^^^\n'
f' File "{__file__}", line {lineno_f+3}, in f2\n'
' raise RuntimeError("fail")\n'
Expand Down Expand Up @@ -1487,6 +1486,114 @@ def f():
' raise MemoryError()']
self.assertEqual(actual, expected)

def test_anchors_for_simple_return_statements_are_eluded(self):
def g():
1/0

def f():
return g()

result_lines = self.get_exception(f)
expected = ['Traceback (most recent call last):',
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
" callable()",
" ~~~~~~~~^^",
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
" return g()",
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
" 1/0",
" ~^~"
]
self.assertEqual(result_lines, expected)

def g(*args):
1/0

def f():
return g(1,
2, 4,
5)

result_lines = self.get_exception(f)
expected = ['Traceback (most recent call last):',
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
" callable()",
" ~~~~~~~~^^",
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
" return g(1,",
" 2, 4,",
" 5)",
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
" 1/0",
" ~^~"
]
self.assertEqual(result_lines, expected)

def test_anchors_for_simple_assign_statements_are_eluded(self):
def g():
1/0

def f():
x = g()

result_lines = self.get_exception(f)
expected = ['Traceback (most recent call last):',
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
" callable()",
" ~~~~~~~~^^",
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
" x = g()",
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
" 1/0",
" ~^~"
]
self.assertEqual(result_lines, expected)

def g(*args):
1/0

def f():
x = g(1,
2, 3,
4)

result_lines = self.get_exception(f)
expected = ['Traceback (most recent call last):',
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
" callable()",
" ~~~~~~~~^^",
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
" x = g(1,",
" 2, 3,",
" 4)",
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
" 1/0",
" ~^~"
]
self.assertEqual(result_lines, expected)



def g():
1/0

def f():
x = y = g()

result_lines = self.get_exception(f)
expected = ['Traceback (most recent call last):',
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
" callable()",
" ~~~~~~~~^^",
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
" x = y = g()",
" ~^^",
f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
" 1/0",
" ~^~"
]
self.assertEqual(result_lines, expected)


@requires_debug_ranges()
class PurePythonTracebackErrorCaretTests(
Expand Down Expand Up @@ -1681,7 +1788,7 @@ def f():
# Check a known (limited) number of recursive invocations
def g(count=10):
if count:
return g(count-1)
return g(count-1) + 1
raise ValueError

with captured_output("stderr") as stderr_g:
Expand All @@ -1695,13 +1802,13 @@ def g(count=10):
lineno_g = g.__code__.co_firstlineno
result_g = (
f' File "{__file__}", line {lineno_g+2}, in g\n'
' return g(count-1)\n'
' return g(count-1) + 1\n'
' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+2}, in g\n'
' return g(count-1)\n'
' return g(count-1) + 1\n'
' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+2}, in g\n'
' return g(count-1)\n'
' return g(count-1) + 1\n'
' ~^^^^^^^^^\n'
' [Previous line repeated 7 more times]\n'
f' File "{__file__}", line {lineno_g+3}, in g\n'
Expand Down Expand Up @@ -1740,13 +1847,10 @@ def h(count=10):
' ~^^\n'
f' File "{__file__}", line {lineno_h+2}, in h\n'
' return h(count-1)\n'
' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_h+2}, in h\n'
' return h(count-1)\n'
' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_h+2}, in h\n'
' return h(count-1)\n'
' ~^^^^^^^^^\n'
' [Previous line repeated 7 more times]\n'
f' File "{__file__}", line {lineno_h+3}, in h\n'
' g()\n'
Expand All @@ -1766,21 +1870,21 @@ def h(count=10):
self.fail("no error raised")
result_g = (
f' File "{__file__}", line {lineno_g+2}, in g\n'
' return g(count-1)\n'
' return g(count-1) + 1\n'
' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+2}, in g\n'
' return g(count-1)\n'
' return g(count-1) + 1\n'
' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+2}, in g\n'
' return g(count-1)\n'
' return g(count-1) + 1\n'
' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+3}, in g\n'
' raise ValueError\n'
'ValueError\n'
)
tb_line = (
'Traceback (most recent call last):\n'
f' File "{__file__}", line {lineno_g+80}, in _check_recursive_traceback_display\n'
f' File "{__file__}", line {lineno_g+77}, in _check_recursive_traceback_display\n'
' g(traceback._RECURSIVE_CUTOFF)\n'
' ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
)
Expand All @@ -1798,13 +1902,13 @@ def h(count=10):
self.fail("no error raised")
result_g = (
f' File "{__file__}", line {lineno_g+2}, in g\n'
' return g(count-1)\n'
' return g(count-1) + 1\n'
' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+2}, in g\n'
' return g(count-1)\n'
' return g(count-1) + 1\n'
' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+2}, in g\n'
' return g(count-1)\n'
' return g(count-1) + 1\n'
' ~^^^^^^^^^\n'
' [Previous line repeated 1 more time]\n'
f' File "{__file__}", line {lineno_g+3}, in g\n'
Expand All @@ -1813,14 +1917,14 @@ def h(count=10):
)
tb_line = (
'Traceback (most recent call last):\n'
f' File "{__file__}", line {lineno_g+112}, in _check_recursive_traceback_display\n'
f' File "{__file__}", line {lineno_g+109}, in _check_recursive_traceback_display\n'
' g(traceback._RECURSIVE_CUTOFF + 1)\n'
' ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
)
expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
actual = stderr_g.getvalue().splitlines()
self.assertEqual(actual, expected)

@requires_debug_ranges()
def test_recursive_traceback(self):
if self.DEBUG_RANGES:
Expand Down
37 changes: 32 additions & 5 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,13 +547,10 @@ def format_frame_summary(self, frame_summary):

# attempt to parse for anchors
anchors = None
show_carets = False
with suppress(Exception):
anchors = _extract_caret_anchors_from_line_segment(segment)

# only use carets if there are anchors or the carets do not span all lines
show_carets = False
if anchors or all_lines[0][:start_offset].lstrip() or all_lines[-1][end_offset:].rstrip():
show_carets = True
show_carets = self.should_show_carets(start_offset, end_offset, all_lines, anchors)

result = []

Expand Down Expand Up @@ -643,6 +640,36 @@ def output_line(lineno):

return ''.join(row)

def should_show_carets(self, start_offset, end_offset, all_lines, anchors):
with suppress(SyntaxError, ImportError):
import ast
tree = ast.parse('\n'.join(all_lines))
statement = tree.body[0]
value = None
def _spawns_full_line(value):
return (
value.lineno == 1
and value.end_lineno == len(all_lines)
and value.col_offset == start_offset
and value.end_col_offset == end_offset
)
match statement:
case ast.Return(value=ast.Call()):
value = statement.value
case ast.Assign(value=ast.Call()):
if (
len(statement.targets) == 1 and
isinstance(statement.targets[0], ast.Name)
):
value = statement.value
if value is not None and _spawns_full_line(value):
return False
if anchors:
return True
if all_lines[0][:start_offset].lstrip() or all_lines[-1][end_offset:].rstrip():
return True
return False

def format(self):
"""Format the stack ready for printing.
Expand Down

0 comments on commit d4cf9af

Please sign in to comment.