Skip to content

Commit d3d2650

Browse files
bpo-37950: Fix ast.dump() when call with incompletely initialized node. (GH-15510)
(cherry picked from commit e64f948) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent be310e0 commit d3d2650

File tree

4 files changed

+59
-19
lines changed

4 files changed

+59
-19
lines changed

Doc/library/ast.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,11 +262,12 @@ and classes for traversing abstract syntax trees:
262262
.. function:: dump(node, annotate_fields=True, include_attributes=False)
263263

264264
Return a formatted dump of the tree in *node*. This is mainly useful for
265-
debugging purposes. The returned string will show the names and the values
266-
for fields. This makes the code impossible to evaluate, so if evaluation is
267-
wanted *annotate_fields* must be set to ``False``. Attributes such as line
265+
debugging purposes. If *annotate_fields* is true (by default),
266+
the returned string will show the names and the values for fields.
267+
If *annotate_fields* is false, the result string will be more compact by
268+
omitting unambiguous field names. Attributes such as line
268269
numbers and column offsets are not dumped by default. If this is wanted,
269-
*include_attributes* can be set to ``True``.
270+
*include_attributes* can be set to true.
270271

271272
.. seealso::
272273

Lib/ast.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,26 +93,35 @@ def _convert(node):
9393

9494
def dump(node, annotate_fields=True, include_attributes=False):
9595
"""
96-
Return a formatted dump of the tree in *node*. This is mainly useful for
97-
debugging purposes. The returned string will show the names and the values
98-
for fields. This makes the code impossible to evaluate, so if evaluation is
99-
wanted *annotate_fields* must be set to False. Attributes such as line
96+
Return a formatted dump of the tree in node. This is mainly useful for
97+
debugging purposes. If annotate_fields is true (by default),
98+
the returned string will show the names and the values for fields.
99+
If annotate_fields is false, the result string will be more compact by
100+
omitting unambiguous field names. Attributes such as line
100101
numbers and column offsets are not dumped by default. If this is wanted,
101-
*include_attributes* can be set to True.
102+
include_attributes can be set to true.
102103
"""
103104
def _format(node):
104105
if isinstance(node, AST):
105-
fields = [(a, _format(b)) for a, b in iter_fields(node)]
106-
rv = '%s(%s' % (node.__class__.__name__, ', '.join(
107-
('%s=%s' % field for field in fields)
108-
if annotate_fields else
109-
(b for a, b in fields)
110-
))
106+
args = []
107+
keywords = annotate_fields
108+
for field in node._fields:
109+
try:
110+
value = getattr(node, field)
111+
except AttributeError:
112+
keywords = True
113+
else:
114+
if keywords:
115+
args.append('%s=%s' % (field, _format(value)))
116+
else:
117+
args.append(_format(value))
111118
if include_attributes and node._attributes:
112-
rv += fields and ', ' or ' '
113-
rv += ', '.join('%s=%s' % (a, _format(getattr(node, a)))
114-
for a in node._attributes)
115-
return rv + ')'
119+
for a in node._attributes:
120+
try:
121+
args.append('%s=%s' % (a, _format(getattr(node, a))))
122+
except AttributeError:
123+
pass
124+
return '%s(%s)' % (node.__class__.__name__, ', '.join(args))
116125
elif isinstance(node, list):
117126
return '[%s]' % ', '.join(_format(x) for x in node)
118127
return repr(node)

Lib/test/test_ast.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,35 @@ def test_dump(self):
462462
"lineno=1, col_offset=0), lineno=1, col_offset=0)])"
463463
)
464464

465+
def test_dump_incomplete(self):
466+
node = ast.Raise(lineno=3, col_offset=4)
467+
self.assertEqual(ast.dump(node),
468+
"Raise()"
469+
)
470+
self.assertEqual(ast.dump(node, include_attributes=True),
471+
"Raise(lineno=3, col_offset=4)"
472+
)
473+
node = ast.Raise(exc=ast.Name(id='e', ctx=ast.Load()), lineno=3, col_offset=4)
474+
self.assertEqual(ast.dump(node),
475+
"Raise(exc=Name(id='e', ctx=Load()))"
476+
)
477+
self.assertEqual(ast.dump(node, annotate_fields=False),
478+
"Raise(Name('e', Load()))"
479+
)
480+
self.assertEqual(ast.dump(node, include_attributes=True),
481+
"Raise(exc=Name(id='e', ctx=Load()), lineno=3, col_offset=4)"
482+
)
483+
self.assertEqual(ast.dump(node, annotate_fields=False, include_attributes=True),
484+
"Raise(Name('e', Load()), lineno=3, col_offset=4)"
485+
)
486+
node = ast.Raise(cause=ast.Name(id='e', ctx=ast.Load()))
487+
self.assertEqual(ast.dump(node),
488+
"Raise(cause=Name(id='e', ctx=Load()))"
489+
)
490+
self.assertEqual(ast.dump(node, annotate_fields=False),
491+
"Raise(cause=Name('e', Load()))"
492+
)
493+
465494
def test_copy_location(self):
466495
src = ast.parse('1 + 1', mode='eval')
467496
src.body.right = ast.copy_location(ast.Num(2), src.body.right)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :func:`ast.dump` when call with incompletely initialized node.

0 commit comments

Comments
 (0)