bpo-37950: Fix ast.dump() when call with incompletely initialized node. (GH-15510)

(cherry picked from commit e64f948e76)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
Miss Islington (bot) 2019-08-29 00:04:44 -07:00 committed by GitHub
parent be310e03d0
commit d3d2650cf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 59 additions and 19 deletions

View File

@ -262,11 +262,12 @@ and classes for traversing abstract syntax trees:
.. function:: dump(node, annotate_fields=True, include_attributes=False) .. function:: dump(node, annotate_fields=True, include_attributes=False)
Return a formatted dump of the tree in *node*. This is mainly useful for 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 debugging purposes. If *annotate_fields* is true (by default),
for fields. This makes the code impossible to evaluate, so if evaluation is the returned string will show the names and the values for fields.
wanted *annotate_fields* must be set to ``False``. Attributes such as line 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, 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:: .. seealso::

View File

@ -93,26 +93,35 @@ def literal_eval(node_or_string):
def dump(node, annotate_fields=True, include_attributes=False): def dump(node, annotate_fields=True, include_attributes=False):
""" """
Return a formatted dump of the tree in *node*. This is mainly useful for 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 debugging purposes. If annotate_fields is true (by default),
for fields. This makes the code impossible to evaluate, so if evaluation is the returned string will show the names and the values for fields.
wanted *annotate_fields* must be set to False. Attributes such as line 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, 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): def _format(node):
if isinstance(node, AST): if isinstance(node, AST):
fields = [(a, _format(b)) for a, b in iter_fields(node)] args = []
rv = '%s(%s' % (node.__class__.__name__, ', '.join( keywords = annotate_fields
('%s=%s' % field for field in fields) for field in node._fields:
if annotate_fields else try:
(b for a, b in fields) 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: if include_attributes and node._attributes:
rv += fields and ', ' or ' ' for a in node._attributes:
rv += ', '.join('%s=%s' % (a, _format(getattr(node, a))) try:
for a in node._attributes) args.append('%s=%s' % (a, _format(getattr(node, a))))
return rv + ')' except AttributeError:
pass
return '%s(%s)' % (node.__class__.__name__, ', '.join(args))
elif isinstance(node, list): elif isinstance(node, list):
return '[%s]' % ', '.join(_format(x) for x in node) return '[%s]' % ', '.join(_format(x) for x in node)
return repr(node) return repr(node)

View File

@ -462,6 +462,35 @@ class ASTHelpers_Test(unittest.TestCase):
"lineno=1, col_offset=0), lineno=1, col_offset=0)])" "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): def test_copy_location(self):
src = ast.parse('1 + 1', mode='eval') src = ast.parse('1 + 1', mode='eval')
src.body.right = ast.copy_location(ast.Num(2), src.body.right) src.body.right = ast.copy_location(ast.Num(2), src.body.right)

View File

@ -0,0 +1 @@
Fix :func:`ast.dump` when call with incompletely initialized node.