]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-37950: Fix ast.dump() when call with incompletely initialized node. (GH-15510)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Thu, 29 Aug 2019 07:04:44 +0000 (00:04 -0700)
committerGitHub <noreply@github.com>
Thu, 29 Aug 2019 07:04:44 +0000 (00:04 -0700)
(cherry picked from commit e64f948e762a6b9fd02e2902ccf42438df6fcb61)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Doc/library/ast.rst
Lib/ast.py
Lib/test/test_ast.py
Misc/NEWS.d/next/Library/2019-08-26-10-45-51.bpo-37950.-K1IKT.rst [new file with mode: 0644]

index 3c73f748acbe36549290abd04264cd4e4a18c922..699ad0456a02f8c106ffe878ea396f71ab827f27 100644 (file)
@@ -262,11 +262,12 @@ and classes for traversing abstract syntax trees:
 .. function:: dump(node, annotate_fields=True, include_attributes=False)
 
    Return a formatted dump of the tree in *node*.  This is mainly useful for
-   debugging purposes.  The returned string will show the names and the values
-   for fields.  This makes the code impossible to evaluate, so if evaluation is
-   wanted *annotate_fields* must be set to ``False``.  Attributes such as line
+   debugging purposes.  If *annotate_fields* is true (by default),
+   the returned string will show the names and the values for fields.
+   If *annotate_fields* is false, the result string will be more compact by
+   omitting unambiguous field names.  Attributes such as line
    numbers and column offsets are not dumped by default.  If this is wanted,
-   *include_attributes* can be set to ``True``.
+   *include_attributes* can be set to true.
 
 .. seealso::
 
index bfe346bba8e3d75217914f0a1b2930f6b85d8c65..1bc6b38da8d5aefaec1ab220d410952e2dfd630c 100644 (file)
@@ -93,26 +93,35 @@ def literal_eval(node_or_string):
 
 def dump(node, annotate_fields=True, include_attributes=False):
     """
-    Return a formatted dump of the tree in *node*.  This is mainly useful for
-    debugging purposes.  The returned string will show the names and the values
-    for fields.  This makes the code impossible to evaluate, so if evaluation is
-    wanted *annotate_fields* must be set to False.  Attributes such as line
+    Return a formatted dump of the tree in node.  This is mainly useful for
+    debugging purposes.  If annotate_fields is true (by default),
+    the returned string will show the names and the values for fields.
+    If annotate_fields is false, the result string will be more compact by
+    omitting unambiguous field names.  Attributes such as line
     numbers and column offsets are not dumped by default.  If this is wanted,
-    *include_attributes* can be set to True.
+    include_attributes can be set to true.
     """
     def _format(node):
         if isinstance(node, AST):
-            fields = [(a, _format(b)) for a, b in iter_fields(node)]
-            rv = '%s(%s' % (node.__class__.__name__, ', '.join(
-                ('%s=%s' % field for field in fields)
-                if annotate_fields else
-                (b for a, b in fields)
-            ))
+            args = []
+            keywords = annotate_fields
+            for field in node._fields:
+                try:
+                    value = getattr(node, field)
+                except AttributeError:
+                    keywords = True
+                else:
+                    if keywords:
+                        args.append('%s=%s' % (field, _format(value)))
+                    else:
+                        args.append(_format(value))
             if include_attributes and node._attributes:
-                rv += fields and ', ' or ' '
-                rv += ', '.join('%s=%s' % (a, _format(getattr(node, a)))
-                                for a in node._attributes)
-            return rv + ')'
+                for a in node._attributes:
+                    try:
+                        args.append('%s=%s' % (a, _format(getattr(node, a))))
+                    except AttributeError:
+                        pass
+            return '%s(%s)' % (node.__class__.__name__, ', '.join(args))
         elif isinstance(node, list):
             return '[%s]' % ', '.join(_format(x) for x in node)
         return repr(node)
index 7614d40e3e5c78f38a15f6a5efbf072d9207a406..2d0b5b24ee7dc2a2e3a37a6cf6a395ea34fc7e43 100644 (file)
@@ -462,6 +462,35 @@ class ASTHelpers_Test(unittest.TestCase):
             "lineno=1, col_offset=0), lineno=1, col_offset=0)])"
         )
 
+    def test_dump_incomplete(self):
+        node = ast.Raise(lineno=3, col_offset=4)
+        self.assertEqual(ast.dump(node),
+            "Raise()"
+        )
+        self.assertEqual(ast.dump(node, include_attributes=True),
+            "Raise(lineno=3, col_offset=4)"
+        )
+        node = ast.Raise(exc=ast.Name(id='e', ctx=ast.Load()), lineno=3, col_offset=4)
+        self.assertEqual(ast.dump(node),
+            "Raise(exc=Name(id='e', ctx=Load()))"
+        )
+        self.assertEqual(ast.dump(node, annotate_fields=False),
+            "Raise(Name('e', Load()))"
+        )
+        self.assertEqual(ast.dump(node, include_attributes=True),
+            "Raise(exc=Name(id='e', ctx=Load()), lineno=3, col_offset=4)"
+        )
+        self.assertEqual(ast.dump(node, annotate_fields=False, include_attributes=True),
+            "Raise(Name('e', Load()), lineno=3, col_offset=4)"
+        )
+        node = ast.Raise(cause=ast.Name(id='e', ctx=ast.Load()))
+        self.assertEqual(ast.dump(node),
+            "Raise(cause=Name(id='e', ctx=Load()))"
+        )
+        self.assertEqual(ast.dump(node, annotate_fields=False),
+            "Raise(cause=Name('e', Load()))"
+        )
+
     def test_copy_location(self):
         src = ast.parse('1 + 1', mode='eval')
         src.body.right = ast.copy_location(ast.Num(2), src.body.right)
diff --git a/Misc/NEWS.d/next/Library/2019-08-26-10-45-51.bpo-37950.-K1IKT.rst b/Misc/NEWS.d/next/Library/2019-08-26-10-45-51.bpo-37950.-K1IKT.rst
new file mode 100644 (file)
index 0000000..ded80d3
--- /dev/null
@@ -0,0 +1 @@
+Fix :func:`ast.dump` when call with incompletely initialized node.