Skip to content

Commit

Permalink
bpo-37950: Fix ast.dump() when call with incompletely initialized nod…
Browse files Browse the repository at this point in the history
…e. (GH-15510)
  • Loading branch information
serhiy-storchaka committed Aug 29, 2019
1 parent b235a1b commit e64f948
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 19 deletions.
9 changes: 5 additions & 4 deletions Doc/library/ast.rst
Original file line number Diff line number Diff line change
Expand Up @@ -322,11 +322,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::

Expand Down
39 changes: 24 additions & 15 deletions Lib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,26 +98,35 @@ def _convert(node):

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)
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,35 @@ def test_dump(self):
"lineno=1, col_offset=0, end_lineno=1, end_col_offset=24)], type_ignores=[])"
)

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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix :func:`ast.dump` when call with incompletely initialized node.

0 comments on commit e64f948

Please sign in to comment.