Skip to content

Commit

Permalink
Merge branch 'master' into check-super-annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
benhgreen authored May 15, 2018
2 parents 7d280b2 + 5bc4529 commit d4dd540
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ What's New in Pylint 2.0?

Close #1923

* 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
Expand Down
4 changes: 4 additions & 0 deletions doc/whatsnew/2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
=============

Expand Down
23 changes: 21 additions & 2 deletions pylint/checkers/typecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ def _missing_member_hint(owner, attrname, distance_threshold, max_choices):
'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',
Expand Down Expand Up @@ -1241,13 +1245,28 @@ def visit_compare(self, node):
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'
Expand Down
11 changes: 11 additions & 0 deletions pylint/test/functional/unhashable_dict_key.py
Original file line number Diff line number Diff line change
@@ -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]
3 changes: 3 additions & 0 deletions pylint/test/functional/unhashable_dict_key.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit d4dd540

Please sign in to comment.