diff options
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | pylint/checkers/classes.py | 38 | ||||
-rw-r--r-- | pylint/test/functional/non_iterator_returned.py | 42 | ||||
-rw-r--r-- | pylint/test/functional/non_iterator_returned.txt | 8 |
4 files changed, 74 insertions, 18 deletions
@@ -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 |