From e64f948e762a6b9fd02e2902ccf42438df6fcb61 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 29 Aug 2019 09:30:23 +0300 Subject: bpo-37950: Fix ast.dump() when call with incompletely initialized node. (GH-15510) --- Doc/library/ast.rst | 9 ++--- Lib/ast.py | 39 +++++++++++++--------- Lib/test/test_ast.py | 29 ++++++++++++++++ .../2019-08-26-10-45-51.bpo-37950.-K1IKT.rst | 1 + 4 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-08-26-10-45-51.bpo-37950.-K1IKT.rst diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 3d2c420..92bf891 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -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:: diff --git a/Lib/ast.py b/Lib/ast.py index 1e639d1..5ab023f 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -98,26 +98,35 @@ def literal_eval(node_or_string): 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) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 3d12397..07bbb4c 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -645,6 +645,35 @@ class ASTHelpers_Test(unittest.TestCase): "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) diff --git a/Misc/NEWS.d/next/Library/2019-08-26-10-45-51.bpo-37950.-K1IKT.rst b/Misc/NEWS.d/next/Library/2019-08-26-10-45-51.bpo-37950.-K1IKT.rst new file mode 100644 index 0000000..ded80d3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-08-26-10-45-51.bpo-37950.-K1IKT.rst @@ -0,0 +1 @@ +Fix :func:`ast.dump` when call with incompletely initialized node. -- cgit v0.12