summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPablo Galindo Salgado <Pablogsal@gmail.com>2024-05-01 13:42:10 (GMT)
committerGitHub <noreply@github.com>2024-05-01 13:42:10 (GMT)
commit4a08a75cf4c490f7c43ede69bdf6e5a79c6a3af3 (patch)
tree3f33ef13df1da9660e6f54227b873b9ac0aff42e
parentc1bf4874c1e9db2beda1d62c8c241229783c789b (diff)
downloadcpython-4a08a75cf4c490f7c43ede69bdf6e5a79c6a3af3.zip
cpython-4a08a75cf4c490f7c43ede69bdf6e5a79c6a3af3.tar.gz
cpython-4a08a75cf4c490f7c43ede69bdf6e5a79c6a3af3.tar.bz2
gh-99180: Remove traceback anchors in return and assign statements that cover all the displayed range (#112670)
-rw-r--r--Lib/test/test_traceback.py233
-rw-r--r--Lib/traceback.py38
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2023-12-03-18-21-59.gh-issue-99180.5m0V0q.rst2
3 files changed, 240 insertions, 33 deletions
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 8927fcc..41f8251 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -695,7 +695,6 @@ class TracebackErrorLocationCaretTestBase:
' ~~~~~~~~^^\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'
' ^'
@@ -785,8 +784,8 @@ class TracebackErrorLocationCaretTestBase:
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 = (
@@ -795,7 +794,7 @@ class TracebackErrorLocationCaretTestBase:
' 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)
@@ -983,7 +982,7 @@ class TracebackErrorLocationCaretTestBase:
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 = (
@@ -992,7 +991,7 @@ class TracebackErrorLocationCaretTestBase:
' 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'
@@ -1507,6 +1506,184 @@ class TracebackErrorLocationCaretTestBase:
' raise MemoryError()']
self.assertEqual(actual, expected)
+ def test_anchors_for_simple_return_statements_are_elided(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():
+ 1/0
+
+ def f():
+ return g() + 1
+
+ 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",
+ " ~^^",
+ 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 g(*args):
+ 1/0
+
+ def f():
+ return g(1,
+ 2, 4,
+ 5) + 1
+
+ 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) + 1",
+ " ^^",
+ 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_elided(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)
+
+ def g(*args):
+ 1/0
+
+ def f():
+ x = y = 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 = y = g(1,",
+ " ~^^^",
+ " 2, 3,",
+ " ^^^^^",
+ " 4)",
+ " ^^",
+ f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+ " 1/0",
+ " ~^~"
+ ]
+ self.assertEqual(result_lines, expected)
+
@requires_debug_ranges()
class PurePythonTracebackErrorCaretTests(
@@ -1701,7 +1878,7 @@ class TracebackFormatMixin:
# 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:
@@ -1715,13 +1892,13 @@ class TracebackFormatMixin:
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'
@@ -1760,13 +1937,10 @@ class TracebackFormatMixin:
' ~^^\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'
@@ -1786,13 +1960,13 @@ class TracebackFormatMixin:
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'
@@ -1800,7 +1974,7 @@ class TracebackFormatMixin:
)
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'
)
@@ -1818,13 +1992,13 @@ class TracebackFormatMixin:
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'
@@ -1833,7 +2007,7 @@ class TracebackFormatMixin:
)
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'
)
@@ -4287,11 +4461,14 @@ class TestColorizedTraceback(unittest.TestCase):
x = {'a':{'b': None}}
y = x['a']['b']['c']
- def baz(*args):
- return foo(1,2,3,4)
+ def baz2(*args):
+ return (lambda *args: foo(*args))(1,2,3,4)
+
+ def baz1(*args):
+ return baz2(1,2,3,4)
def bar():
- return baz(1,
+ return baz1(1,
2,3
,4)
try:
@@ -4305,10 +4482,10 @@ class TestColorizedTraceback(unittest.TestCase):
boldr = traceback._ANSIColors.BOLD_RED
reset = traceback._ANSIColors.RESET
self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines)
- self.assertIn("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, lines)
- self.assertIn("return " + red + "baz" + reset + boldr + "(1," + reset, lines)
- self.assertIn(boldr + "2,3" + reset, lines)
- self.assertIn(boldr + ",4)" + reset, lines)
+ self.assertIn("return " + red + "(lambda *args: foo(*args))" + reset + boldr + "(1,2,3,4)" + reset, lines)
+ self.assertIn("return (lambda *args: " + red + "foo" + reset + boldr + "(*args)" + reset + ")(1,2,3,4)", lines)
+ self.assertIn("return baz2(1,2,3,4)", lines)
+ self.assertIn("return baz1(1,\n 2,3\n ,4)", lines)
self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines)
def test_colorized_syntax_error(self):
diff --git a/Lib/traceback.py b/Lib/traceback.py
index fccec0c..6ce745f 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -617,13 +617,10 @@ class StackSummary(list):
# 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 = []
@@ -737,6 +734,37 @@ class StackSummary(list):
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()):
+ if isinstance(statement.value.func, ast.Name):
+ 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, **kwargs):
"""Format the stack ready for printing.
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-03-18-21-59.gh-issue-99180.5m0V0q.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-03-18-21-59.gh-issue-99180.5m0V0q.rst
new file mode 100644
index 0000000..576626b
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-03-18-21-59.gh-issue-99180.5m0V0q.rst
@@ -0,0 +1,2 @@
+Elide uninformative traceback indicators in ``return`` and simple
+``assignment`` statements. Patch by Pablo Galindo.