mirror of
https://github.com/python/cpython.git
synced 2025-07-13 14:25:18 +00:00
gh-106922: Support multi-line error locations in traceback (attempt 2) (#112097)
This commit is contained in:
parent
5c5022b862
commit
939fc6d6ea
9 changed files with 706 additions and 124 deletions
|
@ -523,27 +523,32 @@ The output for the example would look similar to this:
|
||||||
*** print_tb:
|
*** print_tb:
|
||||||
File "<doctest...>", line 10, in <module>
|
File "<doctest...>", line 10, in <module>
|
||||||
lumberjack()
|
lumberjack()
|
||||||
|
~~~~~~~~~~^^
|
||||||
*** print_exception:
|
*** print_exception:
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<doctest...>", line 10, in <module>
|
File "<doctest...>", line 10, in <module>
|
||||||
lumberjack()
|
lumberjack()
|
||||||
|
~~~~~~~~~~^^
|
||||||
File "<doctest...>", line 4, in lumberjack
|
File "<doctest...>", line 4, in lumberjack
|
||||||
bright_side_of_life()
|
bright_side_of_life()
|
||||||
|
~~~~~~~~~~~~~~~~~~~^^
|
||||||
IndexError: tuple index out of range
|
IndexError: tuple index out of range
|
||||||
*** print_exc:
|
*** print_exc:
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<doctest...>", line 10, in <module>
|
File "<doctest...>", line 10, in <module>
|
||||||
lumberjack()
|
lumberjack()
|
||||||
|
~~~~~~~~~~^^
|
||||||
File "<doctest...>", line 4, in lumberjack
|
File "<doctest...>", line 4, in lumberjack
|
||||||
bright_side_of_life()
|
bright_side_of_life()
|
||||||
|
~~~~~~~~~~~~~~~~~~~^^
|
||||||
IndexError: tuple index out of range
|
IndexError: tuple index out of range
|
||||||
*** format_exc, first and last line:
|
*** format_exc, first and last line:
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
IndexError: tuple index out of range
|
IndexError: tuple index out of range
|
||||||
*** format_exception:
|
*** format_exception:
|
||||||
['Traceback (most recent call last):\n',
|
['Traceback (most recent call last):\n',
|
||||||
' File "<doctest default[0]>", line 10, in <module>\n lumberjack()\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_life()\n',
|
' File "<doctest default[0]>", line 4, in lumberjack\n bright_side_of_life()\n ~~~~~~~~~~~~~~~~~~~^^\n',
|
||||||
' File "<doctest default[0]>", line 7, in bright_side_of_life\n return tuple()[0]\n ~~~~~~~^^^\n',
|
' File "<doctest default[0]>", line 7, in bright_side_of_life\n return tuple()[0]\n ~~~~~~~^^^\n',
|
||||||
'IndexError: tuple index out of range\n']
|
'IndexError: tuple index out of range\n']
|
||||||
*** extract_tb:
|
*** extract_tb:
|
||||||
|
@ -551,8 +556,8 @@ The output for the example would look similar to this:
|
||||||
<FrameSummary file <doctest...>, line 4 in lumberjack>,
|
<FrameSummary file <doctest...>, line 4 in lumberjack>,
|
||||||
<FrameSummary file <doctest...>, line 7 in bright_side_of_life>]
|
<FrameSummary file <doctest...>, line 7 in bright_side_of_life>]
|
||||||
*** format_tb:
|
*** format_tb:
|
||||||
[' File "<doctest default[0]>", line 10, in <module>\n lumberjack()\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_life()\n',
|
' File "<doctest default[0]>", line 4, in lumberjack\n bright_side_of_life()\n ~~~~~~~~~~~~~~~~~~~^^\n',
|
||||||
' File "<doctest default[0]>", line 7, in bright_side_of_life\n return tuple()[0]\n ~~~~~~~^^^\n']
|
' File "<doctest default[0]>", line 7, in bright_side_of_life\n return tuple()[0]\n ~~~~~~~^^^\n']
|
||||||
*** tb_lineno: 10
|
*** tb_lineno: 10
|
||||||
|
|
||||||
|
|
|
@ -2922,6 +2922,9 @@ Check doctest with a non-ascii filename:
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File ...
|
File ...
|
||||||
exec(compile(example.source, filename, "single",
|
exec(compile(example.source, filename, "single",
|
||||||
|
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
compileflags, True), test.globs)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
File "<doctest foo-bär@baz[0]>", line 1, in <module>
|
File "<doctest foo-bär@baz[0]>", line 1, in <module>
|
||||||
raise Exception('clé')
|
raise Exception('clé')
|
||||||
Exception: clé
|
Exception: clé
|
||||||
|
|
|
@ -2080,6 +2080,7 @@ class AssertionErrorTests(unittest.TestCase):
|
||||||
""",
|
""",
|
||||||
[
|
[
|
||||||
' 1 < 2 and',
|
' 1 < 2 and',
|
||||||
|
' 3 > 4',
|
||||||
'AssertionError',
|
'AssertionError',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -2087,7 +2088,7 @@ class AssertionErrorTests(unittest.TestCase):
|
||||||
for source, expected in cases:
|
for source, expected in cases:
|
||||||
with self.subTest(source):
|
with self.subTest(source):
|
||||||
result = self.write_source(source)
|
result = self.write_source(source)
|
||||||
self.assertEqual(result[-2:], expected)
|
self.assertEqual(result[-len(expected):], expected)
|
||||||
|
|
||||||
|
|
||||||
class SyntaxErrorTests(unittest.TestCase):
|
class SyntaxErrorTests(unittest.TestCase):
|
||||||
|
|
|
@ -161,10 +161,11 @@ class TestInteractiveInterpreter(unittest.TestCase):
|
||||||
output = kill_python(p)
|
output = kill_python(p)
|
||||||
self.assertEqual(p.returncode, 0)
|
self.assertEqual(p.returncode, 0)
|
||||||
|
|
||||||
traceback_lines = output.splitlines()[-7:-1]
|
traceback_lines = output.splitlines()[-8:-1]
|
||||||
expected_lines = [
|
expected_lines = [
|
||||||
' File "<stdin>", line 1, in <module>',
|
' File "<stdin>", line 1, in <module>',
|
||||||
' foo(0)',
|
' foo(0)',
|
||||||
|
' ~~~^^^',
|
||||||
' File "<stdin>", line 2, in foo',
|
' File "<stdin>", line 2, in foo',
|
||||||
' 1 / x',
|
' 1 / x',
|
||||||
' ~~^~~',
|
' ~~^~~',
|
||||||
|
|
|
@ -1115,8 +1115,10 @@ class SysModuleTest(unittest.TestCase):
|
||||||
b'Traceback (most recent call last):',
|
b'Traceback (most recent call last):',
|
||||||
b' File "<string>", line 8, in <module>',
|
b' File "<string>", line 8, in <module>',
|
||||||
b' f2()',
|
b' f2()',
|
||||||
|
b' ~~^^',
|
||||||
b' File "<string>", line 6, in f2',
|
b' File "<string>", line 6, in f2',
|
||||||
b' f1()',
|
b' f1()',
|
||||||
|
b' ~~^^',
|
||||||
b' File "<string>", line 4, in f1',
|
b' File "<string>", line 4, in f1',
|
||||||
b' 1 / 0',
|
b' 1 / 0',
|
||||||
b' ~~^~~',
|
b' ~~^~~',
|
||||||
|
@ -1124,8 +1126,8 @@ class SysModuleTest(unittest.TestCase):
|
||||||
]
|
]
|
||||||
check(10, traceback)
|
check(10, traceback)
|
||||||
check(3, traceback)
|
check(3, traceback)
|
||||||
check(2, traceback[:1] + traceback[3:])
|
check(2, traceback[:1] + traceback[4:])
|
||||||
check(1, traceback[:1] + traceback[5:])
|
check(1, traceback[:1] + traceback[7:])
|
||||||
check(0, [traceback[-1]])
|
check(0, [traceback[-1]])
|
||||||
check(-1, [traceback[-1]])
|
check(-1, [traceback[-1]])
|
||||||
check(1<<1000, traceback)
|
check(1<<1000, traceback)
|
||||||
|
|
|
@ -578,6 +578,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||||
' if True: raise ValueError("basic caret tests")\n'
|
' if True: raise ValueError("basic caret tests")\n'
|
||||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
||||||
|
@ -596,6 +597,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f_with_unicode\n'
|
f' File "{__file__}", line {lineno_f+1}, in f_with_unicode\n'
|
||||||
' if True: raise ValueError("Ĥellö Wörld")\n'
|
' if True: raise ValueError("Ĥellö Wörld")\n'
|
||||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
||||||
|
@ -613,6 +615,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f_with_type\n'
|
f' File "{__file__}", line {lineno_f+1}, in f_with_type\n'
|
||||||
' def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n'
|
' def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n'
|
||||||
' ^^^^^^^^^^^^^^^^^^^\n'
|
' ^^^^^^^^^^^^^^^^^^^\n'
|
||||||
|
@ -633,9 +636,14 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f_with_multiline\n'
|
f' File "{__file__}", line {lineno_f+1}, in f_with_multiline\n'
|
||||||
' if True: raise ValueError(\n'
|
' if True: raise ValueError(\n'
|
||||||
' ^^^^^^^^^^^^^^^^^'
|
' ^^^^^^^^^^^^^^^^^\n'
|
||||||
|
' "error over multiple lines"\n'
|
||||||
|
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
||||||
|
' )\n'
|
||||||
|
' ^'
|
||||||
)
|
)
|
||||||
result_lines = self.get_exception(f_with_multiline)
|
result_lines = self.get_exception(f_with_multiline)
|
||||||
self.assertEqual(result_lines, expected_f.splitlines())
|
self.assertEqual(result_lines, expected_f.splitlines())
|
||||||
|
@ -664,9 +672,10 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
|
||||||
' return compile(code, "?", "exec")\n'
|
' return compile(code, "?", "exec")\n'
|
||||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
' ~~~~~~~^^^^^^^^^^^^^^^^^^^\n'
|
||||||
' File "?", line 7\n'
|
' File "?", line 7\n'
|
||||||
' foo(a, z\n'
|
' foo(a, z\n'
|
||||||
' ^'
|
' ^'
|
||||||
|
@ -689,9 +698,12 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
|
||||||
' 2 + 1 /\n'
|
' 2 + 1 /\n'
|
||||||
' ^^^'
|
' ~~^\n'
|
||||||
|
' 0\n'
|
||||||
|
' ~'
|
||||||
)
|
)
|
||||||
result_lines = self.get_exception(f_with_multiline)
|
result_lines = self.get_exception(f_with_multiline)
|
||||||
self.assertEqual(result_lines, expected_f.splitlines())
|
self.assertEqual(result_lines, expected_f.splitlines())
|
||||||
|
@ -706,6 +718,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
|
||||||
' return 10 + divisor / 0 + 30\n'
|
' return 10 + divisor / 0 + 30\n'
|
||||||
' ~~~~~~~~^~~\n'
|
' ~~~~~~~~^~~\n'
|
||||||
|
@ -723,6 +736,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
|
||||||
' return 10 + áóí / 0 + 30\n'
|
' return 10 + áóí / 0 + 30\n'
|
||||||
' ~~~~^~~\n'
|
' ~~~~^~~\n'
|
||||||
|
@ -740,6 +754,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
|
||||||
' return 10 + divisor // 0 + 30\n'
|
' return 10 + divisor // 0 + 30\n'
|
||||||
' ~~~~~~~~^^~~\n'
|
' ~~~~~~~~^^~~\n'
|
||||||
|
@ -751,16 +766,102 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
def f_with_binary_operator():
|
def f_with_binary_operator():
|
||||||
a = 1
|
a = 1
|
||||||
b = ""
|
b = ""
|
||||||
return ( a ) + b
|
return ( a ) +b
|
||||||
|
|
||||||
lineno_f = f_with_binary_operator.__code__.co_firstlineno
|
lineno_f = f_with_binary_operator.__code__.co_firstlineno
|
||||||
expected_error = (
|
expected_error = (
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
|
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
|
||||||
' return ( a ) + b\n'
|
' return ( a ) +b\n'
|
||||||
' ~~~~~~~~~~^~~\n'
|
' ~~~~~~~~~~^~\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f_with_binary_operator)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_caret_for_binary_operators_multiline(self):
|
||||||
|
def f_with_binary_operator():
|
||||||
|
b = 1
|
||||||
|
c = ""
|
||||||
|
a = b \
|
||||||
|
+\
|
||||||
|
c # test
|
||||||
|
return a
|
||||||
|
|
||||||
|
lineno_f = f_with_binary_operator.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'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+3}, in f_with_binary_operator\n'
|
||||||
|
' a = b \\\n'
|
||||||
|
' ~~~~~~\n'
|
||||||
|
' +\\\n'
|
||||||
|
' ^~\n'
|
||||||
|
' c # test\n'
|
||||||
|
' ~\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f_with_binary_operator)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_caret_for_binary_operators_multiline_two_char(self):
|
||||||
|
def f_with_binary_operator():
|
||||||
|
b = 1
|
||||||
|
c = ""
|
||||||
|
a = (
|
||||||
|
(b # test +
|
||||||
|
) \
|
||||||
|
# +
|
||||||
|
<< (c # test
|
||||||
|
\
|
||||||
|
) # test
|
||||||
|
)
|
||||||
|
return a
|
||||||
|
|
||||||
|
lineno_f = f_with_binary_operator.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'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+4}, in f_with_binary_operator\n'
|
||||||
|
' (b # test +\n'
|
||||||
|
' ~~~~~~~~~~~~\n'
|
||||||
|
' ) \\\n'
|
||||||
|
' ~~~~\n'
|
||||||
|
' # +\n'
|
||||||
|
' ~~~\n'
|
||||||
|
' << (c # test\n'
|
||||||
|
' ^^~~~~~~~~~~~\n'
|
||||||
|
' \\\n'
|
||||||
|
' ~\n'
|
||||||
|
' ) # test\n'
|
||||||
|
' ~\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f_with_binary_operator)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_caret_for_binary_operators_multiline_with_unicode(self):
|
||||||
|
def f_with_binary_operator():
|
||||||
|
b = 1
|
||||||
|
a = ("ááá" +
|
||||||
|
"áá") + b
|
||||||
|
return a
|
||||||
|
|
||||||
|
lineno_f = f_with_binary_operator.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'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'
|
||||||
|
' a = ("ááá" +\n'
|
||||||
|
' ~~~~~~~~\n'
|
||||||
|
' "áá") + b\n'
|
||||||
|
' ~~~~~~^~~\n'
|
||||||
)
|
)
|
||||||
result_lines = self.get_exception(f_with_binary_operator)
|
result_lines = self.get_exception(f_with_binary_operator)
|
||||||
self.assertEqual(result_lines, expected_error.splitlines())
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
@ -775,6 +876,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
|
||||||
" return some_dict['x']['y']['z']\n"
|
" return some_dict['x']['y']['z']\n"
|
||||||
' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
|
' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
|
||||||
|
@ -792,6 +894,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
|
||||||
" return some_dict['ó']['á']['í']['beta']\n"
|
" return some_dict['ó']['á']['í']['beta']\n"
|
||||||
' ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^\n'
|
' ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^\n'
|
||||||
|
@ -810,6 +913,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
|
f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
|
||||||
' return b [ a ] + c\n'
|
' return b [ a ] + c\n'
|
||||||
' ~~~~~~^^^^^^^^^\n'
|
' ~~~~~~^^^^^^^^^\n'
|
||||||
|
@ -817,6 +921,226 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
result_lines = self.get_exception(f_with_binary_operator)
|
result_lines = self.get_exception(f_with_binary_operator)
|
||||||
self.assertEqual(result_lines, expected_error.splitlines())
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_caret_for_subscript_multiline(self):
|
||||||
|
def f_with_subscript():
|
||||||
|
bbbbb = {}
|
||||||
|
ccc = 1
|
||||||
|
ddd = 2
|
||||||
|
b = bbbbb \
|
||||||
|
[ ccc # test
|
||||||
|
|
||||||
|
+ ddd \
|
||||||
|
|
||||||
|
] # test
|
||||||
|
return b
|
||||||
|
|
||||||
|
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'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
|
f' File "{__file__}", line {lineno_f+4}, in f_with_subscript\n'
|
||||||
|
' b = bbbbb \\\n'
|
||||||
|
' ~~~~~~~\n'
|
||||||
|
' [ ccc # test\n'
|
||||||
|
' ^^^^^^^^^^^^^\n'
|
||||||
|
' \n'
|
||||||
|
' \n'
|
||||||
|
' + ddd \\\n'
|
||||||
|
' ^^^^^^^^\n'
|
||||||
|
' \n'
|
||||||
|
' \n'
|
||||||
|
' ] # test\n'
|
||||||
|
' ^\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f_with_subscript)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_caret_for_call(self):
|
||||||
|
def f_with_call():
|
||||||
|
def f1(a):
|
||||||
|
def f2(b):
|
||||||
|
raise RuntimeError("fail")
|
||||||
|
return f2
|
||||||
|
return f1("x")("y")
|
||||||
|
|
||||||
|
lineno_f = f_with_call.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'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+5}, in f_with_call\n'
|
||||||
|
' return f1("x")("y")\n'
|
||||||
|
' ~~~~~~~^^^^^\n'
|
||||||
|
f' File "{__file__}", line {lineno_f+3}, in f2\n'
|
||||||
|
' raise RuntimeError("fail")\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f_with_call)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_caret_for_call_unicode(self):
|
||||||
|
def f_with_call():
|
||||||
|
def f1(a):
|
||||||
|
def f2(b):
|
||||||
|
raise RuntimeError("fail")
|
||||||
|
return f2
|
||||||
|
return f1("ó")("á")
|
||||||
|
|
||||||
|
lineno_f = f_with_call.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'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+5}, in f_with_call\n'
|
||||||
|
' return f1("ó")("á")\n'
|
||||||
|
' ~~~~~~~^^^^^\n'
|
||||||
|
f' File "{__file__}", line {lineno_f+3}, in f2\n'
|
||||||
|
' raise RuntimeError("fail")\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f_with_call)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_caret_for_call_with_spaces_and_parenthesis(self):
|
||||||
|
def f_with_binary_operator():
|
||||||
|
def f(a):
|
||||||
|
raise RuntimeError("fail")
|
||||||
|
return f ( "x" ) + 2
|
||||||
|
|
||||||
|
lineno_f = f_with_binary_operator.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'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+3}, in f_with_binary_operator\n'
|
||||||
|
' return f ( "x" ) + 2\n'
|
||||||
|
' ~~~~~~^^^^^^^^^^^\n'
|
||||||
|
f' File "{__file__}", line {lineno_f+2}, in f\n'
|
||||||
|
' raise RuntimeError("fail")\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f_with_binary_operator)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_caret_for_call_multiline(self):
|
||||||
|
def f_with_call():
|
||||||
|
class C:
|
||||||
|
def y(self, a):
|
||||||
|
def f(b):
|
||||||
|
raise RuntimeError("fail")
|
||||||
|
return f
|
||||||
|
def g(x):
|
||||||
|
return C()
|
||||||
|
a = (g(1).y)(
|
||||||
|
2
|
||||||
|
)(3)(4)
|
||||||
|
return a
|
||||||
|
|
||||||
|
lineno_f = f_with_call.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'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+8}, in f_with_call\n'
|
||||||
|
' a = (g(1).y)(\n'
|
||||||
|
' ~~~~~~~~~\n'
|
||||||
|
' 2\n'
|
||||||
|
' ~\n'
|
||||||
|
' )(3)(4)\n'
|
||||||
|
' ~^^^\n'
|
||||||
|
f' File "{__file__}", line {lineno_f+4}, in f\n'
|
||||||
|
' raise RuntimeError("fail")\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f_with_call)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_many_lines(self):
|
||||||
|
def f():
|
||||||
|
x = 1
|
||||||
|
if True: x += (
|
||||||
|
"a" +
|
||||||
|
"a"
|
||||||
|
) # test
|
||||||
|
|
||||||
|
lineno_f = f.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'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\n'
|
||||||
|
' if True: x += (\n'
|
||||||
|
' ^^^^^^\n'
|
||||||
|
' ...<2 lines>...\n'
|
||||||
|
' ) # test\n'
|
||||||
|
' ^\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_many_lines_no_caret(self):
|
||||||
|
def f():
|
||||||
|
x = 1
|
||||||
|
x += (
|
||||||
|
"a" +
|
||||||
|
"a"
|
||||||
|
)
|
||||||
|
|
||||||
|
lineno_f = f.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'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\n'
|
||||||
|
' x += (\n'
|
||||||
|
' ...<2 lines>...\n'
|
||||||
|
' )\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
|
def test_many_lines_binary_op(self):
|
||||||
|
def f_with_binary_operator():
|
||||||
|
b = 1
|
||||||
|
c = "a"
|
||||||
|
a = (
|
||||||
|
b +
|
||||||
|
b
|
||||||
|
) + (
|
||||||
|
c +
|
||||||
|
c +
|
||||||
|
c
|
||||||
|
)
|
||||||
|
return a
|
||||||
|
|
||||||
|
lineno_f = f_with_binary_operator.__code__.co_firstlineno
|
||||||
|
expected_error = (
|
||||||
|
'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+3}, in f_with_binary_operator\n'
|
||||||
|
' a = (\n'
|
||||||
|
' ~\n'
|
||||||
|
' b +\n'
|
||||||
|
' ~~~\n'
|
||||||
|
' b\n'
|
||||||
|
' ~\n'
|
||||||
|
' ) + (\n'
|
||||||
|
' ~~^~~\n'
|
||||||
|
' c +\n'
|
||||||
|
' ~~~\n'
|
||||||
|
' ...<2 lines>...\n'
|
||||||
|
' )\n'
|
||||||
|
' ~\n'
|
||||||
|
)
|
||||||
|
result_lines = self.get_exception(f_with_binary_operator)
|
||||||
|
self.assertEqual(result_lines, expected_error.splitlines())
|
||||||
|
|
||||||
def test_traceback_specialization_with_syntax_error(self):
|
def test_traceback_specialization_with_syntax_error(self):
|
||||||
bytecode = compile("1 / 0 / 1 / 2\n", TESTFN, "exec")
|
bytecode = compile("1 / 0 / 1 / 2\n", TESTFN, "exec")
|
||||||
|
|
||||||
|
@ -833,6 +1157,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{TESTFN}", line {lineno_f}, in <module>\n'
|
f' File "{TESTFN}", line {lineno_f}, in <module>\n'
|
||||||
" 1 $ 0 / 1 / 2\n"
|
" 1 $ 0 / 1 / 2\n"
|
||||||
' ^^^^^\n'
|
' ^^^^^\n'
|
||||||
|
@ -855,6 +1180,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{TESTFN}", line {lineno_f}, in <module>\n'
|
f' File "{TESTFN}", line {lineno_f}, in <module>\n'
|
||||||
f' {source}\n'
|
f' {source}\n'
|
||||||
f' {" "*len("if True: ") + "^"*256}\n'
|
f' {" "*len("if True: ") + "^"*256}\n'
|
||||||
|
@ -872,6 +1198,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
|
f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
|
||||||
" some_dict['x']['y']['z']\n"
|
" some_dict['x']['y']['z']\n"
|
||||||
' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
|
' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
|
||||||
|
@ -891,6 +1218,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
f' + Exception Group Traceback (most recent call last):\n'
|
f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | callable()\n'
|
f' | callable()\n'
|
||||||
|
f' | ~~~~~~~~^^\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\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' | if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
|
||||||
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
||||||
|
@ -956,6 +1284,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_applydescs + 1}, in applydecs\n'
|
f' File "{__file__}", line {lineno_applydescs + 1}, in applydecs\n'
|
||||||
' @dec_error\n'
|
' @dec_error\n'
|
||||||
' ^^^^^^^^^\n'
|
' ^^^^^^^^^\n'
|
||||||
|
@ -974,6 +1303,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
' callable()\n'
|
' callable()\n'
|
||||||
|
' ~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {lineno_applydescs_class + 1}, in applydecs_class\n'
|
f' File "{__file__}", line {lineno_applydescs_class + 1}, in applydecs_class\n'
|
||||||
' @dec_error\n'
|
' @dec_error\n'
|
||||||
' ^^^^^^^^^\n'
|
' ^^^^^^^^^\n'
|
||||||
|
@ -992,6 +1322,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
"Traceback (most recent call last):",
|
"Traceback (most recent call last):",
|
||||||
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
||||||
" callable()",
|
" callable()",
|
||||||
|
" ~~~~~~~~^^",
|
||||||
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
|
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
|
||||||
" .method",
|
" .method",
|
||||||
" ^^^^^^",
|
" ^^^^^^",
|
||||||
|
@ -1008,6 +1339,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
"Traceback (most recent call last):",
|
"Traceback (most recent call last):",
|
||||||
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
||||||
" callable()",
|
" callable()",
|
||||||
|
" ~~~~~~~~^^",
|
||||||
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
|
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
|
||||||
" method",
|
" method",
|
||||||
]
|
]
|
||||||
|
@ -1023,6 +1355,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
"Traceback (most recent call last):",
|
"Traceback (most recent call last):",
|
||||||
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
||||||
" callable()",
|
" callable()",
|
||||||
|
" ~~~~~~~~^^",
|
||||||
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
|
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
|
||||||
" . method",
|
" . method",
|
||||||
" ^^^^^^",
|
" ^^^^^^",
|
||||||
|
@ -1038,6 +1371,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
"Traceback (most recent call last):",
|
"Traceback (most recent call last):",
|
||||||
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
||||||
" callable()",
|
" callable()",
|
||||||
|
" ~~~~~~~~^^",
|
||||||
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
|
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
|
||||||
" width",
|
" width",
|
||||||
]
|
]
|
||||||
|
@ -1054,6 +1388,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
"Traceback (most recent call last):",
|
"Traceback (most recent call last):",
|
||||||
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
||||||
" callable()",
|
" callable()",
|
||||||
|
" ~~~~~~~~^^",
|
||||||
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
|
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
|
||||||
" raise ValueError(width)",
|
" raise ValueError(width)",
|
||||||
]
|
]
|
||||||
|
@ -1072,9 +1407,12 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
"Traceback (most recent call last):",
|
"Traceback (most recent call last):",
|
||||||
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
||||||
" callable()",
|
" callable()",
|
||||||
|
" ~~~~~~~~^^",
|
||||||
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 4}, in f",
|
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 4}, in f",
|
||||||
f" print(1, www(",
|
f" print(1, www(",
|
||||||
f" ^^^^^^^",
|
f" ~~~~~~^",
|
||||||
|
f" th))",
|
||||||
|
f" ^^^^^",
|
||||||
]
|
]
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
@ -1089,6 +1427,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
f"Traceback (most recent call last):",
|
f"Traceback (most recent call last):",
|
||||||
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
||||||
f" callable()",
|
f" callable()",
|
||||||
|
f" ~~~~~~~~^^",
|
||||||
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 3}, in f",
|
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 3}, in f",
|
||||||
f" return 说明说明 / şçöğıĤellö",
|
f" return 说明说明 / şçöğıĤellö",
|
||||||
f" ~~~~~~~~~^~~~~~~~~~~~",
|
f" ~~~~~~~~~^~~~~~~~~~~~",
|
||||||
|
@ -1105,6 +1444,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
f"Traceback (most recent call last):",
|
f"Traceback (most recent call last):",
|
||||||
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
||||||
f" callable()",
|
f" callable()",
|
||||||
|
f" ~~~~~~~~^^",
|
||||||
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
|
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
|
||||||
f' return "✨🐍" + func_说明说明("📗🚛",',
|
f' return "✨🐍" + func_说明说明("📗🚛",',
|
||||||
f" ^^^^^^^^^^^^^",
|
f" ^^^^^^^^^^^^^",
|
||||||
|
@ -1127,6 +1467,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
f"Traceback (most recent call last):",
|
f"Traceback (most recent call last):",
|
||||||
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
f" File \"{__file__}\", line {self.callable_line}, in get_exception",
|
||||||
f" callable()",
|
f" callable()",
|
||||||
|
f" ~~~~~~~~^^",
|
||||||
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 8}, in f",
|
f" File \"{__file__}\", line {f.__code__.co_firstlineno + 8}, in f",
|
||||||
f' return my_dct["✨🚛✨"]["说明"]["🐍"]["说明"]["🐍🐍"]',
|
f' return my_dct["✨🚛✨"]["说明"]["🐍"]["说明"]["🐍🐍"]',
|
||||||
f" ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^",
|
f" ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^",
|
||||||
|
@ -1141,6 +1482,7 @@ class TracebackErrorLocationCaretTestBase:
|
||||||
expected = ['Traceback (most recent call last):',
|
expected = ['Traceback (most recent call last):',
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception',
|
f' File "{__file__}", line {self.callable_line}, in get_exception',
|
||||||
' callable()',
|
' callable()',
|
||||||
|
' ~~~~~~~~^^',
|
||||||
f' File "{__file__}", line {f.__code__.co_firstlineno + 1}, in f',
|
f' File "{__file__}", line {f.__code__.co_firstlineno + 1}, in f',
|
||||||
' raise MemoryError()']
|
' raise MemoryError()']
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
@ -1187,6 +1529,14 @@ class TracebackFormatMixin:
|
||||||
def some_exception(self):
|
def some_exception(self):
|
||||||
raise KeyError('blah')
|
raise KeyError('blah')
|
||||||
|
|
||||||
|
def _filter_debug_ranges(self, expected):
|
||||||
|
return [line for line in expected if not set(line.strip()) <= set("^~")]
|
||||||
|
|
||||||
|
def _maybe_filter_debug_ranges(self, expected):
|
||||||
|
if not self.DEBUG_RANGES:
|
||||||
|
return self._filter_debug_ranges(expected)
|
||||||
|
return expected
|
||||||
|
|
||||||
@cpython_only
|
@cpython_only
|
||||||
def check_traceback_format(self, cleanup_func=None):
|
def check_traceback_format(self, cleanup_func=None):
|
||||||
from _testcapi import traceback_print
|
from _testcapi import traceback_print
|
||||||
|
@ -1199,6 +1549,11 @@ class TracebackFormatMixin:
|
||||||
cleanup_func(tb.tb_next)
|
cleanup_func(tb.tb_next)
|
||||||
traceback_fmt = 'Traceback (most recent call last):\n' + \
|
traceback_fmt = 'Traceback (most recent call last):\n' + \
|
||||||
''.join(traceback.format_tb(tb))
|
''.join(traceback.format_tb(tb))
|
||||||
|
# clear caret lines from traceback_fmt since internal API does
|
||||||
|
# not emit them
|
||||||
|
traceback_fmt = "\n".join(
|
||||||
|
self._filter_debug_ranges(traceback_fmt.splitlines())
|
||||||
|
) + "\n"
|
||||||
file_ = StringIO()
|
file_ = StringIO()
|
||||||
traceback_print(tb, file_)
|
traceback_print(tb, file_)
|
||||||
python_fmt = file_.getvalue()
|
python_fmt = file_.getvalue()
|
||||||
|
@ -1291,12 +1646,16 @@ class TracebackFormatMixin:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
|
f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
|
||||||
' f()\n'
|
' f()\n'
|
||||||
|
' ~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||||
' f()\n'
|
' f()\n'
|
||||||
|
' ~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||||
' f()\n'
|
' f()\n'
|
||||||
|
' ~^^\n'
|
||||||
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
f' File "{__file__}", line {lineno_f+1}, in f\n'
|
||||||
' f()\n'
|
' f()\n'
|
||||||
|
' ~^^\n'
|
||||||
# XXX: The following line changes depending on whether the tests
|
# XXX: The following line changes depending on whether the tests
|
||||||
# are run through the interactive interpreter or with -m
|
# are run through the interactive interpreter or with -m
|
||||||
# It also varies depending on the platform (stack size)
|
# It also varies depending on the platform (stack size)
|
||||||
|
@ -1305,7 +1664,7 @@ class TracebackFormatMixin:
|
||||||
'RecursionError: maximum recursion depth exceeded\n'
|
'RecursionError: maximum recursion depth exceeded\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = result_f.splitlines()
|
expected = self._maybe_filter_debug_ranges(result_f.splitlines())
|
||||||
actual = stderr_f.getvalue().splitlines()
|
actual = stderr_f.getvalue().splitlines()
|
||||||
|
|
||||||
# Check the output text matches expectations
|
# Check the output text matches expectations
|
||||||
|
@ -1337,13 +1696,13 @@ class TracebackFormatMixin:
|
||||||
result_g = (
|
result_g = (
|
||||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||||
' return g(count-1)\n'
|
' return g(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||||
' return g(count-1)\n'
|
' return g(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||||
' return g(count-1)\n'
|
' return g(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
' [Previous line repeated 7 more times]\n'
|
' [Previous line repeated 7 more times]\n'
|
||||||
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
||||||
' raise ValueError\n'
|
' raise ValueError\n'
|
||||||
|
@ -1353,11 +1712,10 @@ class TracebackFormatMixin:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
|
f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
|
||||||
' g()\n'
|
' g()\n'
|
||||||
|
' ~^^\n'
|
||||||
)
|
)
|
||||||
expected = (tb_line + result_g).splitlines()
|
expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
|
||||||
actual = stderr_g.getvalue().splitlines()
|
actual = stderr_g.getvalue().splitlines()
|
||||||
if not self.DEBUG_RANGES:
|
|
||||||
expected = [line for line in expected if not set(line.strip()) == {"^"}]
|
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
# Check 2 different repetitive sections
|
# Check 2 different repetitive sections
|
||||||
|
@ -1379,23 +1737,23 @@ class TracebackFormatMixin:
|
||||||
'Traceback (most recent call last):\n'
|
'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n'
|
f' File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n'
|
||||||
' h()\n'
|
' h()\n'
|
||||||
|
' ~^^\n'
|
||||||
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
||||||
' return h(count-1)\n'
|
' return h(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
||||||
' return h(count-1)\n'
|
' return h(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
f' File "{__file__}", line {lineno_h+2}, in h\n'
|
||||||
' return h(count-1)\n'
|
' return h(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
' [Previous line repeated 7 more times]\n'
|
' [Previous line repeated 7 more times]\n'
|
||||||
f' File "{__file__}", line {lineno_h+3}, in h\n'
|
f' File "{__file__}", line {lineno_h+3}, in h\n'
|
||||||
' g()\n'
|
' g()\n'
|
||||||
|
' ~^^\n'
|
||||||
)
|
)
|
||||||
expected = (result_h + result_g).splitlines()
|
expected = self._maybe_filter_debug_ranges((result_h + result_g).splitlines())
|
||||||
actual = stderr_h.getvalue().splitlines()
|
actual = stderr_h.getvalue().splitlines()
|
||||||
if not self.DEBUG_RANGES:
|
|
||||||
expected = [line for line in expected if not set(line.strip()) == {"^"}]
|
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
# Check the boundary conditions. First, test just below the cutoff.
|
# Check the boundary conditions. First, test just below the cutoff.
|
||||||
|
@ -1409,26 +1767,25 @@ class TracebackFormatMixin:
|
||||||
result_g = (
|
result_g = (
|
||||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||||
' return g(count-1)\n'
|
' return g(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||||
' return g(count-1)\n'
|
' return g(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||||
' return g(count-1)\n'
|
' return g(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
||||||
' raise ValueError\n'
|
' raise ValueError\n'
|
||||||
'ValueError\n'
|
'ValueError\n'
|
||||||
)
|
)
|
||||||
tb_line = (
|
tb_line = (
|
||||||
'Traceback (most recent call last):\n'
|
'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+80}, in _check_recursive_traceback_display\n'
|
||||||
' g(traceback._RECURSIVE_CUTOFF)\n'
|
' g(traceback._RECURSIVE_CUTOFF)\n'
|
||||||
|
' ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
||||||
)
|
)
|
||||||
expected = (tb_line + result_g).splitlines()
|
expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
|
||||||
actual = stderr_g.getvalue().splitlines()
|
actual = stderr_g.getvalue().splitlines()
|
||||||
if not self.DEBUG_RANGES:
|
|
||||||
expected = [line for line in expected if not set(line.strip()) == {"^"}]
|
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
# Second, test just above the cutoff.
|
# Second, test just above the cutoff.
|
||||||
|
@ -1442,13 +1799,13 @@ class TracebackFormatMixin:
|
||||||
result_g = (
|
result_g = (
|
||||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||||
' return g(count-1)\n'
|
' return g(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||||
' return g(count-1)\n'
|
' return g(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
f' File "{__file__}", line {lineno_g+2}, in g\n'
|
||||||
' return g(count-1)\n'
|
' return g(count-1)\n'
|
||||||
' ^^^^^^^^^^\n'
|
' ~^^^^^^^^^\n'
|
||||||
' [Previous line repeated 1 more time]\n'
|
' [Previous line repeated 1 more time]\n'
|
||||||
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
f' File "{__file__}", line {lineno_g+3}, in g\n'
|
||||||
' raise ValueError\n'
|
' raise ValueError\n'
|
||||||
|
@ -1456,13 +1813,12 @@ class TracebackFormatMixin:
|
||||||
)
|
)
|
||||||
tb_line = (
|
tb_line = (
|
||||||
'Traceback (most recent call last):\n'
|
'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+112}, in _check_recursive_traceback_display\n'
|
||||||
' g(traceback._RECURSIVE_CUTOFF + 1)\n'
|
' g(traceback._RECURSIVE_CUTOFF + 1)\n'
|
||||||
|
' ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
|
||||||
)
|
)
|
||||||
expected = (tb_line + result_g).splitlines()
|
expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
|
||||||
actual = stderr_g.getvalue().splitlines()
|
actual = stderr_g.getvalue().splitlines()
|
||||||
if not self.DEBUG_RANGES:
|
|
||||||
expected = [line for line in expected if not set(line.strip()) == {"^"}]
|
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
@requires_debug_ranges()
|
@requires_debug_ranges()
|
||||||
|
@ -1942,6 +2298,7 @@ class BaseExceptionReportingTests:
|
||||||
f' + Exception Group Traceback (most recent call last):\n'
|
f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | exception_or_callable()\n'
|
f' | exception_or_callable()\n'
|
||||||
|
f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
|
||||||
f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
|
f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
|
||||||
f' | ExceptionGroup: eg (2 sub-exceptions)\n'
|
f' | ExceptionGroup: eg (2 sub-exceptions)\n'
|
||||||
|
@ -1977,6 +2334,7 @@ class BaseExceptionReportingTests:
|
||||||
f' + Exception Group Traceback (most recent call last):\n'
|
f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | exception_or_callable()\n'
|
f' | exception_or_callable()\n'
|
||||||
|
f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\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' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n'
|
||||||
f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
|
f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
|
||||||
|
@ -2028,6 +2386,7 @@ class BaseExceptionReportingTests:
|
||||||
f'Traceback (most recent call last):\n'
|
f'Traceback (most recent call last):\n'
|
||||||
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' exception_or_callable()\n'
|
f' exception_or_callable()\n'
|
||||||
|
f' ~~~~~~~~~~~~~~~~~~~~~^^\n'
|
||||||
f' File "{__file__}", line {exc.__code__.co_firstlineno + 8}, in exc\n'
|
f' File "{__file__}", line {exc.__code__.co_firstlineno + 8}, in exc\n'
|
||||||
f' raise ImportError(5)\n'
|
f' raise ImportError(5)\n'
|
||||||
f'ImportError: 5\n')
|
f'ImportError: 5\n')
|
||||||
|
@ -2074,6 +2433,7 @@ class BaseExceptionReportingTests:
|
||||||
f' + Exception Group Traceback (most recent call last):\n'
|
f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | exception_or_callable()\n'
|
f' | exception_or_callable()\n'
|
||||||
|
f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n'
|
||||||
f' | raise EG("top", [VE(5)])\n'
|
f' | raise EG("top", [VE(5)])\n'
|
||||||
f' | ExceptionGroup: top (1 sub-exception)\n'
|
f' | ExceptionGroup: top (1 sub-exception)\n'
|
||||||
|
@ -2233,6 +2593,7 @@ class BaseExceptionReportingTests:
|
||||||
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | exception_or_callable()\n'
|
f' | exception_or_callable()\n'
|
||||||
|
f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
|
||||||
f' | raise ExceptionGroup("nested", excs)\n'
|
f' | raise ExceptionGroup("nested", excs)\n'
|
||||||
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
|
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
|
||||||
|
@ -2284,6 +2645,7 @@ class BaseExceptionReportingTests:
|
||||||
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
expected = (f' + Exception Group Traceback (most recent call last):\n'
|
||||||
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
|
||||||
f' | exception_or_callable()\n'
|
f' | exception_or_callable()\n'
|
||||||
|
f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
|
||||||
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 10}, in exc\n'
|
f' | File "{__file__}", line {exc.__code__.co_firstlineno + 10}, in exc\n'
|
||||||
f' | raise ExceptionGroup("nested", excs)\n'
|
f' | raise ExceptionGroup("nested", excs)\n'
|
||||||
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
|
f' | ExceptionGroup: nested (2 sub-exceptions)\n'
|
||||||
|
@ -2552,7 +2914,7 @@ class TestFrame(unittest.TestCase):
|
||||||
def test_lazy_lines(self):
|
def test_lazy_lines(self):
|
||||||
linecache.clearcache()
|
linecache.clearcache()
|
||||||
f = traceback.FrameSummary("f", 1, "dummy", lookup_line=False)
|
f = traceback.FrameSummary("f", 1, "dummy", lookup_line=False)
|
||||||
self.assertEqual(None, f._line)
|
self.assertEqual(None, f._lines)
|
||||||
linecache.lazycache("f", globals())
|
linecache.lazycache("f", globals())
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'"""Test cases for traceback module"""',
|
'"""Test cases for traceback module"""',
|
||||||
|
@ -3143,6 +3505,7 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
|
||||||
f' | Traceback (most recent call last):',
|
f' | Traceback (most recent call last):',
|
||||||
f' | File "{__file__}", line {lno_g+9}, in _get_exception_group',
|
f' | File "{__file__}", line {lno_g+9}, in _get_exception_group',
|
||||||
f' | f()',
|
f' | f()',
|
||||||
|
f' | ~^^',
|
||||||
f' | File "{__file__}", line {lno_f+1}, in f',
|
f' | File "{__file__}", line {lno_f+1}, in f',
|
||||||
f' | 1/0',
|
f' | 1/0',
|
||||||
f' | ~^~',
|
f' | ~^~',
|
||||||
|
@ -3151,6 +3514,7 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
|
||||||
f' | Traceback (most recent call last):',
|
f' | Traceback (most recent call last):',
|
||||||
f' | File "{__file__}", line {lno_g+13}, in _get_exception_group',
|
f' | File "{__file__}", line {lno_g+13}, in _get_exception_group',
|
||||||
f' | g(42)',
|
f' | g(42)',
|
||||||
|
f' | ~^^^^',
|
||||||
f' | File "{__file__}", line {lno_g+1}, in g',
|
f' | File "{__file__}", line {lno_g+1}, in g',
|
||||||
f' | raise ValueError(v)',
|
f' | raise ValueError(v)',
|
||||||
f' | ValueError: 42',
|
f' | ValueError: 42',
|
||||||
|
@ -3159,6 +3523,7 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
|
||||||
f' | Traceback (most recent call last):',
|
f' | Traceback (most recent call last):',
|
||||||
f' | File "{__file__}", line {lno_g+20}, in _get_exception_group',
|
f' | File "{__file__}", line {lno_g+20}, in _get_exception_group',
|
||||||
f' | g(24)',
|
f' | g(24)',
|
||||||
|
f' | ~^^^^',
|
||||||
f' | File "{__file__}", line {lno_g+1}, in g',
|
f' | File "{__file__}", line {lno_g+1}, in g',
|
||||||
f' | raise ValueError(v)',
|
f' | raise ValueError(v)',
|
||||||
f' | ValueError: 24',
|
f' | ValueError: 24',
|
||||||
|
|
|
@ -1260,8 +1260,8 @@ class EnvironmentVariableTests(BaseTest):
|
||||||
b" File \"<string>\", line 1, in <module>",
|
b" File \"<string>\", line 1, in <module>",
|
||||||
b' import sys, warnings; sys.stdout.write(str(sys.warnoptions)); warnings.w'
|
b' import sys, warnings; sys.stdout.write(str(sys.warnoptions)); warnings.w'
|
||||||
b"arn('Message', DeprecationWarning)",
|
b"arn('Message', DeprecationWarning)",
|
||||||
b' ^^^^^^^^^^'
|
b' ~~~~~~~~~~'
|
||||||
b'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
|
b'~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
|
||||||
b"DeprecationWarning: Message"])
|
b"DeprecationWarning: Message"])
|
||||||
|
|
||||||
def test_default_filter_configuration(self):
|
def test_default_filter_configuration(self):
|
||||||
|
|
362
Lib/traceback.py
362
Lib/traceback.py
|
@ -274,7 +274,7 @@ class FrameSummary:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno',
|
__slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno',
|
||||||
'name', '_line', 'locals')
|
'name', '_lines', '_lines_dedented', 'locals')
|
||||||
|
|
||||||
def __init__(self, filename, lineno, name, *, lookup_line=True,
|
def __init__(self, filename, lineno, name, *, lookup_line=True,
|
||||||
locals=None, line=None,
|
locals=None, line=None,
|
||||||
|
@ -290,15 +290,16 @@ class FrameSummary:
|
||||||
"""
|
"""
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.lineno = lineno
|
self.lineno = lineno
|
||||||
|
self.end_lineno = lineno if end_lineno is None else end_lineno
|
||||||
|
self.colno = colno
|
||||||
|
self.end_colno = end_colno
|
||||||
self.name = name
|
self.name = name
|
||||||
self._line = line
|
self._lines = line
|
||||||
|
self._lines_dedented = None
|
||||||
if lookup_line:
|
if lookup_line:
|
||||||
self.line
|
self.line
|
||||||
self.locals = {k: _safe_string(v, 'local', func=repr)
|
self.locals = {k: _safe_string(v, 'local', func=repr)
|
||||||
for k, v in locals.items()} if locals else None
|
for k, v in locals.items()} if locals else None
|
||||||
self.end_lineno = end_lineno
|
|
||||||
self.colno = colno
|
|
||||||
self.end_colno = end_colno
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, FrameSummary):
|
if isinstance(other, FrameSummary):
|
||||||
|
@ -323,19 +324,39 @@ class FrameSummary:
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return 4
|
return 4
|
||||||
|
|
||||||
|
def _set_lines(self):
|
||||||
|
if (
|
||||||
|
self._lines is None
|
||||||
|
and self.lineno is not None
|
||||||
|
and self.end_lineno is not None
|
||||||
|
):
|
||||||
|
lines = []
|
||||||
|
for lineno in range(self.lineno, self.end_lineno + 1):
|
||||||
|
# treat errors (empty string) and empty lines (newline) as the same
|
||||||
|
lines.append(linecache.getline(self.filename, lineno).rstrip())
|
||||||
|
self._lines = "\n".join(lines) + "\n"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _original_line(self):
|
def _original_lines(self):
|
||||||
# Returns the line as-is from the source, without modifying whitespace.
|
# Returns the line as-is from the source, without modifying whitespace.
|
||||||
self.line
|
self._set_lines()
|
||||||
return self._line
|
return self._lines
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _dedented_lines(self):
|
||||||
|
# Returns _original_lines, but dedented
|
||||||
|
self._set_lines()
|
||||||
|
if self._lines_dedented is None and self._lines is not None:
|
||||||
|
self._lines_dedented = textwrap.dedent(self._lines)
|
||||||
|
return self._lines_dedented
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def line(self):
|
def line(self):
|
||||||
if self._line is None:
|
self._set_lines()
|
||||||
if self.lineno is None:
|
if self._lines is None:
|
||||||
return None
|
return None
|
||||||
self._line = linecache.getline(self.filename, self.lineno)
|
# return only the first line, stripped
|
||||||
return self._line.strip()
|
return self._lines.partition("\n")[0].strip()
|
||||||
|
|
||||||
|
|
||||||
def walk_stack(f):
|
def walk_stack(f):
|
||||||
|
@ -487,56 +508,135 @@ class StackSummary(list):
|
||||||
filename = "<stdin>"
|
filename = "<stdin>"
|
||||||
row.append(' File "{}", line {}, in {}\n'.format(
|
row.append(' File "{}", line {}, in {}\n'.format(
|
||||||
filename, frame_summary.lineno, frame_summary.name))
|
filename, frame_summary.lineno, frame_summary.name))
|
||||||
if frame_summary.line:
|
if frame_summary._dedented_lines and frame_summary._dedented_lines.strip():
|
||||||
stripped_line = frame_summary.line.strip()
|
|
||||||
row.append(' {}\n'.format(stripped_line))
|
|
||||||
|
|
||||||
line = frame_summary._original_line
|
|
||||||
orig_line_len = len(line)
|
|
||||||
frame_line_len = len(frame_summary.line.lstrip())
|
|
||||||
stripped_characters = orig_line_len - frame_line_len
|
|
||||||
if (
|
if (
|
||||||
frame_summary.colno is not None
|
frame_summary.colno is None or
|
||||||
and frame_summary.end_colno is not None
|
frame_summary.end_colno is None
|
||||||
):
|
):
|
||||||
start_offset = _byte_offset_to_character_offset(
|
# only output first line if column information is missing
|
||||||
line, frame_summary.colno)
|
row.append(textwrap.indent(frame_summary.line, ' ') + "\n")
|
||||||
end_offset = _byte_offset_to_character_offset(
|
else:
|
||||||
line, frame_summary.end_colno)
|
# get first and last line
|
||||||
code_segment = line[start_offset:end_offset]
|
all_lines_original = frame_summary._original_lines.splitlines()
|
||||||
|
first_line = all_lines_original[0]
|
||||||
|
# assume all_lines_original has enough lines (since we constructed it)
|
||||||
|
last_line = all_lines_original[frame_summary.end_lineno - frame_summary.lineno]
|
||||||
|
|
||||||
|
# character index of the start/end of the instruction
|
||||||
|
start_offset = _byte_offset_to_character_offset(first_line, frame_summary.colno)
|
||||||
|
end_offset = _byte_offset_to_character_offset(last_line, frame_summary.end_colno)
|
||||||
|
|
||||||
|
all_lines = frame_summary._dedented_lines.splitlines()[
|
||||||
|
:frame_summary.end_lineno - frame_summary.lineno + 1
|
||||||
|
]
|
||||||
|
|
||||||
|
# adjust start/end offset based on dedent
|
||||||
|
dedent_characters = len(first_line) - len(all_lines[0])
|
||||||
|
start_offset = max(0, start_offset - dedent_characters)
|
||||||
|
end_offset = max(0, end_offset - dedent_characters)
|
||||||
|
|
||||||
|
# When showing this on a terminal, some of the non-ASCII characters
|
||||||
|
# might be rendered as double-width characters, so we need to take
|
||||||
|
# that into account when calculating the length of the line.
|
||||||
|
dp_start_offset = _display_width(all_lines[0], offset=start_offset)
|
||||||
|
dp_end_offset = _display_width(all_lines[-1], offset=end_offset)
|
||||||
|
|
||||||
|
# get exact code segment corresponding to the instruction
|
||||||
|
segment = "\n".join(all_lines)
|
||||||
|
segment = segment[start_offset:len(segment) - (len(all_lines[-1]) - end_offset)]
|
||||||
|
|
||||||
|
# attempt to parse for anchors
|
||||||
anchors = None
|
anchors = None
|
||||||
if frame_summary.lineno == frame_summary.end_lineno:
|
with suppress(Exception):
|
||||||
with suppress(Exception):
|
anchors = _extract_caret_anchors_from_line_segment(segment)
|
||||||
anchors = _extract_caret_anchors_from_line_segment(code_segment)
|
|
||||||
else:
|
|
||||||
# Don't count the newline since the anchors only need to
|
|
||||||
# go up until the last character of the line.
|
|
||||||
end_offset = len(line.rstrip())
|
|
||||||
|
|
||||||
# show indicators if primary char doesn't span the frame line
|
# only use carets if there are anchors or the carets do not span all lines
|
||||||
if end_offset - start_offset < len(stripped_line) or (
|
show_carets = False
|
||||||
anchors and anchors.right_start_offset - anchors.left_end_offset > 0):
|
if anchors or all_lines[0][:start_offset].lstrip() or all_lines[-1][end_offset:].rstrip():
|
||||||
# When showing this on a terminal, some of the non-ASCII characters
|
show_carets = True
|
||||||
# might be rendered as double-width characters, so we need to take
|
|
||||||
# that into account when calculating the length of the line.
|
|
||||||
dp_start_offset = _display_width(line, start_offset) + 1
|
|
||||||
dp_end_offset = _display_width(line, end_offset) + 1
|
|
||||||
|
|
||||||
row.append(' ')
|
result = []
|
||||||
row.append(' ' * (dp_start_offset - stripped_characters))
|
|
||||||
|
|
||||||
if anchors:
|
# only display first line, last line, and lines around anchor start/end
|
||||||
dp_left_end_offset = _display_width(code_segment, anchors.left_end_offset)
|
significant_lines = {0, len(all_lines) - 1}
|
||||||
dp_right_start_offset = _display_width(code_segment, anchors.right_start_offset)
|
|
||||||
row.append(anchors.primary_char * dp_left_end_offset)
|
|
||||||
row.append(anchors.secondary_char * (dp_right_start_offset - dp_left_end_offset))
|
|
||||||
row.append(anchors.primary_char * (dp_end_offset - dp_start_offset - dp_right_start_offset))
|
|
||||||
else:
|
|
||||||
row.append('^' * (dp_end_offset - dp_start_offset))
|
|
||||||
|
|
||||||
row.append('\n')
|
anchors_left_end_offset = 0
|
||||||
|
anchors_right_start_offset = 0
|
||||||
|
primary_char = "^"
|
||||||
|
secondary_char = "^"
|
||||||
|
if anchors:
|
||||||
|
anchors_left_end_offset = anchors.left_end_offset
|
||||||
|
anchors_right_start_offset = anchors.right_start_offset
|
||||||
|
# computed anchor positions do not take start_offset into account,
|
||||||
|
# so account for it here
|
||||||
|
if anchors.left_end_lineno == 0:
|
||||||
|
anchors_left_end_offset += start_offset
|
||||||
|
if anchors.right_start_lineno == 0:
|
||||||
|
anchors_right_start_offset += start_offset
|
||||||
|
|
||||||
|
# account for display width
|
||||||
|
anchors_left_end_offset = _display_width(
|
||||||
|
all_lines[anchors.left_end_lineno], offset=anchors_left_end_offset
|
||||||
|
)
|
||||||
|
anchors_right_start_offset = _display_width(
|
||||||
|
all_lines[anchors.right_start_lineno], offset=anchors_right_start_offset
|
||||||
|
)
|
||||||
|
|
||||||
|
primary_char = anchors.primary_char
|
||||||
|
secondary_char = anchors.secondary_char
|
||||||
|
significant_lines.update(
|
||||||
|
range(anchors.left_end_lineno - 1, anchors.left_end_lineno + 2)
|
||||||
|
)
|
||||||
|
significant_lines.update(
|
||||||
|
range(anchors.right_start_lineno - 1, anchors.right_start_lineno + 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
# remove bad line numbers
|
||||||
|
significant_lines.discard(-1)
|
||||||
|
significant_lines.discard(len(all_lines))
|
||||||
|
|
||||||
|
def output_line(lineno):
|
||||||
|
"""output all_lines[lineno] along with carets"""
|
||||||
|
result.append(all_lines[lineno] + "\n")
|
||||||
|
if not show_carets:
|
||||||
|
return
|
||||||
|
num_spaces = len(all_lines[lineno]) - len(all_lines[lineno].lstrip())
|
||||||
|
carets = []
|
||||||
|
num_carets = dp_end_offset if lineno == len(all_lines) - 1 else _display_width(all_lines[lineno])
|
||||||
|
# compute caret character for each position
|
||||||
|
for col in range(num_carets):
|
||||||
|
if col < num_spaces or (lineno == 0 and col < dp_start_offset):
|
||||||
|
# before first non-ws char of the line, or before start of instruction
|
||||||
|
carets.append(' ')
|
||||||
|
elif anchors and (
|
||||||
|
lineno > anchors.left_end_lineno or
|
||||||
|
(lineno == anchors.left_end_lineno and col >= anchors_left_end_offset)
|
||||||
|
) and (
|
||||||
|
lineno < anchors.right_start_lineno or
|
||||||
|
(lineno == anchors.right_start_lineno and col < anchors_right_start_offset)
|
||||||
|
):
|
||||||
|
# within anchors
|
||||||
|
carets.append(secondary_char)
|
||||||
|
else:
|
||||||
|
carets.append(primary_char)
|
||||||
|
result.append("".join(carets) + "\n")
|
||||||
|
|
||||||
|
# display significant lines
|
||||||
|
sig_lines_list = sorted(significant_lines)
|
||||||
|
for i, lineno in enumerate(sig_lines_list):
|
||||||
|
if i:
|
||||||
|
linediff = lineno - sig_lines_list[i - 1]
|
||||||
|
if linediff == 2:
|
||||||
|
# 1 line in between - just output it
|
||||||
|
output_line(lineno - 1)
|
||||||
|
elif linediff > 2:
|
||||||
|
# > 1 line in between - abbreviate
|
||||||
|
result.append(f"...<{linediff - 1} lines>...\n")
|
||||||
|
output_line(lineno)
|
||||||
|
|
||||||
|
row.append(
|
||||||
|
textwrap.indent(textwrap.dedent("".join(result)), ' ', lambda line: True)
|
||||||
|
)
|
||||||
if frame_summary.locals:
|
if frame_summary.locals:
|
||||||
for name, value in sorted(frame_summary.locals.items()):
|
for name, value in sorted(frame_summary.locals.items()):
|
||||||
row.append(' {name} = {value}\n'.format(name=name, value=value))
|
row.append(' {name} = {value}\n'.format(name=name, value=value))
|
||||||
|
@ -599,7 +699,9 @@ def _byte_offset_to_character_offset(str, offset):
|
||||||
_Anchors = collections.namedtuple(
|
_Anchors = collections.namedtuple(
|
||||||
"_Anchors",
|
"_Anchors",
|
||||||
[
|
[
|
||||||
|
"left_end_lineno",
|
||||||
"left_end_offset",
|
"left_end_offset",
|
||||||
|
"right_start_lineno",
|
||||||
"right_start_offset",
|
"right_start_offset",
|
||||||
"primary_char",
|
"primary_char",
|
||||||
"secondary_char",
|
"secondary_char",
|
||||||
|
@ -608,59 +710,161 @@ _Anchors = collections.namedtuple(
|
||||||
)
|
)
|
||||||
|
|
||||||
def _extract_caret_anchors_from_line_segment(segment):
|
def _extract_caret_anchors_from_line_segment(segment):
|
||||||
|
"""
|
||||||
|
Given source code `segment` corresponding to a FrameSummary, determine:
|
||||||
|
- for binary ops, the location of the binary op
|
||||||
|
- for indexing and function calls, the location of the brackets.
|
||||||
|
`segment` is expected to be a valid Python expression.
|
||||||
|
"""
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(segment)
|
# Without parentheses, `segment` is parsed as a statement.
|
||||||
|
# Binary ops, subscripts, and calls are expressions, so
|
||||||
|
# we can wrap them with parentheses to parse them as
|
||||||
|
# (possibly multi-line) expressions.
|
||||||
|
# e.g. if we try to highlight the addition in
|
||||||
|
# x = (
|
||||||
|
# a +
|
||||||
|
# b
|
||||||
|
# )
|
||||||
|
# then we would ast.parse
|
||||||
|
# a +
|
||||||
|
# b
|
||||||
|
# which is not a valid statement because of the newline.
|
||||||
|
# Adding brackets makes it a valid expression.
|
||||||
|
# (
|
||||||
|
# a +
|
||||||
|
# b
|
||||||
|
# )
|
||||||
|
# Line locations will be different than the original,
|
||||||
|
# which is taken into account later on.
|
||||||
|
tree = ast.parse(f"(\n{segment}\n)")
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if len(tree.body) != 1:
|
if len(tree.body) != 1:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
normalize = lambda offset: _byte_offset_to_character_offset(segment, offset)
|
lines = segment.splitlines()
|
||||||
|
|
||||||
|
def normalize(lineno, offset):
|
||||||
|
"""Get character index given byte offset"""
|
||||||
|
return _byte_offset_to_character_offset(lines[lineno], offset)
|
||||||
|
|
||||||
|
def next_valid_char(lineno, col):
|
||||||
|
"""Gets the next valid character index in `lines`, if
|
||||||
|
the current location is not valid. Handles empty lines.
|
||||||
|
"""
|
||||||
|
while lineno < len(lines) and col >= len(lines[lineno]):
|
||||||
|
col = 0
|
||||||
|
lineno += 1
|
||||||
|
assert lineno < len(lines) and col < len(lines[lineno])
|
||||||
|
return lineno, col
|
||||||
|
|
||||||
|
def increment(lineno, col):
|
||||||
|
"""Get the next valid character index in `lines`."""
|
||||||
|
col += 1
|
||||||
|
lineno, col = next_valid_char(lineno, col)
|
||||||
|
return lineno, col
|
||||||
|
|
||||||
|
def nextline(lineno, col):
|
||||||
|
"""Get the next valid character at least on the next line"""
|
||||||
|
col = 0
|
||||||
|
lineno += 1
|
||||||
|
lineno, col = next_valid_char(lineno, col)
|
||||||
|
return lineno, col
|
||||||
|
|
||||||
|
def increment_until(lineno, col, stop):
|
||||||
|
"""Get the next valid non-"\\#" character that satisfies the `stop` predicate"""
|
||||||
|
while True:
|
||||||
|
ch = lines[lineno][col]
|
||||||
|
if ch in "\\#":
|
||||||
|
lineno, col = nextline(lineno, col)
|
||||||
|
elif not stop(ch):
|
||||||
|
lineno, col = increment(lineno, col)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return lineno, col
|
||||||
|
|
||||||
|
def setup_positions(expr, force_valid=True):
|
||||||
|
"""Get the lineno/col position of the end of `expr`. If `force_valid` is True,
|
||||||
|
forces the position to be a valid character (e.g. if the position is beyond the
|
||||||
|
end of the line, move to the next line)
|
||||||
|
"""
|
||||||
|
# -2 since end_lineno is 1-indexed and because we added an extra
|
||||||
|
# bracket + newline to `segment` when calling ast.parse
|
||||||
|
lineno = expr.end_lineno - 2
|
||||||
|
col = normalize(lineno, expr.end_col_offset)
|
||||||
|
return next_valid_char(lineno, col) if force_valid else (lineno, col)
|
||||||
|
|
||||||
statement = tree.body[0]
|
statement = tree.body[0]
|
||||||
match statement:
|
match statement:
|
||||||
case ast.Expr(expr):
|
case ast.Expr(expr):
|
||||||
match expr:
|
match expr:
|
||||||
case ast.BinOp():
|
case ast.BinOp():
|
||||||
operator_start = normalize(expr.left.end_col_offset)
|
# ast gives these locations for BinOp subexpressions
|
||||||
operator_end = normalize(expr.right.col_offset)
|
# ( left_expr ) + ( right_expr )
|
||||||
operator_str = segment[operator_start:operator_end]
|
# left^^^^^ right^^^^^
|
||||||
operator_offset = len(operator_str) - len(operator_str.lstrip())
|
lineno, col = setup_positions(expr.left)
|
||||||
|
|
||||||
left_anchor = expr.left.end_col_offset + operator_offset
|
# First operator character is the first non-space/')' character
|
||||||
right_anchor = left_anchor + 1
|
lineno, col = increment_until(lineno, col, lambda x: not x.isspace() and x != ')')
|
||||||
|
|
||||||
|
# binary op is 1 or 2 characters long, on the same line,
|
||||||
|
# before the right subexpression
|
||||||
|
right_col = col + 1
|
||||||
if (
|
if (
|
||||||
operator_offset + 1 < len(operator_str)
|
right_col < len(lines[lineno])
|
||||||
and not operator_str[operator_offset + 1].isspace()
|
and (
|
||||||
|
# operator char should not be in the right subexpression
|
||||||
|
expr.right.lineno - 2 > lineno or
|
||||||
|
right_col < normalize(expr.right.lineno - 2, expr.right.col_offset)
|
||||||
|
)
|
||||||
|
and not (ch := lines[lineno][right_col]).isspace()
|
||||||
|
and ch not in "\\#"
|
||||||
):
|
):
|
||||||
right_anchor += 1
|
right_col += 1
|
||||||
|
|
||||||
while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch in ")#"):
|
# right_col can be invalid since it is exclusive
|
||||||
left_anchor += 1
|
return _Anchors(lineno, col, lineno, right_col)
|
||||||
right_anchor += 1
|
|
||||||
return _Anchors(normalize(left_anchor), normalize(right_anchor))
|
|
||||||
case ast.Subscript():
|
case ast.Subscript():
|
||||||
left_anchor = normalize(expr.value.end_col_offset)
|
# ast gives these locations for value and slice subexpressions
|
||||||
right_anchor = normalize(expr.slice.end_col_offset + 1)
|
# ( value_expr ) [ slice_expr ]
|
||||||
while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch != "["):
|
# value^^^^^ slice^^^^^
|
||||||
left_anchor += 1
|
# subscript^^^^^^^^^^^^^^^^^^^^
|
||||||
while right_anchor < len(segment) and ((ch := segment[right_anchor]).isspace() or ch != "]"):
|
|
||||||
right_anchor += 1
|
# find left bracket
|
||||||
if right_anchor < len(segment):
|
left_lineno, left_col = setup_positions(expr.value)
|
||||||
right_anchor += 1
|
left_lineno, left_col = increment_until(left_lineno, left_col, lambda x: x == '[')
|
||||||
return _Anchors(left_anchor, right_anchor)
|
# find right bracket (final character of expression)
|
||||||
|
right_lineno, right_col = setup_positions(expr, force_valid=False)
|
||||||
|
return _Anchors(left_lineno, left_col, right_lineno, right_col)
|
||||||
|
case ast.Call():
|
||||||
|
# ast gives these locations for function call expressions
|
||||||
|
# ( func_expr ) (args, kwargs)
|
||||||
|
# func^^^^^
|
||||||
|
# call^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
# find left bracket
|
||||||
|
left_lineno, left_col = setup_positions(expr.func)
|
||||||
|
left_lineno, left_col = increment_until(left_lineno, left_col, lambda x: x == '(')
|
||||||
|
# find right bracket (final character of expression)
|
||||||
|
right_lineno, right_col = setup_positions(expr, force_valid=False)
|
||||||
|
return _Anchors(left_lineno, left_col, right_lineno, right_col)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
_WIDE_CHAR_SPECIFIERS = "WF"
|
_WIDE_CHAR_SPECIFIERS = "WF"
|
||||||
|
|
||||||
def _display_width(line, offset):
|
def _display_width(line, offset=None):
|
||||||
"""Calculate the extra amount of width space the given source
|
"""Calculate the extra amount of width space the given source
|
||||||
code segment might take if it were to be displayed on a fixed
|
code segment might take if it were to be displayed on a fixed
|
||||||
width output device. Supports wide unicode characters and emojis."""
|
width output device. Supports wide unicode characters and emojis."""
|
||||||
|
|
||||||
|
if offset is None:
|
||||||
|
offset = len(line)
|
||||||
|
|
||||||
# Fast track for ASCII-only strings
|
# Fast track for ASCII-only strings
|
||||||
if line.isascii():
|
if line.isascii():
|
||||||
return offset
|
return offset
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Display multiple lines with ``traceback`` when errors span multiple lines.
|
Loading…
Add table
Add a link
Reference in a new issue