summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Green <benhgreen@icloud.com>2018-05-15 10:55:12 -0400
committerAshley Whetter <AWhetter@users.noreply.github.com>2018-05-15 07:55:12 -0700
commit5bc4529031350397665629cdafe7172c422ab32f (patch)
tree45e314f0e4fe45368ac23eff28f4945b098b1a25
parentc011c53ba207b40904b43d60f3f1ede328cd6372 (diff)
downloadpylint-git-5bc4529031350397665629cdafe7172c422ab32f.tar.gz
Add check for unhashable dict keys (fixes #586) (#2089)
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--ChangeLog4
-rw-r--r--doc/whatsnew/2.0.rst4
-rw-r--r--pylint/checkers/typecheck.py23
-rw-r--r--pylint/test/functional/unhashable_dict_key.py11
-rw-r--r--pylint/test/functional/unhashable_dict_key.txt3
6 files changed, 45 insertions, 2 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 84531db7d..024eb7e06 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -173,6 +173,8 @@ Order doesn't matter (not that much, at least ;)
* Marianna Polatoglou: minor contribution for wildcard import check
+* Ben Green: contributor
+
* Benjamin Freeman: contributor
* Fureigh: contributor
diff --git a/ChangeLog b/ChangeLog
index 1b404ae4c..833d87739 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,10 @@ Pylint's ChangeLog
What's New in Pylint 2.0?
=========================
+ * Add `unhashable-dict-key` check.
+
+ Closes #586
+
* Don't warn that a global variable is unused if it is defined by an import
Close #1453
diff --git a/doc/whatsnew/2.0.rst b/doc/whatsnew/2.0.rst
index a0313a100..cfa74e55d 100644
--- a/doc/whatsnew/2.0.rst
+++ b/doc/whatsnew/2.0.rst
@@ -115,6 +115,10 @@ New checkers
some_value = some_call()
return locals()
+ * New `unhashable-dict-key` check added to detect dict lookups using
+ unhashable keys such as lists or dicts.
+
+
Other Changes
=============
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index b24f7498c..c7acdb67d 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -288,6 +288,10 @@ MSGS = {
'Emitted whenever we can detect that a class is using, '
'as a metaclass, something which might be invalid for using as '
'a metaclass.'),
+ 'E1140': ("Dict key is unhashable",
+ 'unhashable-dict-key',
+ "Emitted when a dict key is not hashable"
+ "(i.e. doesn't define __hash__ method)"),
'W1113': ('Keyword argument before variable positional arguments list '
'in the definition of %s function',
'keyword-arg-before-vararg',
@@ -1241,13 +1245,28 @@ accessed. Python regular expressions are accepted.'}
if op in ['in', 'not in']:
self._check_membership_test(right)
- @check_messages('unsubscriptable-object', 'unsupported-assignment-operation',
- 'unsupported-delete-operation')
+ @check_messages('unsubscriptable-object',
+ 'unsupported-assignment-operation',
+ 'unsupported-delete-operation',
+ 'unhashable-dict-key')
def visit_subscript(self, node):
supported_protocol = None
if isinstance(node.value, (astroid.ListComp, astroid.DictComp)):
return
+ if isinstance(node.value, astroid.Dict):
+ # Assert dict key is hashable
+ inferred = safe_infer(node.slice.value)
+ if inferred is not None:
+ try:
+ hash_fn = next(inferred.igetattr('__hash__'))
+ except (astroid.InferenceError, TypeError):
+ pass
+ else:
+ if getattr(hash_fn, 'value', True) is None:
+ self.add_message('unhashable-dict-key',
+ node=node.value)
+
if node.ctx == astroid.Load:
supported_protocol = supports_getitem
msg = 'unsubscriptable-object'
diff --git a/pylint/test/functional/unhashable_dict_key.py b/pylint/test/functional/unhashable_dict_key.py
new file mode 100644
index 000000000..54b3065a5
--- /dev/null
+++ b/pylint/test/functional/unhashable_dict_key.py
@@ -0,0 +1,11 @@
+# pylint: disable=missing-docstring,expression-not-assigned,too-few-public-methods,pointless-statement
+
+
+class Unhashable(object):
+ __hash__ = list.__hash__
+
+{}[[1, 2, 3]] # [unhashable-dict-key]
+{}[{}] # [unhashable-dict-key]
+{}[Unhashable()] # [unhashable-dict-key]
+{'foo': 'bar'}['foo']
+{'foo': 'bar'}[42]
diff --git a/pylint/test/functional/unhashable_dict_key.txt b/pylint/test/functional/unhashable_dict_key.txt
new file mode 100644
index 000000000..77c3b9287
--- /dev/null
+++ b/pylint/test/functional/unhashable_dict_key.txt
@@ -0,0 +1,3 @@
+unhashable-dict-key:7::Dict key is unhashable
+unhashable-dict-key:8::Dict key is unhashable
+unhashable-dict-key:9::Dict key is unhashable