summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2015-10-06 01:22:55 +0300
committerClaudiu Popa <pcmanticore@gmail.com>2015-10-06 01:22:55 +0300
commit7fe6eb2afac0abb1aae04414c1fb3c69df79ad8b (patch)
tree1050881bf66e3fed26b3ea5c9a502a4547c2a60d
parent5f8ddec3d362056d603efbf097f588d1da52c73a (diff)
downloadastroid-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--ChangeLog9
-rw-r--r--astroid/as_string.py17
-rw-r--r--astroid/node_classes.py12
-rw-r--r--astroid/nodes.py8
-rw-r--r--astroid/rebuilder.py18
-rw-r--r--astroid/tests/unittest_python3.py16
6 files changed, 71 insertions, 9 deletions
diff --git a/ChangeLog b/ChangeLog
index ed0ea9e..2026199 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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()