summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <cpopa@cloudbasesolutions.com>2014-12-16 14:16:40 +0200
committerClaudiu Popa <cpopa@cloudbasesolutions.com>2014-12-16 14:16:40 +0200
commitc1a622340037b27533085ece42478e85e2e7e395 (patch)
treed9b92ffdc0e54761469a4551d6f9ca248eb865f4
parentfc0b272b5f520cdf0d54eb628dbf4cc860bc629f (diff)
downloadpylint-c1a622340037b27533085ece42478e85e2e7e395.tar.gz
Proper abstract method lookup while checking for abstract-class-instantiated.
This depends on the new Class.mro method, which will be added in astroid 1.3.3. Closes issue #401.
-rw-r--r--ChangeLog3
-rw-r--r--checkers/base.py33
-rw-r--r--test/functional/abstract_class_instantiated_py3.py50
-rw-r--r--test/functional/abstract_class_instantiated_py3.txt7
4 files changed, 88 insertions, 5 deletions
diff --git a/ChangeLog b/ChangeLog
index d25a775..6e13e04 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -11,6 +11,9 @@ ChangeLog for Pylint
* Warn about using the input() or round() built-ins for Python 3.
Closes issue #411.
+ * Proper abstract method lookup while checking for
+ abstract-class-instantiated. Closes issue #401.
+
2014-11-23 -- 1.4.0
diff --git a/checkers/base.py b/checkers/base.py
index 17e6713..60bc096 100644
--- a/checkers/base.py
+++ b/checkers/base.py
@@ -197,8 +197,37 @@ def _has_abstract_methods(node):
Determine if the given `node` has
abstract methods, defined with `abc` module.
"""
- return any(_decorated_with_abc(meth)
- for meth in node.methods())
+ visited = set()
+ try:
+ mro = reversed(node.mro())
+ except NotImplementedError:
+ # Old style class, it will not have a mro.
+ return False
+ for ancestor in mro:
+ for obj in ancestor.values():
+ infered = obj
+ if isinstance(obj, astroid.AssName):
+ infered = safe_infer(obj)
+ if not infered:
+ continue
+ if not isinstance(infered, astroid.Function):
+ if obj.name in visited:
+ visited.remove(obj.name)
+ if isinstance(infered, astroid.Function):
+ # It's critical to use the original name,
+ # since after inferring, an object can be something
+ # else than expected, as in the case of the
+ # following assignment.
+ #
+ # class A:
+ # def keys(self): pass
+ # __iter__ = keys
+ abstract = _decorated_with_abc(infered)
+ if abstract:
+ visited.add(obj.name)
+ elif not abstract and obj.name in visited:
+ visited.remove(obj.name)
+ return len(visited) > 0
def report_by_type_stats(sect, stats, old_stats):
"""make a report of
diff --git a/test/functional/abstract_class_instantiated_py3.py b/test/functional/abstract_class_instantiated_py3.py
index 146666e..8d94733 100644
--- a/test/functional/abstract_class_instantiated_py3.py
+++ b/test/functional/abstract_class_instantiated_py3.py
@@ -10,6 +10,7 @@ abstract methods.
__revision__ = 0
import abc
+import weakref
class GoodClass(object, metaclass=abc.ABCMeta):
pass
@@ -38,11 +39,60 @@ class ThirdBadClass(SecondBadClass):
pass
+class Structure(object, metaclass=abc.ABCMeta):
+ @abc.abstractmethod
+ def __iter__(self):
+ pass
+ @abc.abstractmethod
+ def __len__(self):
+ pass
+ @abc.abstractmethod
+ def __contains__(self):
+ pass
+ @abc.abstractmethod
+ def __hash__(self):
+ pass
+
+class Container(Structure):
+ def __contains__(self):
+ pass
+
+class Sizable(Structure):
+ def __len__(self):
+ pass
+
+class Hashable(Structure):
+ __hash__ = 42
+
+
+class Iterator(Structure):
+ def keys(self): # pylint: disable=no-self-use
+ return iter([1, 2, 3])
+
+ __iter__ = keys
+
+class AbstractSizable(Structure):
+ @abc.abstractmethod
+ def length(self):
+ pass
+ __len__ = length
+
+class NoMroAbstractMethods(Container, Iterator, Sizable, Hashable):
+ pass
+
+class BadMroAbstractMethods(Container, Iterator, AbstractSizable):
+ pass
+
def main():
""" do nothing """
GoodClass()
SecondGoodClass()
ThirdGoodClass()
+ weakref.WeakKeyDictionary()
+ weakref.WeakValueDictionary()
+ NoMroAbstractMethods()
+
+ BadMroAbstractMethods() # [abstract-class-instantiated]
BadClass() # [abstract-class-instantiated]
SecondBadClass() # [abstract-class-instantiated]
ThirdBadClass() # [abstract-class-instantiated]
diff --git a/test/functional/abstract_class_instantiated_py3.txt b/test/functional/abstract_class_instantiated_py3.txt
index 3c4f0c2..db5bef6 100644
--- a/test/functional/abstract_class_instantiated_py3.txt
+++ b/test/functional/abstract_class_instantiated_py3.txt
@@ -1,3 +1,4 @@
-abstract-class-instantiated:46:main:Abstract class with abstract methods instantiated
-abstract-class-instantiated:47:main:Abstract class with abstract methods instantiated
-abstract-class-instantiated:48:main:Abstract class with abstract methods instantiated \ No newline at end of file
+abstract-class-instantiated:95:main:Abstract class with abstract methods instantiated
+abstract-class-instantiated:96:main:Abstract class with abstract methods instantiated
+abstract-class-instantiated:97:main:Abstract class with abstract methods instantiated
+abstract-class-instantiated:98:main:Abstract class with abstract methods instantiated