]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-99180: Remove traceback anchors in return and assign statements that cover all...
authorPablo Galindo Salgado <Pablogsal@gmail.com>
Wed, 1 May 2024 13:42:10 +0000 (14:42 +0100)
committerGitHub <noreply@github.com>
Wed, 1 May 2024 13:42:10 +0000 (14:42 +0100)
Lib/test/test_traceback.py
Lib/traceback.py
Misc/NEWS.d/next/Core and Builtins/2023-12-03-18-21-59.gh-issue-99180.5m0V0q.rst [new file with mode: 0644]

index 8927fccc28932094eee73a708ed97e82c5bed843..41f82512480d4f62b568e301fc7e2665c64103db 100644 (file)
@@ -695,7 +695,6 @@ class TracebackErrorLocationCaretTestBase:
             '    ~~~~~~~~^^\n'
             f'  File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
             '    return compile(code, "?", "exec")\n'
-            '           ~~~~~~~^^^^^^^^^^^^^^^^^^^\n'
             '  File "?", line 7\n'
             '    foo(a, z\n'
             '           ^'
@@ -785,8 +784,8 @@ class TracebackErrorLocationCaretTestBase:
     def test_caret_for_binary_operators_with_spaces_and_parenthesis(self):
         def f_with_binary_operator():
             a = 1
-            b = ""
-            return ( a   )   +b
+            b = c = ""
+            return ( a   )   +b + c
 
         lineno_f = f_with_binary_operator.__code__.co_firstlineno
         expected_error = (
@@ -795,7 +794,7 @@ class TracebackErrorLocationCaretTestBase:
             '    callable()\n'
             '    ~~~~~~~~^^\n'
             f'  File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
-            '    return ( a   )   +b\n'
+            '    return ( a   )   +b + c\n'
             '           ~~~~~~~~~~^~\n'
         )
         result_lines = self.get_exception(f_with_binary_operator)
@@ -983,7 +982,7 @@ class TracebackErrorLocationCaretTestBase:
                 def f2(b):
                     raise RuntimeError("fail")
                 return f2
-            return f1("x")("y")
+            return f1("x")("y")("z")
 
         lineno_f = f_with_call.__code__.co_firstlineno
         expected_error = (
@@ -992,7 +991,7 @@ class TracebackErrorLocationCaretTestBase:
             '    callable()\n'
             '    ~~~~~~~~^^\n'
             f'  File "{__file__}", line {lineno_f+5}, in f_with_call\n'
-            '    return f1("x")("y")\n'
+            '    return f1("x")("y")("z")\n'
             '           ~~~~~~~^^^^^\n'
             f'  File "{__file__}", line {lineno_f+3}, in f2\n'
             '    raise RuntimeError("fail")\n'
@@ -1507,6 +1506,184 @@ class TracebackErrorLocationCaretTestBase:
             '    raise MemoryError()']
         self.assertEqual(actual, expected)
 
