diff options
author | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2014-12-21 17:07:52 +0200 |
---|---|---|
committer | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2014-12-21 17:07:52 +0200 |
commit | fc12599809c8a7397bee2b49c8162b3c60e36db6 (patch) | |
tree | 271674821cebd7d0df96c4056f4e373cc38fae84 /checkers/utils.py | |
parent | 49da69cade967362dff9933929f82e5fe6890df4 (diff) | |
download | pylint-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.py | 59 |
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 |