summaryrefslogtreecommitdiff
path: root/checkers/utils.py
diff options
context:
space:
mode:
authorClaudiu Popa <cpopa@cloudbasesolutions.com>2014-12-21 17:07:52 +0200
committerClaudiu Popa <cpopa@cloudbasesolutions.com>2014-12-21 17:07:52 +0200
commitfc12599809c8a7397bee2b49c8162b3c60e36db6 (patch)
tree271674821cebd7d0df96c4056f4e373cc38fae84 /checkers/utils.py
parent49da69cade967362dff9933929f82e5fe6890df4 (diff)
downloadpylint-fc12599809c8a7397bee2b49c8162b3c60e36db6.tar.gz
Use a mro traversal for finding abstract methods. Closes issue #415.
This patch adds a new unimplemented_abstract_methods in pylint.checkers.utils, which is used to obtain all the abstract methods which weren't implemented anywhere in the mro of a class. The code works now by traversing the mro, gathering all abstract methods encountered at each step and resolving the implemented ones through either definition or assignment. This disables a couple of false positives on classes with complex hierarchies.
Diffstat (limited to 'checkers/utils.py')
-rw-r--r--checkers/utils.py59
1 files changed, 59 insertions, 0 deletions
diff --git a/checkers/utils.py b/checkers/utils.py
index f3a7d17..2df59e1 100644
--- a/checkers/utils.py
+++ b/checkers/utils.py
@@ -34,6 +34,8 @@ if not PY3K:
EXCEPTIONS_MODULE = "exceptions"
else:
EXCEPTIONS_MODULE = "builtins"
+ABC_METHODS = set(('abc.abstractproperty', 'abc.abstractmethod',
+ 'abc.abstractclassmethod', 'abc.abstractstaticmethod'))
class NoSuchArgumentError(Exception):
@@ -499,3 +501,60 @@ def decorated_with_property(node):
return True
except astroid.InferenceError:
pass
+
+
+def decorated_with_abc(func):
+ """Determine if the `func` node is decorated with `abc` decorators."""
+ if func.decorators:
+ for node in func.decorators.nodes:
+ try:
+ infered = next(node.infer())
+ except astroid.InferenceError:
+ continue
+ if infered and infered.qname() in ABC_METHODS:
+ return True
+
+
+def unimplemented_abstract_methods(node, is_abstract_cb=decorated_with_abc):
+ """
+ Get the unimplemented abstract methods for the given *node*.
+
+ A method can be considered abstract if the callback *is_abstract_cb*
+ returns a ``True`` value. The check defaults to verifying that
+ a method is decorated with abstract methods.
+ The function will work only for new-style classes. For old-style
+ classes, it will simply return an empty dictionary.
+ For the rest of them, it will return a dictionary of abstract method
+ names and their inferred objects.
+ """
+ visited = {}
+ try:
+ mro = reversed(node.mro())
+ except NotImplementedError:
+ # Old style class, it will not have a mro.
+ return {}
+ 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:
+ del visited[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 = is_abstract_cb(infered)
+ if abstract:
+ visited[obj.name] = infered
+ elif not abstract and obj.name in visited:
+ del visited[obj.name]
+ return visited