diff options
author | Claudiu Popa <pcmanticore@gmail.com> | 2015-10-06 01:22:55 +0300 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2015-10-06 01:22:55 +0300 |
commit | 7fe6eb2afac0abb1aae04414c1fb3c69df79ad8b (patch) | |
tree | 1050881bf66e3fed26b3ea5c9a502a4547c2a60d | |
parent | 5f8ddec3d362056d603efbf097f588d1da52c73a (diff) | |
download | astroid-7fe6eb2afac0abb1aae04414c1fb3c69df79ad8b.tar.gz |
Add a new node, DictUnpack, for representing the unpacking of a dict using PEP 448
This is a different approach than what the builtin ast module does,
since it just uses None to represent this kind of operation,
which seems conceptually wrong, due to the fact the AST contains
non-AST nodes. Closes issue #206.
-rw-r--r-- | ChangeLog | 9 | ||||
-rw-r--r-- | astroid/as_string.py | 17 | ||||
-rw-r--r-- | astroid/node_classes.py | 12 | ||||
-rw-r--r-- | astroid/nodes.py | 8 | ||||
-rw-r--r-- | astroid/rebuilder.py | 18 | ||||
-rw-r--r-- | astroid/tests/unittest_python3.py | 16 |
6 files changed, 71 insertions, 9 deletions
@@ -312,6 +312,15 @@ Change log for the astroid package (used to be astng) * Understand the `slice` builtin. Closes issue #184. * Add brain tips for numpy.core, which should fix Pylint's #453. + + * Add a new node, DictUnpack, which is used to represent the unpacking + of a dictionary into another dictionary, using PEP 448 specific syntax + ({1:2, **{2:3}) + + This is a different approach than what the builtin ast module does, + since it just uses None to represent this kind of operation, + which seems conceptually wrong, due to the fact the AST contains + non-AST nodes. Closes issue #206. diff --git a/astroid/as_string.py b/astroid/as_string.py index 20f4936..14ef45f 100644 --- a/astroid/as_string.py +++ b/astroid/as_string.py @@ -159,9 +159,20 @@ class AsStringVisitor(object): def visit_dict(self, node): """return an astroid.Dict node as string""" - return '{%s}' % ', '.join(['%s: %s' % (key.accept(self), - value.accept(self)) - for key, value in node.items]) + return '{%s}' % ', '.join(self._visit_dict(node)) + + def _visit_dict(self, node): + for key, value in node.items: + key = key.accept(self) + value = value.accept(self) + if key == '**': + # It can only be a DictUnpack node. + yield key + value + else: + yield '%s: %s' % (key, value) + + def visit_dictunpack(self, node): + return '**' def visit_dictcomp(self, node): """return an astroid.DictComp node as string""" diff --git a/astroid/node_classes.py b/astroid/node_classes.py index f1c5592..af1cb48 100644 --- a/astroid/node_classes.py +++ b/astroid/node_classes.py @@ -1337,6 +1337,18 @@ class YieldFrom(Yield): """ Class representing a YieldFrom node. """ +class DictUnpack(bases.NodeNG): + """Represents the unpacking of dicts into dicts using PEP 448. + + Its value is the dictionary which is unpacked. + """ + + _astroid_fields = ('value', ) + value = None + + def postinit(self, value=None): + self.value = value + # constants ############################################################## CONST_CLS = { diff --git a/astroid/nodes.py b/astroid/nodes.py index 67522e0..2fd6cb6 100644 --- a/astroid/nodes.py +++ b/astroid/nodes.py @@ -47,7 +47,9 @@ from astroid.node_classes import ( const_factory, AsyncFor, Await, AsyncWith, # Backwards-compatibility aliases - Backquote, Discard, AssName, AssAttr, Getattr, CallFunc, From + Backquote, Discard, AssName, AssAttr, Getattr, CallFunc, From, + # Node not present in the builtin ast module. + DictUnpack, ) from astroid.scoped_nodes import ( Module, GeneratorExp, Lambda, DictComp, @@ -66,7 +68,7 @@ ALL_NODE_CLASSES = ( Repr, BinOp, BoolOp, Break, Call, ClassDef, Compare, Comprehension, Const, Continue, Decorators, DelAttr, DelName, Delete, - Dict, DictComp, Expr, + Dict, DictComp, DictUnpack, Expr, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, For, ImportFrom, FunctionDef, Attribute, GeneratorExp, Global, @@ -81,5 +83,5 @@ ALL_NODE_CLASSES = ( TryExcept, TryFinally, Tuple, UnaryOp, While, With, - Yield, YieldFrom + Yield, YieldFrom, ) diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 7ea9cf2..8c63883 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -384,12 +384,24 @@ class TreeRebuilder(object): for child in node.targets]) return newnode + def _visit_dict_items(self, node, parent, newnode, assign_ctx): + for key, value in zip(node.keys, node.values): + rebuilt_value = self.visit(value, newnode, assign_ctx) + if not key: + # Python 3.5 and extended unpacking + rebuilt_key = nodes.DictUnpack(rebuilt_value.lineno, + rebuilt_value.col_offset, + parent) + rebuilt_key.postinit(rebuilt_value) + else: + rebuilt_key = self.visit(key, newnode, assign_ctx) + yield rebuilt_key, rebuilt_value + def visit_dict(self, node, parent, assign_ctx=None): """visit a Dict node by returning a fresh instance of it""" newnode = nodes.Dict(node.lineno, node.col_offset, parent) - newnode.postinit([(self.visit(key, newnode, assign_ctx), - self.visit(value, newnode, assign_ctx)) - for key, value in zip(node.keys, node.values)]) + items = list(self._visit_dict_items(node, parent, newnode, assign_ctx)) + newnode.postinit(items) return newnode def visit_dictcomp(self, node, parent, assign_ctx=None): diff --git a/astroid/tests/unittest_python3.py b/astroid/tests/unittest_python3.py index f84acd5..e89fd1e 100644 --- a/astroid/tests/unittest_python3.py +++ b/astroid/tests/unittest_python3.py @@ -18,6 +18,7 @@ from textwrap import dedent import unittest +from astroid import nodes from astroid.node_classes import Assign, Expr, YieldFrom, Name, Const from astroid.builder import AstroidBuilder from astroid.scoped_nodes import ClassDef, FunctionDef @@ -225,6 +226,21 @@ class Python3TC(unittest.TestCase): func = extract_node(code) self.assertEqual(func.as_string(), code) + @require_version('3.5') + def test_unpacking_in_dicts(self): + code = "{'x': 1, **{'y': 2}}" + node = extract_node(code) + self.assertEqual(node.as_string(), code) + keys = [key for (key, _) in node.items] + self.assertIsInstance(keys[0], nodes.Const) + self.assertIsInstance(keys[1], nodes.DictUnpack) + + @require_version('3.5') + def test_nested_unpacking_in_dicts(self): + code = "{'x': 1, **{'y': 2, **{'z': 3}}}" + node = extract_node(code) + self.assertEqual(node.as_string(), code) + if __name__ == '__main__': unittest.main() |