summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/traceback.rst13
-rw-r--r--Doc/whatsnew/3.11.rst1
-rw-r--r--Lib/idlelib/idle_test/test_run.py13
-rw-r--r--Lib/test/test_cmd_line_script.py4
-rw-r--r--Lib/test/test_doctest.py4
-rw-r--r--Lib/test/test_traceback.py166
-rw-r--r--Lib/traceback.py30
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2022-06-24-14-06-20.gh-issue-93883.8jVQQ4.rst1
-rw-r--r--Python/traceback.c18
9 files changed, 113 insertions, 137 deletions
diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst
index 796309c..a8412cc 100644
--- a/Doc/library/traceback.rst
+++ b/Doc/library/traceback.rst
@@ -464,32 +464,27 @@ The output for the example would look similar to this:
*** print_tb:
File "<doctest...>", line 10, in <module>
lumberjack()
- ^^^^^^^^^^^^
*** print_exception:
Traceback (most recent call last):
File "<doctest...>", line 10, in <module>
lumberjack()
- ^^^^^^^^^^^^
File "<doctest...>", line 4, in lumberjack
bright_side_of_death()
- ^^^^^^^^^^^^^^^^^^^^^^
IndexError: tuple index out of range
*** print_exc:
Traceback (most recent call last):
File "<doctest...>", line 10, in <module>
lumberjack()
- ^^^^^^^^^^^^
File "<doctest...>", line 4, in lumberjack
bright_side_of_death()
- ^^^^^^^^^^^^^^^^^^^^^^
IndexError: tuple index out of range
*** format_exc, first and last line:
Traceback (most recent call last):
IndexError: tuple index out of range
*** format_exception:
['Traceback (most recent call last):\n',
- ' File "<doctest default[0]>", line 10, in <module>\n lumberjack()\n ^^^^^^^^^^^^\n',
- ' File "<doctest default[0]>", line 4, in lumberjack\n bright_side_of_death()\n ^^^^^^^^^^^^^^^^^^^^^^\n',
+ ' File "<doctest default[0]>", line 10, in <module>\n lumberjack()\n',
+ ' File "<doctest default[0]>", line 4, in lumberjack\n bright_side_of_death()\n',
' File "<doctest default[0]>", line 7, in bright_side_of_death\n return tuple()[0]\n ~~~~~~~^^^\n',
'IndexError: tuple index out of range\n']
*** extract_tb:
@@ -497,8 +492,8 @@ The output for the example would look similar to this:
<FrameSummary file <doctest...>, line 4 in lumberjack>,
<FrameSummary file <doctest...>, line 7 in bright_side_of_death>]
*** format_tb:
- [' File "<doctest default[0]>", line 10, in <module>\n lumberjack()\n ^^^^^^^^^^^^\n',
- ' File "<doctest default[0]>", line 4, in lumberjack\n bright_side_of_death()\n ^^^^^^^^^^^^^^^^^^^^^^\n',
+ [' File "<doctest default[0]>", line 10, in <module>\n lumberjack()\n',
+ ' File "<doctest default[0]>", line 4, in lumberjack\n bright_side_of_death()\n',
' File "<doctest default[0]>", line 7, in bright_side_of_death\n return tuple()[0]\n ~~~~~~~^^^\n']
*** tb_lineno: 10
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 9599152..f7274a8 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -117,7 +117,6 @@ when dealing with deeply nested dictionary objects and multiple function calls,
Traceback (most recent call last):
File "query.py", line 37, in <module>
magic_arithmetic('foo')
- ^^^^^^^^^^^^^^^^^^^^^^^
File "query.py", line 18, in magic_arithmetic
return add_counts(x) / 25
^^^^^^^^^^^^^
diff --git a/Lib/idlelib/idle_test/test_run.py b/Lib/idlelib/idle_test/test_run.py
index d859ffc..ec4637c 100644
--- a/Lib/idlelib/idle_test/test_run.py
+++ b/Lib/idlelib/idle_test/test_run.py
@@ -3,7 +3,7 @@
from idlelib import run
import io
import sys
-from test.support import captured_output, captured_stderr, has_no_debug_ranges
+from test.support import captured_output, captured_stderr
import unittest
from unittest import mock
import idlelib
@@ -33,14 +33,9 @@ class ExceptionTest(unittest.TestCase):
run.print_exception()
tb = output.getvalue().strip().splitlines()
- if has_no_debug_ranges():
- self.assertEqual(11, len(tb))
- self.assertIn('UnhashableException: ex2', tb[3])
- self.assertIn('UnhashableException: ex1', tb[10])
- else:
- self.assertEqual(13, len(tb))
- self.assertIn('UnhashableException: ex2', tb[4])
- self.assertIn('UnhashableException: ex1', tb[12])
+ self.assertEqual(11, len(tb))
+ self.assertIn('UnhashableException: ex2', tb[3])
+ self.assertIn('UnhashableException: ex1', tb[10])
data = (('1/0', ZeroDivisionError, "division by zero\n"),
('abc', NameError, "name 'abc' is not defined. "
diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
index d783af6..9e98edf 100644
--- a/Lib/test/test_cmd_line_script.py
+++ b/Lib/test/test_cmd_line_script.py
@@ -549,10 +549,10 @@ class CmdLineTest(unittest.TestCase):
script_name = _make_test_script(script_dir, 'script', script)
exitcode, stdout, stderr = assert_python_failure(script_name)
text = stderr.decode('ascii').split('\n')
- self.assertEqual(len(text), 6)
+ self.assertEqual(len(text), 5)
self.assertTrue(text[0].startswith('Traceback'))
self.assertTrue(text[1].startswith(' File '))
- self.assertTrue(text[4].startswith('NameError'))
+ self.assertTrue(text[3].startswith('NameError'))
def test_non_ascii(self):
# Mac OS X denies the creation of a file with an invalid UTF-8 name.
diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py
index 7c79969..65e215f 100644
--- a/Lib/test/test_doctest.py
+++ b/Lib/test/test_doctest.py
@@ -2854,7 +2854,7 @@ except UnicodeEncodeError:
# Skip the test: the filesystem encoding is unable to encode the filename
supports_unicode = False
-if supports_unicode and not support.has_no_debug_ranges():
+if supports_unicode:
def test_unicode(): """
Check doctest with a non-ascii filename:
@@ -2876,10 +2876,8 @@ Check doctest with a non-ascii filename:
Traceback (most recent call last):
File ...
exec(compile(example.source, filename, "single",
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest foo-bär@baz[0]>", line 1, in <module>
raise Exception('clé')
- ^^^^^^^^^^^^^^^^^^^^^^
Exception: clé
TestResults(failed=1, attempted=1)
"""
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 72d67bf..f4161fb 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -252,7 +252,7 @@ class TracebackCases(unittest.TestCase):
self.assertTrue(stdout[2].endswith(err_line),
"Invalid traceback line: {0!r} instead of {1!r}".format(
stdout[2], err_line))
- actual_err_msg = stdout[3 if has_no_debug_ranges() else 4]
+ actual_err_msg = stdout[3]
self.assertTrue(actual_err_msg == err_msg,
"Invalid error message: {0!r} instead of {1!r}".format(
actual_err_msg, err_msg))
@@ -386,18 +386,19 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
callable_line = get_exception.__code__.co_firstlineno + 2
def test_basic_caret(self):
+ # NOTE: In caret tests, "if True:" is used as a way to force indicator
+ # display, since the raising expression spans only part of the line.
def f():
- raise ValueError("basic caret tests")
+ if True: raise ValueError("basic caret tests")
lineno_f = f.__code__.co_firstlineno
expected_f = (
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_f+1}, in f\n'
- ' raise ValueError("basic caret tests")\n'
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
+ ' if True: raise ValueError("basic caret tests")\n'
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
)
result_lines = self.get_exception(f)
self.assertEqual(result_lines, expected_f.splitlines())
@@ -406,17 +407,16 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
# Make sure that even if a line contains multi-byte unicode characters
# the correct carets are printed.
def f_with_unicode():
- raise ValueError("Ĥellö Wörld")
+ if True: raise ValueError("Ĥellö Wörld")
lineno_f = f_with_unicode.__code__.co_firstlineno
expected_f = (
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_f+1}, in f_with_unicode\n'
- ' raise ValueError("Ĥellö Wörld")\n'
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
+ ' if True: raise ValueError("Ĥellö Wörld")\n'
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
)
result_lines = self.get_exception(f_with_unicode)
self.assertEqual(result_lines, expected_f.splitlines())
@@ -431,7 +431,6 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_f+1}, in f_with_type\n'
' def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n'
' ^^^^^^^^^^^^^^^^^^^\n'
@@ -443,7 +442,7 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
# Make sure no carets are printed for expressions spanning multiple
# lines.
def f_with_multiline():
- raise ValueError(
+ if True: raise ValueError(
"error over multiple lines"
)
@@ -452,10 +451,9 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_f+1}, in f_with_multiline\n'
- ' raise ValueError(\n'
- ' ^^^^^^^^^^^^^^^^^'
+ ' if True: raise ValueError(\n'
+ ' ^^^^^^^^^^^^^^^^^'
)
result_lines = self.get_exception(f_with_multiline)
self.assertEqual(result_lines, expected_f.splitlines())
@@ -484,7 +482,6 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
' return compile(code, "?", "exec")\n'
' ^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
@@ -501,9 +498,8 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
# lines.
def f_with_multiline():
return (
- 1 /
- 0 +
- 2
+ 2 + 1 /
+ 0
)
lineno_f = f_with_multiline.__code__.co_firstlineno
@@ -511,10 +507,9 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
- ' 1 /\n'
- ' ^^^'
+ ' 2 + 1 /\n'
+ ' ^^^'
)
result_lines = self.get_exception(f_with_multiline)
self.assertEqual(result_lines, expected_f.splitlines())
@@ -529,7 +524,6 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
' return 10 + divisor / 0 + 30\n'
' ~~~~~~~~^~~\n'
@@ -547,7 +541,6 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
' return 10 + divisor // 0 + 30\n'
' ~~~~~~~~^^~~\n'
@@ -565,7 +558,6 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
" return some_dict['x']['y']['z']\n"
' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
@@ -589,7 +581,6 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{TESTFN}", line {lineno_f}, in <module>\n'
" 1 $ 0 / 1 / 2\n"
' ^^^^^\n'
@@ -597,7 +588,7 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
self.assertEqual(result_lines, expected_error.splitlines())
def test_traceback_very_long_line(self):
- source = "a" * 256
+ source = "if True: " + "a" * 256
bytecode = compile(source, TESTFN, "exec")
with open(TESTFN, "w") as file:
@@ -612,13 +603,54 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{TESTFN}", line {lineno_f}, in <module>\n'
f' {source}\n'
- f' {"^"*len(source)}\n'
+ f' {" "*len("if True: ") + "^"*256}\n'
)
self.assertEqual(result_lines, expected_error.splitlines())
+ def test_secondary_caret_not_elided(self):
+ # Always show a line's indicators if they include the secondary character.
+ def f_with_subscript():
+ some_dict = {'x': {'y': None}}
+ some_dict['x']['y']['z']
+
+ lineno_f = f_with_subscript.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
+ " some_dict['x']['y']['z']\n"
+ ' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
+ )
+ result_lines = self.get_exception(f_with_subscript)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ def test_caret_exception_group(self):
+ # Notably, this covers whether indicators handle margin strings correctly.
+ # (Exception groups use margin strings to display vertical indicators.)
+ # The implementation must account for both "indent" and "margin" offsets.
+
+ def exc():
+ if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])
+
+ expected_error = (
+ f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
+ f' | callable()\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
+ f' | if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
+ f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
+ f' | ExceptionGroup: eg (2 sub-exceptions)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | ValueError: 1\n'
+ f' +---------------- 2 ----------------\n'
+ f' | TypeError: 2\n')
+
+ result_lines = self.get_exception(exc)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
def assertSpecialized(self, func, expected_specialization):
result_lines = self.get_exception(func)
specialization_line = result_lines[-1]
@@ -672,13 +704,11 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_applydescs + 1}, in applydecs\n'
' @dec_error\n'
' ^^^^^^^^^\n'
f' File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
' raise TypeError\n'
- ' ^^^^^^^^^^^^^^^\n'
)
self.assertEqual(result_lines, expected_error.splitlines())
@@ -692,13 +722,11 @@ class TracebackErrorLocationCaretTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
' callable()\n'
- ' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_applydescs_class + 1}, in applydecs_class\n'
' @dec_error\n'
' ^^^^^^^^^\n'
f' File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
' raise TypeError\n'
- ' ^^^^^^^^^^^^^^^\n'
)
self.assertEqual(result_lines, expected_error.splitlines())
@@ -816,12 +844,8 @@ class TracebackFormatTests(unittest.TestCase):
# Make sure that the traceback is properly indented.
tb_lines = python_fmt.splitlines()
banner = tb_lines[0]
- if has_no_debug_ranges():
- self.assertEqual(len(tb_lines), 5)
- location, source_line = tb_lines[-2], tb_lines[-1]
- else:
- self.assertEqual(len(tb_lines), 7)
- location, source_line = tb_lines[-3], tb_lines[-2]
+ self.assertEqual(len(tb_lines), 5)
+ location, source_line = tb_lines[-2], tb_lines[-1]
self.assertTrue(banner.startswith('Traceback'))
self.assertTrue(location.startswith(' File'))
self.assertTrue(source_line.startswith(' raise'))
@@ -885,16 +909,12 @@ class TracebackFormatTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
' f()\n'
- ' ^^^\n'
f' File "{__file__}", line {lineno_f+1}, in f\n'
' f()\n'
- ' ^^^\n'
f' File "{__file__}", line {lineno_f+1}, in f\n'
' f()\n'
- ' ^^^\n'
f' File "{__file__}", line {lineno_f+1}, in f\n'
' f()\n'
- ' ^^^\n'
# XXX: The following line changes depending on whether the tests
# are run through the interactive interpreter or with -m
# It also varies depending on the platform (stack size)
@@ -945,14 +965,12 @@ class TracebackFormatTests(unittest.TestCase):
' [Previous line repeated 7 more times]\n'
f' File "{__file__}", line {lineno_g+3}, in g\n'
' raise ValueError\n'
- ' ^^^^^^^^^^^^^^^^\n'
'ValueError\n'
)
tb_line = (
'Traceback (most recent call last):\n'
f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
' g()\n'
- ' ^^^\n'
)
expected = (tb_line + result_g).splitlines()
actual = stderr_g.getvalue().splitlines()
@@ -977,7 +995,6 @@ class TracebackFormatTests(unittest.TestCase):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n'
' h()\n'
- ' ^^^\n'
f' File "{__file__}", line {lineno_h+2}, in h\n'
' return h(count-1)\n'
' ^^^^^^^^^^\n'
@@ -990,7 +1007,6 @@ class TracebackFormatTests(unittest.TestCase):
' [Previous line repeated 7 more times]\n'
f' File "{__file__}", line {lineno_h+3}, in h\n'
' g()\n'
- ' ^^^\n'
)
expected = (result_h + result_g).splitlines()
actual = stderr_h.getvalue().splitlines()
@@ -1016,14 +1032,12 @@ class TracebackFormatTests(unittest.TestCase):
' ^^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+3}, in g\n'
' raise ValueError\n'
- ' ^^^^^^^^^^^^^^^^\n'
'ValueError\n'
)
tb_line = (
'Traceback (most recent call last):\n'
- f' File "{__file__}", line {lineno_g+81}, 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'
)
expected = (tb_line + result_g).splitlines()
actual = stderr_g.getvalue().splitlines()
@@ -1050,14 +1064,12 @@ class TracebackFormatTests(unittest.TestCase):
' [Previous line repeated 1 more time]\n'
f' File "{__file__}", line {lineno_g+3}, in g\n'
' raise ValueError\n'
- ' ^^^^^^^^^^^^^^^^\n'
'ValueError\n'
)
tb_line = (
'Traceback (most recent call last):\n'
- f' File "{__file__}", line {lineno_g+114}, in _check_recursive_traceback_display\n'
+ f' File "{__file__}", line {lineno_g+108}, in _check_recursive_traceback_display\n'
' g(traceback._RECURSIVE_CUTOFF + 1)\n'
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
)
expected = (tb_line + result_g).splitlines()
actual = stderr_g.getvalue().splitlines()
@@ -1110,16 +1122,10 @@ class TracebackFormatTests(unittest.TestCase):
exception_print(exc_val)
tb = stderr_f.getvalue().strip().splitlines()
- if has_no_debug_ranges():
- self.assertEqual(11, len(tb))
- self.assertEqual(context_message.strip(), tb[5])
- self.assertIn('UnhashableException: ex2', tb[3])
- self.assertIn('UnhashableException: ex1', tb[10])
- else:
- self.assertEqual(13, len(tb))
- self.assertEqual(context_message.strip(), tb[6])
- self.assertIn('UnhashableException: ex2', tb[4])
- self.assertIn('UnhashableException: ex1', tb[12])
+ self.assertEqual(11, len(tb))
+ self.assertEqual(context_message.strip(), tb[5])
+ self.assertIn('UnhashableException: ex2', tb[3])
+ self.assertIn('UnhashableException: ex1', tb[10])
def deep_eg(self):
e = TypeError(1)
@@ -1255,12 +1261,8 @@ class BaseExceptionReportingTests:
except ZeroDivisionError as _:
e = _
lines = self.get_report(e).splitlines()
- if has_no_debug_ranges():
- self.assertEqual(len(lines), 4)
- self.assertTrue(lines[3].startswith('ZeroDivisionError'))
- else:
- self.assertEqual(len(lines), 5)
- self.assertTrue(lines[4].startswith('ZeroDivisionError'))
+ self.assertEqual(len(lines), 4)
+ self.assertTrue(lines[3].startswith('ZeroDivisionError'))
self.assertTrue(lines[0].startswith('Traceback'))
self.assertTrue(lines[1].startswith(' File'))
self.assertIn('ZeroDivisionError from None', lines[2])
@@ -1510,10 +1512,8 @@ class BaseExceptionReportingTests:
f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
f' | exception_or_callable()\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n'
@@ -1535,7 +1535,6 @@ class BaseExceptionReportingTests:
expected = (f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n'
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n'
@@ -1548,10 +1547,8 @@ class BaseExceptionReportingTests:
f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
f' | exception_or_callable()\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
f' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | ValueError: 3\n'
@@ -1577,7 +1574,6 @@ class BaseExceptionReportingTests:
f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n'
f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n'
@@ -1590,7 +1586,6 @@ class BaseExceptionReportingTests:
f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
f' | raise EG("eg2", [ValueError(3), TypeError(4)])\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | ValueError: 3\n'
@@ -1603,10 +1598,8 @@ class BaseExceptionReportingTests:
f'Traceback (most recent call last):\n'
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
f' exception_or_callable()\n'
- f' ^^^^^^^^^^^^^^^^^^^^^^^\n'
f' File "{__file__}", line {exc.__code__.co_firstlineno + 8}, in exc\n'
f' raise ImportError(5)\n'
- f' ^^^^^^^^^^^^^^^^^^^^\n'
f'ImportError: 5\n')
report = self.get_report(exc)
@@ -1629,7 +1622,6 @@ class BaseExceptionReportingTests:
expected = (f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
f' | raise EG("eg", [VE(1), exc, VE(4)])\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: eg (3 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | ValueError: 1\n'
@@ -1637,7 +1629,6 @@ class BaseExceptionReportingTests:
f' | Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
f' | raise EG("nested", [TE(2), TE(3)])\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
f' +-+---------------- 1 ----------------\n'
f' | TypeError: 2\n'
@@ -1653,10 +1644,8 @@ class BaseExceptionReportingTests:
f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
f' | exception_or_callable()\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n'
f' | raise EG("top", [VE(5)])\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: top (1 sub-exception)\n'
f' +-+---------------- 1 ----------------\n'
f' | ValueError: 5\n'
@@ -1814,10 +1803,8 @@ class BaseExceptionReportingTests:
expected = (f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
f' | exception_or_callable()\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
f' | raise ExceptionGroup("nested", excs)\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
f' | >> Multi line note\n'
f' | >> Because I am such\n'
@@ -1829,14 +1816,12 @@ class BaseExceptionReportingTests:
f' | Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
f' | raise ValueError(msg)\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^\n'
f' | ValueError: bad value\n'
f' | the bad value\n'
f' +---------------- 2 ----------------\n'
f' | Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
f' | raise ValueError(msg)\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^\n'
f' | ValueError: terrible value\n'
f' | the terrible value\n'
f' +------------------------------------\n')
@@ -1869,10 +1854,8 @@ class BaseExceptionReportingTests:
expected = (f' + Exception Group Traceback (most recent call last):\n'
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
f' | exception_or_callable()\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 10}, in exc\n'
f' | raise ExceptionGroup("nested", excs)\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
f' | >> Multi line note\n'
f' | >> Because I am such\n'
@@ -1885,7 +1868,6 @@ class BaseExceptionReportingTests:
f' | Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
f' | raise ValueError(msg)\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^\n'
f' | ValueError: bad value\n'
f' | the bad value\n'
f' | Goodbye bad value\n'
@@ -1893,7 +1875,6 @@ class BaseExceptionReportingTests:
f' | Traceback (most recent call last):\n'
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
f' | raise ValueError(msg)\n'
- f' | ^^^^^^^^^^^^^^^^^^^^^\n'
f' | ValueError: terrible value\n'
f' | the terrible value\n'
f' | Goodbye terrible value\n'
@@ -2669,19 +2650,16 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
f' + Exception Group Traceback (most recent call last):',
f' | File "{__file__}", line {lno_g+23}, in _get_exception_group',
f' | raise ExceptionGroup("eg2", [exc3, exc4])',
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
f' | ExceptionGroup: eg2 (2 sub-exceptions)',
f' +-+---------------- 1 ----------------',
f' | Exception Group Traceback (most recent call last):',
f' | File "{__file__}", line {lno_g+16}, in _get_exception_group',
f' | raise ExceptionGroup("eg1", [exc1, exc2])',
- f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
f' | ExceptionGroup: eg1 (2 sub-exceptions)',
f' +-+---------------- 1 ----------------',
f' | Traceback (most recent call last):',
f' | File "{__file__}", line {lno_g+9}, in _get_exception_group',
f' | f()',
- f' | ^^^',
f' | File "{__file__}", line {lno_f+1}, in f',
f' | 1/0',
f' | ~^~',
@@ -2690,20 +2668,16 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
f' | Traceback (most recent call last):',
f' | File "{__file__}", line {lno_g+13}, in _get_exception_group',
f' | g(42)',
- f' | ^^^^^',
f' | File "{__file__}", line {lno_g+1}, in g',
f' | raise ValueError(v)',
- f' | ^^^^^^^^^^^^^^^^^^^',
f' | ValueError: 42',
f' +------------------------------------',
f' +---------------- 2 ----------------',
f' | Traceback (most recent call last):',
f' | File "{__file__}", line {lno_g+20}, in _get_exception_group',
f' | g(24)',
- f' | ^^^^^',
f' | File "{__file__}", line {lno_g+1}, in g',
f' | raise ValueError(v)',
- f' | ^^^^^^^^^^^^^^^^^^^',
f' | ValueError: 24',
f' +------------------------------------',
f'']
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 3afe49d..55f8080 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -465,7 +465,8 @@ class StackSummary(list):
row.append(' File "{}", line {}, in {}\n'.format(
frame_summary.filename, frame_summary.lineno, frame_summary.name))
if frame_summary.line:
- row.append(' {}\n'.format(frame_summary.line.strip()))
+ stripped_line = frame_summary.line.strip()
+ row.append(' {}\n'.format(stripped_line))
orig_line_len = len(frame_summary._original_line)
frame_line_len = len(frame_summary.line.lstrip())
@@ -486,19 +487,22 @@ class StackSummary(list):
frame_summary._original_line[colno - 1:end_colno - 1]
)
else:
- end_colno = stripped_characters + len(frame_summary.line.strip())
-
- row.append(' ')
- row.append(' ' * (colno - stripped_characters))
-
- if anchors:
- row.append(anchors.primary_char * (anchors.left_end_offset))
- row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
- row.append(anchors.primary_char * (end_colno - colno - anchors.right_start_offset))
- else:
- row.append('^' * (end_colno - colno))
+ end_colno = stripped_characters + len(stripped_line)
+
+ # show indicators if primary char doesn't span the frame line
+ if end_colno - colno < len(stripped_line) or (
+ anchors and anchors.right_start_offset - anchors.left_end_offset > 0):
+ row.append(' ')
+ row.append(' ' * (colno - stripped_characters))
+
+ if anchors:
+ row.append(anchors.primary_char * (anchors.left_end_offset))
+ row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
+ row.append(anchors.primary_char * (end_colno - colno - anchors.right_start_offset))
+ else:
+ row.append('^' * (end_colno - colno))
- row.append('\n')
+ row.append('\n')
if frame_summary.locals:
for name, value in sorted(frame_summary.locals.items()):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-06-24-14-06-20.gh-issue-93883.8jVQQ4.rst b/Misc/NEWS.d/next/Core and Builtins/2022-06-24-14-06-20.gh-issue-93883.8jVQQ4.rst
new file mode 100644
index 0000000..5334557
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-06-24-14-06-20.gh-issue-93883.8jVQQ4.rst
@@ -0,0 +1 @@
+Revise the display strategy of traceback enhanced error locations. The indicators are only shown when the location doesn't span the whole line.
diff --git a/Python/traceback.c b/Python/traceback.c
index 439689b..de658b9 100644
--- a/Python/traceback.c
+++ b/Python/traceback.c
@@ -592,7 +592,6 @@ _Py_DisplaySourceLine(PyObject *f, PyObject *filename, int lineno, int indent,
* Traceback (most recent call last):
* File "/home/isidentical/cpython/cpython/t.py", line 10, in <module>
* add_values(1, 2, 'x', 3, 4)
- * ^^^^^^^^^^^^^^^^^^^^^^^^^^^
* File "/home/isidentical/cpython/cpython/t.py", line 2, in add_values
* return a + b + c + d + e
* ~~~~~~^~~
@@ -736,7 +735,7 @@ print_error_location_carets(PyObject *f, int offset, Py_ssize_t start_offset, Py
int special_chars = (left_end_offset != -1 || right_start_offset != -1);
const char *str;
while (++offset <= end_offset) {
- if (offset <= start_offset || offset > end_offset) {
+ if (offset <= start_offset) {
str = " ";
} else if (special_chars && left_end_offset < offset && offset <= right_start_offset) {
str = secondary;
@@ -792,6 +791,7 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
int code_offset = tb->tb_lasti;
PyCodeObject* code = frame->f_frame->f_code;
+ const Py_ssize_t source_line_len = PyUnicode_GET_LENGTH(source_line);
int start_line;
int end_line;
@@ -813,7 +813,7 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
//
// ERROR LINE ERROR LINE ERROR LINE ERROR LINE ERROR LINE ERROR LINE ERROR LINE
// ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~
- // | |-> left_end_offset | |-> left_offset
+ // | |-> left_end_offset | |-> end_offset
// |-> start_offset |-> right_start_offset
//
// In general we will only have (start_offset, end_offset) but we can gather more information
@@ -822,6 +822,9 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
// the different ranges (primary_error_char and secondary_error_char). If we cannot obtain the
// AST information or we cannot identify special ranges within it, then left_end_offset and
// right_end_offset will be set to -1.
+ //
+ // To keep the column indicators pertinent, they are not shown when the primary character
+ // spans the whole line.
// Convert the utf-8 byte offset to the actual character offset so we print the right number of carets.
assert(source_line);
@@ -859,7 +862,7 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
goto done;
}
- Py_ssize_t i = PyUnicode_GET_LENGTH(source_line);
+ Py_ssize_t i = source_line_len;
while (--i >= 0) {
if (!IS_WHITESPACE(source_line_str[i])) {
break;
@@ -869,6 +872,13 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
end_offset = i + 1;
}
+ // Elide indicators if primary char spans the frame line
+ Py_ssize_t stripped_line_len = source_line_len - truncation - _TRACEBACK_SOURCE_LINE_INDENT;
+ bool has_secondary_ranges = (left_end_offset != -1 || right_start_offset != -1);
+ if (end_offset - start_offset == stripped_line_len && !has_secondary_ranges) {
+ goto done;
+ }
+
if (_Py_WriteIndentedMargin(margin_indent, margin, f) < 0) {
err = -1;
goto done;