Commit e64f948e authored by Serhiy Storchaka's avatar Serhiy Storchaka Committed by GitHub

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

parent b235a1b4
...@@ -322,11 +322,12 @@ and classes for traversing abstract syntax trees: ...@@ -322,11 +322,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::
......
...@@ -98,26 +98,35 @@ def literal_eval(node_or_string): ...@@ -98,26 +98,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)
......
...@@ -645,6 +645,35 @@ class ASTHelpers_Test(unittest.TestCase): ...@@ -645,6 +645,35 @@ class ASTHelpers_Test(unittest.TestCase):
"lineno=1, col_offset=0, end_lineno=1, end_col_offset=24)], type_ignores=[])" "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): 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)
......
Fix :func:`ast.dump` when call with incompletely initialized node.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment