summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorŁukasz Rogalski <rogalski.91@gmail.com>2016-03-10 21:47:07 +0100
committerŁukasz Rogalski <rogalski.91@gmail.com>2016-03-14 21:48:42 +0100
commit74512f1b650c3cff88ce135c87baa06767179da0 (patch)
tree26cf34d8a50682ec50b8a457c2948348af209921
parent9f128369b94df988c9eb0bc67184ab90fe535553 (diff)
downloadpylint-git-74512f1b650c3cff88ce135c87baa06767179da0.tar.gz
New check: invalid-length-returned
Implementation of issue #557.
-rw-r--r--pylint/checkers/classes.py19
-rw-r--r--pylint/test/functional/invalid_length_returned.py58
-rw-r--r--pylint/test/functional/invalid_length_returned.txt3
3 files changed, 79 insertions, 1 deletions
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index 95cd5f4d3..fd4c4b252 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -995,16 +995,22 @@ class SpecialMethodsChecker(BaseChecker):
'invalid number of parameters. If it has too few or '
'too many, it might not work at all.',
{'old_names': [('E0235', 'bad-context-manager')]}),
+ 'E0303': ('__len__ does not return non-negative integer',
+ 'invalid-length-returned',
+ 'Used when an __len__ method returns something which is not a '
+ 'non-negative integer', {}),
}
priority = -2
@check_messages('unexpected-special-method-signature',
- 'non-iterator-returned')
+ 'non-iterator-returned', 'invalid-length-returned')
def visit_functiondef(self, node):
if not node.is_method():
return
if node.name == '__iter__':
self._check_iter(node)
+ if node.name == '__len__':
+ self._check_len(node)
if node.name in PYMETHODS:
self._check_unexpected_method_signature(node)
@@ -1086,6 +1092,17 @@ class SpecialMethodsChecker(BaseChecker):
if not self._is_iterator(infered):
self.add_message('non-iterator-returned', node=node)
+ def _check_len(self, node):
+ infered = _safe_infer_call_result(node, node)
+ if infered is None or infered is astroid.util.Uninferable:
+ return
+ try:
+ value = infered.value
+ except AttributeError:
+ value = None
+ if not isinstance(value, six.integer_types) or value < 0:
+ self.add_message('invalid-length-returned', node=node)
+
def _ancestors_to_call(klass_node, method='__init__'):
"""return a dictionary where keys are the list of base classes providing
diff --git a/pylint/test/functional/invalid_length_returned.py b/pylint/test/functional/invalid_length_returned.py
new file mode 100644
index 000000000..cdc35639e
--- /dev/null
+++ b/pylint/test/functional/invalid_length_returned.py
@@ -0,0 +1,58 @@
+"""Check invalid value returned by __len__ """
+
+# pylint: disable=too-few-public-methods,missing-docstring,no-self-use,import-error
+import sys
+
+import six
+
+from missing import Missing
+
+
+class FirstGoodLen(object):
+ """__len__ returns <type 'int'>"""
+
+ def __len__(self):
+ return 0
+
+
+class SecondGoodLen(object):
+ """__len__ returns <type 'long'>"""
+
+ def __len__(self):
+ return sys.maxsize + 1
+
+
+class LenMetaclass(type):
+ def __len__(cls):
+ return 1
+
+
+@six.add_metaclass(LenMetaclass)
+class ThirdGoodLen(object):
+ """Length through the metaclass."""
+
+
+class FirstBadLen(object):
+ """ __len__ returns a negative integer """
+
+ def __len__(self): # [invalid-length-returned]
+ return -1
+
+
+class SecondBadLen(object):
+ """ __len__ returns non-int """
+
+ def __len__(self): # [invalid-length-returned]
+ return 3.0
+
+
+class ThirdBadLen(object):
+ """ __len__ returns node which does not have 'value' in AST """
+
+ def __len__(self): # [invalid-length-returned]
+ return lambda: 3
+
+
+class AmbigousLen(object):
+ """ Uninferable return value """
+ __len__ = lambda self: Missing
diff --git a/pylint/test/functional/invalid_length_returned.txt b/pylint/test/functional/invalid_length_returned.txt
new file mode 100644
index 000000000..6febeb612
--- /dev/null
+++ b/pylint/test/functional/invalid_length_returned.txt
@@ -0,0 +1,3 @@
+invalid-length-returned:38:FirstBadLen.__len__:__len__ does not return non-negative integer
+invalid-length-returned:45:SecondBadLen.__len__:__len__ does not return non-negative integer
+invalid-length-returned:52:ThirdBadLen.__len__:__len__ does not return non-negative integer