+    def test_anchors_for_simple_return_statements_are_elided(self):
+        def g():
+            1/0
+
+        def f():
+            return g()
+
+        result_lines = self.get_exception(f)
+        expected = ['Traceback (most recent call last):',
+            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
+            "    callable()",
+            "    ~~~~~~~~^^",
+            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+            "    return g()",
+            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+            "    1/0",
+            "    ~^~"
+        ]
+        self.assertEqual(result_lines, expected)
+
+        def g():
+            1/0
+
+        def f():
+            return g() + 1
+
+        result_lines = self.get_exception(f)
+        expected = ['Traceback (most recent call last):',
+            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
+            "    callable()",
+            "    ~~~~~~~~^^",
+            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+            "    return g() + 1",
+            "           ~^^",
+            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+            "    1/0",
+            "    ~^~"
+        ]
+        self.assertEqual(result_lines, expected)
+
+        def g(*args):
+            1/0
+
+        def f():
+            return g(1,
+                     2, 4,
+                     5)
+
+        result_lines = self.get_exception(f)
+        expected = ['Traceback (most recent call last):',
+            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
+            "    callable()",
+            "    ~~~~~~~~^^",
+            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+            "    return g(1,",
+            "             2, 4,",
+            "             5)",
+            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+            "    1/0",
+            "    ~^~"
+        ]
+        self.assertEqual(result_lines, expected)
+
+        def g(*args):
+            1/0
+
+        def f():
+            return g(1,
+                     2, 4,
+                     5) + 1
+
+        result_lines = self.get_exception(f)
+        expected = ['Traceback (most recent call last):',
+            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
+            "    callable()",
+            "    ~~~~~~~~^^",
+            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+            "    return g(1,",
+            "           ~^^^",
+            "             2, 4,",
+            "             ^^^^^",
+            "             5) + 1",
+            "             ^^",
+            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+            "    1/0",
+            "    ~^~"
+        ]
+        self.assertEqual(result_lines, expected)
+
+    def test_anchors_for_simple_assign_statements_are_elided(self):
+        def g():
+            1/0
+
+        def f():
+            x = g()
+
+        result_lines = self.get_exception(f)
+        expected = ['Traceback (most recent call last):',
+            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
+            "    callable()",
+            "    ~~~~~~~~^^",
+            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+            "    x = g()",
+            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+            "    1/0",
+            "    ~^~"
+        ]
+        self.assertEqual(result_lines, expected)
+
+        def g(*args):
+            1/0
+
+        def f():
+            x = g(1,
+                  2, 3,
+                  4)
+
+        result_lines = self.get_exception(f)
+        expected = ['Traceback (most recent call last):',
+            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
+            "    callable()",
+            "    ~~~~~~~~^^",
+            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+            "    x = g(1,",
+            "          2, 3,",
+            "          4)",
+            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+            "    1/0",
+            "    ~^~"
+        ]
+        self.assertEqual(result_lines, expected)
+
+        def g():
+            1/0
+
+        def f():
+            x = y = g()
+
+        result_lines = self.get_exception(f)
+        expected = ['Traceback (most recent call last):',
+            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
+            "    callable()",
+            "    ~~~~~~~~^^",
+            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+            "    x = y = g()",
+            "            ~^^",
+            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+            "    1/0",
+            "    ~^~"
+        ]
+        self.assertEqual(result_lines, expected)
+
+        def g(*args):
+            1/0
+
+        def f():
+            x = y = g(1,
+                      2, 3,
+                      4)
+
+        result_lines = self.get_exception(f)
+        expected = ['Traceback (most recent call last):',
+            f"  File \"{__file__}\", line {self.callable_line}, in get_exception",
+            "    callable()",
+            "    ~~~~~~~~^^",
+            f"  File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+            "    x = y = g(1,",
+            "            ~^^^",
+            "              2, 3,",
+            "              ^^^^^",
+            "              4)",
+            "              ^^",
+            f"  File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+            "    1/0",
+            "    ~^~"
+        ]
+        self.assertEqual(result_lines, expected)
+
 
 @requires_debug_ranges()
 class PurePythonTracebackErrorCaretTests(
@@ -1701,7 +1878,7 @@ class TracebackFormatMixin:
         # Check a known (limited) number of recursive invocations
         def g(count=10):
             if count:
-                return g(count-1)
+                return g(count-1) + 1
             raise ValueError
 
         with captured_output("stderr") as stderr_g:
@@ -1715,13 +1892,13 @@ class TracebackFormatMixin:
         lineno_g = g.__code__.co_firstlineno
         result_g = (
             f'  File "{__file__}", line {lineno_g+2}, in g\n'
-            '    return g(count-1)\n'
+            '    return g(count-1) + 1\n'
             '           ~^^^^^^^^^\n'
             f'  File "{__file__}", line {lineno_g+2}, in g\n'
-            '    return g(count-1)\n'
+            '    return g(count-1) + 1\n'
             '           ~^^^^^^^^^\n'
             f'  File "{__file__}", line {lineno_g+2}, in g\n'
-            '    return g(count-1)\n'
+            '    return g(count-1) + 1\n'
             '           ~^^^^^^^^^\n'
             '  [Previous line repeated 7 more times]\n'
             f'  File "{__file__}", line {lineno_g+3}, in g\n'
@@ -1760,13 +1937,10 @@ class TracebackFormatMixin:
             '    ~^^\n'
             f'  File "{__file__}", line {lineno_h+2}, in h\n'
             '    return h(count-1)\n'
-            '           ~^^^^^^^^^\n'
             f'  File "{__file__}", line {lineno_h+2}, in h\n'
             '    return h(count-1)\n'
-            '           ~^^^^^^^^^\n'
             f'  File "{__file__}", line {lineno_h+2}, in h\n'
             '    return h(count-1)\n'
-            '           ~^^^^^^^^^\n'
             '  [Previous line repeated 7 more times]\n'
             f'  File "{__file__}", line {lineno_h+3}, in h\n'
             '    g()\n'
@@ -1786,13 +1960,13 @@ class TracebackFormatMixin:
                 self.fail("no error raised")
         result_g = (
             f'  File "{__file__}", line {lineno_g+2}, in g\n'
-            '    return g(count-1)\n'
+            '    return g(count-1) + 1\n'
             '           ~^^^^^^^^^\n'
             f'  File "{__file__}", line {lineno_g+2}, in g\n'
-            '    return g(count-1)\n'
+            '    return g(count-1) + 1\n'
             '           ~^^^^^^^^^\n'
             f'  File "{__file__}", line {lineno_g+2}, in g\n'
-            '    return g(count-1)\n'
+            '    return g(count-1) + 1\n'
             '           ~^^^^^^^^^\n'
             f'  File "{__file__}", line {lineno_g+3}, in g\n'
             '    raise ValueError\n'
@@ -1800,7 +1974,7 @@ class TracebackFormatMixin:
         )
         tb_line = (
             'Traceback (most recent call last):\n'
-            f'  File "{__file__}", line {lineno_g+80}, in _check_recursive_traceback_display\n'
+            f'  File "{__file__}", line {lineno_g+77}, in _check_recursive_traceback_display\n'
             '    g(traceback._RECURSIVE_CUTOFF)\n'
             '    ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
         )
@@ -1818,13 +1992,13 @@ class TracebackFormatMixin:
                 self.fail("no error raised")
         result_g = (
             f'  File "{__file__}", line {lineno_g+2}, in g\n'
-            '    return g(count-1)\n'
+            '    return g(count-1) + 1\n'
             '           ~^^^^^^^^^\n'
             f'  File "{__file__}", line {lineno_g+2}, in g\n'
-            '    return g(count-1)\n'
+            '    return g(count-1) + 1\n'
             '           ~^^^^^^^^^\n'
             f'  File "{__file__}", line {lineno_g+2}, in g\n'
-            '    return g(count-1)\n'
+            '    return g(count-1) + 1\n'
             '           ~^^^^^^^^^\n'
             '  [Previous line repeated 1 more time]\n'
             f'  File "{__file__}", line {lineno_g+3}, in g\n'
@@ -1833,7 +2007,7 @@ class TracebackFormatMixin:
         )
         tb_line = (
             'Traceback (most recent call last):\n'
-            f'  File "{__file__}", line {lineno_g+112}, in _check_recursive_traceback_display\n'
+            f'  File "{__file__}", line {lineno_g+109}, in _check_recursive_traceback_display\n'
             '    g(traceback._RECURSIVE_CUTOFF + 1)\n'
             '    ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
         )
@@ -4287,11 +4461,14 @@ class TestColorizedTraceback(unittest.TestCase):
             x = {'a':{'b': None}}
             y = x['a']['b']['c']
 
-        def baz(*args):
-            return foo(1,2,3,4)
+        def baz2(*args):
+            return (lambda *args: foo(*args))(1,2,3,4)
+
+        def baz1(*args):
+            return baz2(1,2,3,4)
 
         def bar():
-            return baz(1,
+            return baz1(1,
                     2,3
                     ,4)
         try:
@@ -4305,10 +4482,10 @@ class TestColorizedTraceback(unittest.TestCase):
         boldr = traceback._ANSIColors.BOLD_RED
         reset = traceback._ANSIColors.RESET
         self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines)
-        self.assertIn("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, lines)
-        self.assertIn("return " + red + "baz" + reset + boldr + "(1," + reset, lines)
-        self.assertIn(boldr + "2,3" + reset, lines)
-        self.assertIn(boldr + ",4)" + reset, lines)
+        self.assertIn("return " + red + "(lambda *args: foo(*args))" + reset + boldr + "(1,2,3,4)" + reset, lines)
+        self.assertIn("return (lambda *args: " + red + "foo" + reset + boldr + "(*args)" + reset + ")(1,2,3,4)", lines)
+        self.assertIn("return baz2(1,2,3,4)", lines)
+        self.assertIn("return baz1(1,\n            2,3\n            ,4)", lines)
         self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines)
 
     def test_colorized_syntax_error(self):
index fccec0c71c3695d39fc1470ed5d4776262118402..6ce745f32d132422469fa72c09fd7a9f97e26353 100644 (file)
@@ -617,13 +617,10 @@ class StackSummary(list):
 
                 # attempt to parse for anchors
                 anchors = None
+                show_carets = False
                 with suppress(Exception):
                     anchors = _extract_caret_anchors_from_line_segment(segment)
-
-                # only use carets if there are anchors or the carets do not span all lines
-                show_carets = False
-                if anchors or all_lines[0][:start_offset].lstrip() or all_lines[-1][end_offset:].rstrip():
-                    show_carets = True
+                show_carets = self.should_show_carets(start_offset, end_offset, all_lines, anchors)
 
                 result = []
 
@@ -737,6 +734,37 @@ class StackSummary(list):
 
         return ''.join(row)
 
+    def should_show_carets(self, start_offset, end_offset, all_lines, anchors):
+        with suppress(SyntaxError, ImportError):
+            import ast
+            tree = ast.parse('\n'.join(all_lines))
+            statement = tree.body[0]
+            value = None
+            def _spawns_full_line(value):
+                return (
+                    value.lineno == 1
+                    and value.end_lineno == len(all_lines)
+                    and value.col_offset == start_offset
+                    and value.end_col_offset == end_offset
+                )
+            match statement:
+                case ast.Return(value=ast.Call()):
+                    if isinstance(statement.value.func, ast.Name):
+                        value = statement.value
+                case ast.Assign(value=ast.Call()):
+                    if (
+                        len(statement.targets) == 1 and
+                        isinstance(statement.targets[0], ast.Name)
+                    ):
+                        value = statement.value
+            if value is not None and _spawns_full_line(value):
+                return False
+        if anchors:
+            return True
+        if all_lines[0][:start_offset].lstrip() or all_lines[-1][end_offset:].rstrip():
+            return True
+        return False
+
     def format(self, **kwargs):
         """Format the stack ready for printing.
 
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-03-18-21-59.gh-issue-99180.5m0V0q.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-03-18-21-59.gh-issue-99180.5m0V0q.rst
new file mode 100644 (file)
index 0000000..576626b
--- /dev/null
@@ -0,0 +1,2 @@
+Elide uninformative traceback indicators in ``return`` and simple
+``assignment`` statements. Patch by Pablo Galindo.