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:
parent
be310e03d0
commit
d3d2650cf8
|
@ -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::
|
||||||
|
|
||||||
|
|
39
Lib/ast.py
39
Lib/ast.py
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix :func:`ast.dump` when call with incompletely initialized node.
|
Loading…
Reference in New Issue