summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryce Guinta <bryce.paul.guinta@gmail.com>2018-01-20 10:10:29 -0700
committerBryce Guinta <bryce.paul.guinta@gmail.com>2018-03-02 21:59:08 -0700
commitb604b267f4876298d142f165713d6be1bd77790e (patch)
tree1d396f094a4c4bd05e02eaa23cb38dd72b7de800
parent9b5aa979effadfe55be602f7e157368316a373be (diff)
downloadastroid-git-b604b267f4876298d142f165713d6be1bd77790e.tar.gz
Fix duplicate values in inferred dictionary node from dictionary unpacking
-rw-r--r--ChangeLog6
-rw-r--r--astroid/inference.py28
-rw-r--r--astroid/tests/.unittest_inference.py.swpbin176128 -> 0 bytes
-rw-r--r--astroid/tests/unittest_inference.py24
4 files changed, 56 insertions, 2 deletions
diff --git a/ChangeLog b/ChangeLog
index 28f5c3af..33e09594 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -50,6 +50,12 @@ Change log for the astroid package (used to be astng)
Close #98
+ * Stop duplicate nodes with the same key values
+ from appearing in dictionaries from dictionary
+ unpacking
+
+ Close PyCQA/pylint#1843
+
2017-12-15 -- 1.6.0
diff --git a/astroid/inference.py b/astroid/inference.py
index 5c83ed5e..37b1b2bb 100644
--- a/astroid/inference.py
+++ b/astroid/inference.py
@@ -89,6 +89,29 @@ def infer_map(self, context=None):
yield new_seq
+def _update_with_replacement(lhs_dict, rhs_dict):
+ """Delete nodes that equate to duplicate keys
+
+ Since an astroid node doesn't 'equal' another node with the same value,
+ this function uses the as_string method to make sure duplicate keys
+ don't get through
+
+ Note that both the key and the value are astroid nodes
+
+ Fixes issue with DictUnpack causing duplicte keys
+ in inferred Dict items
+
+ :param dict(nodes.NodeNG, nodes.NodeNG) lhs_dict: Dictionary to 'merge' nodes into
+ :param dict(nodes.NodeNG, nodes.NodeNG) rhs_dict: Dictionary with nodes to pull from
+ :return dict(nodes.NodeNG, nodes.NodeNG): merged dictionary of nodes
+ """
+ combined_dict = itertools.chain(lhs_dict.items(), rhs_dict.items())
+ # Overwrite keys which have the same string values
+ string_map = {key.as_string(): (key, value) for key, value in combined_dict}
+ # Return to dictionary
+ return dict(string_map.values())
+
+
def _infer_map(node, context):
"""Infer all values based on Dict.items"""
values = {}
@@ -100,14 +123,15 @@ def _infer_map(node, context):
if not isinstance(double_starred, nodes.Dict):
raise exceptions.InferenceError(node=node,
context=context)
- values.update(_infer_map(double_starred, context))
+ unpack_items = _infer_map(double_starred, context)
+ values = _update_with_replacement(values, unpack_items)
else:
key = helpers.safe_infer(name, context=context)
value = helpers.safe_infer(value, context=context)
if any(elem in (None, util.Uninferable) for elem in (key, value)):
raise exceptions.InferenceError(node=node,
context=context)
- values[key] = value
+ values = _update_with_replacement(values, {key: value})
return values
diff --git a/astroid/tests/.unittest_inference.py.swp b/astroid/tests/.unittest_inference.py.swp
deleted file mode 100644
index 4274c129..00000000
--- a/astroid/tests/.unittest_inference.py.swp
+++ /dev/null
Binary files differ
diff --git a/astroid/tests/unittest_inference.py b/astroid/tests/unittest_inference.py
index f50fa7ec..98a1b284 100644
--- a/astroid/tests/unittest_inference.py
+++ b/astroid/tests/unittest_inference.py
@@ -1925,6 +1925,30 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
node = extract_node(code)
self.assertInferDict(node, expected_value)
+ @test_utils.require_version('3.5')
+ def test_dict_inference_unpack_repeated_key(self):
+ """Make sure astroid does not infer repeated keys in a dictionary
+
+ Regression test for https://github.com/PyCQA/pylint/issues/1843
+ """
+ code = """
+ base = {'data': 0}
+ new = {**base, 'data': 1} #@
+ new2 = {'data': 1, **base} #@ # Make sure overwrite works
+ a = 'd' + 'ata'
+ b3 = {**base, a: 3} #@ Make sure keys are properly inferred
+ b4 = {a: 3, **base} #@
+ """
+ ast = extract_node(code)
+ final_values = (
+ "{'data': 1}",
+ "{'data': 0}",
+ "{'data': 3}",
+ "{'data': 0}",
+ )
+ for node, final_value in zip(ast, final_values):
+ assert node.targets[0].inferred()[0].as_string() == final_value
+
def test_dict_invalid_args(self):
invalid_values = [
'dict(*1)',