summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2015-10-20 18:35:18 +0300
committerClaudiu Popa <pcmanticore@gmail.com>2015-10-20 18:35:18 +0300
commit050fc46ebc5d4ddfc50b7553f4af336172431ac5 (patch)
tree5c03800fb87f72875c622f732b90fb27d538f501
parent86f5425d304ced106cab4a7543bea3c2d605ba5d (diff)
downloadpylint-050fc46ebc5d4ddfc50b7553f4af336172431ac5.tar.gz
non-iterator-returned can detect classes with iterator-metaclasses.
Closes issue #679.
-rw-r--r--ChangeLog4
-rw-r--r--pylint/checkers/classes.py38
-rw-r--r--pylint/test/functional/non_iterator_returned.py42
-rw-r--r--pylint/test/functional/non_iterator_returned.txt8
4 files changed, 74 insertions, 18 deletions
diff --git a/ChangeLog b/ChangeLog
index 957ed56..8d6c9a7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,10 @@ ChangeLog for Pylint
--------------------
--
+
+ * non-iterator-returned can detect classes with iterator-metaclasses.
+ Closes issue #679.
+
* Add a new error, 'unsupported-membership-test', emitted when value
to the right of the 'in' operator doesn't support membership test
protocol (i.e. doesn't define __contains__/__iter__/__getitem__)
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index c39b971..86ccb19 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -1006,23 +1006,39 @@ class SpecialMethodsChecker(BaseChecker):
args=(node.name, expected_params, current_params, verb),
node=node)
+ @staticmethod
+ def _is_iterator(node):
+ if node is astroid.YES:
+ # Just ignore YES objects.
+ return True
+ if isinstance(node, Generator):
+ # Generators can be itered.
+ return True
+
+ if isinstance(node, astroid.Instance):
+ try:
+ node.local_attr(NEXT_METHOD)
+ return True
+ except astroid.NotFoundError:
+ pass
+ elif isinstance(node, astroid.ClassDef):
+ metaclass = node.metaclass()
+ if metaclass and isinstance(metaclass, astroid.ClassDef):
+ try:
+ metaclass.local_attr(NEXT_METHOD)
+ return True
+ except astroid.NotFoundError:
+ pass
+ return False
+
def _check_iter(self, node):
try:
infered = node.infer_call_result(node)
except astroid.InferenceError:
return
- for infered_node in infered:
- if (infered_node is astroid.YES
- or isinstance(infered_node, Generator)):
- continue
- if isinstance(infered_node, astroid.Instance):
- try:
- infered_node.local_attr(NEXT_METHOD)
- except astroid.NotFoundError:
- self.add_message('non-iterator-returned',
- node=node)
- break
+ if not all(map(self._is_iterator, infered)):
+ self.add_message('non-iterator-returned', node=node)
def _ancestors_to_call(klass_node, method='__init__'):
diff --git a/pylint/test/functional/non_iterator_returned.py b/pylint/test/functional/non_iterator_returned.py
index 845500b..804ceee 100644
--- a/pylint/test/functional/non_iterator_returned.py
+++ b/pylint/test/functional/non_iterator_returned.py
@@ -1,8 +1,8 @@
"""Check non-iterators returned by __iter__ """
-# pylint: disable=too-few-public-methods
+# pylint: disable=too-few-public-methods, missing-docstring, no-self-use
-__revision__ = 0
+import six
class FirstGoodIterator(object):
""" yields in iterator. """
@@ -17,11 +17,11 @@ class SecondGoodIterator(object):
def __iter__(self):
return self
- def __next__(self): # pylint: disable=no-self-use
+ def __next__(self):
""" Infinite iterator, but still an iterator """
return 1
- def next(self): # pylint: disable=no-self-use
+ def next(self):
"""Same as __next__, but for Python 2."""
return 1
@@ -37,6 +37,26 @@ class FourthGoodIterator(object):
def __iter__(self):
return iter(range(10))
+
+class IteratorMetaclass(type):
+ def __next__(cls):
+ return 1
+
+ def next(cls):
+ return 2
+
+
+@six.add_metaclass(IteratorMetaclass)
+class IteratorClass(object):
+ """Iterable through the metaclass."""
+
+
+class FifthGoodIterator(object):
+ """__iter__ returns a class which uses an iterator-metaclass."""
+ def __iter__(self):
+ return IteratorClass
+
+
class FirstBadIterator(object):
""" __iter__ returns a list """
@@ -54,3 +74,17 @@ class ThirdBadIterator(object):
def __iter__(self): # [non-iterator-returned]
return SecondBadIterator()
+
+class FourthBadIterator(object):
+ """__iter__ returns a class."""
+
+ def __iter__(self): # [non-iterator-returned]
+ return ThirdBadIterator
+
+class FifthBadIterator(object):
+ """All branches should return an iterator."""
+
+ def __iter__(self): # [non-iterator-returned]
+ if self:
+ return 1
+ return SecondGoodIterator()
diff --git a/pylint/test/functional/non_iterator_returned.txt b/pylint/test/functional/non_iterator_returned.txt
index f2881eb..fe3db10 100644
--- a/pylint/test/functional/non_iterator_returned.txt
+++ b/pylint/test/functional/non_iterator_returned.txt
@@ -1,3 +1,5 @@
-non-iterator-returned:43:FirstBadIterator.__iter__:__iter__ returns non-iterator
-non-iterator-returned:49:SecondBadIterator.__iter__:__iter__ returns non-iterator
-non-iterator-returned:55:ThirdBadIterator.__iter__:__iter__ returns non-iterator \ No newline at end of file
+non-iterator-returned:63:FirstBadIterator.__iter__:__iter__ returns non-iterator
+non-iterator-returned:69:SecondBadIterator.__iter__:__iter__ returns non-iterator
+non-iterator-returned:75:ThirdBadIterator.__iter__:__iter__ returns non-iterator
+non-iterator-returned:81:FourthBadIterator.__iter__:__iter__ returns non-iterator
+non-iterator-returned:87:FifthBadIterator.__iter__:__iter__ returns non-iterator \ No newline at end of file