summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSlavfox <slavfoxman@gmail.com>2020-03-24 10:47:17 +0100
committerGitHub <noreply@github.com>2020-03-24 10:47:17 +0100
commit4024949f6caf5eff5f3da7ab2b4c3cf2e296472b (patch)
treece63c74ff3961954656ff68ea081755a03b4a8d0
parent8a89edf3bf1aa00b93cc09507c8a32e71f9dc315 (diff)
downloadpylint-git-4024949f6caf5eff5f3da7ab2b4c3cf2e296472b.tar.gz
Recognize classes that explicitly inherit from `ABC` or define `metaclass=ABCMeta` as abstract (#3446)
Related to #179 and #3098. Tweaks `utils.class_is_abstract` to not depend purely on the presence of abstract methods, and instead also return True for classes that either explicitly inherit from `abc.ABC`, or explicitly define `abc.ABCMeta` as a metaclass. This means that classes like: class Foo(AbstractParent, ABC): ... class Bar(AbstractParent, metaclass=ABCMeta): ... no longer trigger W0223.
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--ChangeLog5
-rw-r--r--pylint/checkers/utils.py12
-rw-r--r--tests/functional/a/abstract_method_py3.py19
-rw-r--r--tests/functional/a/abstract_method_py3.txt32
5 files changed, 54 insertions, 16 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index e05877b1f..488cc7b54 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -373,3 +373,5 @@ contributors:
* Benny Müller: contributor
* Bernie Gray: contributor
+
+* Slavfox: contributor
diff --git a/ChangeLog b/ChangeLog
index 0b5ad6f2b..f6949f797 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -232,6 +232,11 @@ Release date: TBA
* Fixed ``broad_try_clause`` extension to check try/finally statements and to
check for nested statements (e.g., inside of an ``if`` statement).
+* Recognize classes explicitly inheriting from ``abc.ABC`` or having an
+ ``abc.ABCMeta`` metaclass as abstract. This makes them not trigger W0223.
+
+ Closes #3098
+
What's New in Pylint 2.4.4?
===========================
Release date: 2019-11-13
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index dcc617929..0b4f2c952 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -51,6 +51,7 @@ COMP_NODE_TYPES = (
astroid.GeneratorExp,
)
EXCEPTIONS_MODULE = "builtins"
+ABC_MODULES = {"abc", "_py_abc"}
ABC_METHODS = {
"abc.abstractproperty",
"abc.abstractmethod",
@@ -927,6 +928,17 @@ def class_is_abstract(node: astroid.ClassDef) -> bool:
"""return true if the given class node should be considered as an abstract
class
"""
+ # Only check for explicit metaclass=ABCMeta on this specific class
+ meta = node.declared_metaclass()
+ if meta is not None:
+ if meta.name == "ABCMeta" and meta.root().name in ABC_MODULES:
+ return True
+
+ for ancestor in node.ancestors():
+ if ancestor.name == "ABC" and ancestor.root().name in ABC_MODULES:
+ # abc.ABC inheritance
+ return True
+
for method in node.methods():
if method.parent.frame() is node:
if method.is_abstract(pass_is_abstract=False):
diff --git a/tests/functional/a/abstract_method_py3.py b/tests/functional/a/abstract_method_py3.py
index f4e3dd568..8bb491819 100644
--- a/tests/functional/a/abstract_method_py3.py
+++ b/tests/functional/a/abstract_method_py3.py
@@ -5,6 +5,7 @@ from __future__ import print_function
# pylint: disable=too-few-public-methods, useless-object-inheritance
import abc
+
class Abstract(object):
def aaaa(self):
"""should be overridden in concrete class"""
@@ -25,6 +26,24 @@ class AbstractB(Abstract):
"""should be overridden in concrete class"""
raise NotImplementedError()
+class AbstractC(AbstractB, abc.ABC):
+ """
+ Abstract class.
+
+ Should not trigger a warning for unimplemented
+ abstract methods, because of explicit abc.ABC inheritance.
+ """
+
+
+class AbstractD(AbstractB, metaclass=abc.ABCMeta):
+ """
+ Abstract class.
+
+ Should not trigger a warning for unimplemented
+ abstract methods, because of explicit metaclass.
+ """
+
+
class Concrete(Abstract): # [abstract-method]
"""Concrete class"""
diff --git a/tests/functional/a/abstract_method_py3.txt b/tests/functional/a/abstract_method_py3.txt
index 17f8abf4e..73c97d8be 100644
--- a/tests/functional/a/abstract_method_py3.txt
+++ b/tests/functional/a/abstract_method_py3.txt
@@ -1,16 +1,16 @@
-abstract-method:28:Concrete:"Method 'bbbb' is abstract in class 'Abstract' but is not overridden"
-abstract-method:51:Container:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
-abstract-method:51:Container:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
-abstract-method:51:Container:"Method '__len__' is abstract in class 'Structure' but is not overridden"
-abstract-method:57:Sizable:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
-abstract-method:57:Sizable:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
-abstract-method:57:Sizable:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
-abstract-method:63:Hashable:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
-abstract-method:63:Hashable:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
-abstract-method:63:Hashable:"Method '__len__' is abstract in class 'Structure' but is not overridden"
-abstract-method:68:Iterator:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
-abstract-method:68:Iterator:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
-abstract-method:68:Iterator:"Method '__len__' is abstract in class 'Structure' but is not overridden"
-abstract-method:87:BadComplexMro:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
-abstract-method:87:BadComplexMro:"Method '__len__' is abstract in class 'AbstractSizable' but is not overridden"
-abstract-method:87:BadComplexMro:"Method 'length' is abstract in class 'AbstractSizable' but is not overridden"
+abstract-method:47:Concrete:"Method 'bbbb' is abstract in class 'Abstract' but is not overridden"
+abstract-method:70:Container:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:70:Container:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
+abstract-method:70:Container:"Method '__len__' is abstract in class 'Structure' but is not overridden"
+abstract-method:76:Sizable:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
+abstract-method:76:Sizable:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:76:Sizable:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
+abstract-method:82:Hashable:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
+abstract-method:82:Hashable:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
+abstract-method:82:Hashable:"Method '__len__' is abstract in class 'Structure' but is not overridden"
+abstract-method:87:Iterator:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
+abstract-method:87:Iterator:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:87:Iterator:"Method '__len__' is abstract in class 'Structure' but is not overridden"
+abstract-method:106:BadComplexMro:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:106:BadComplexMro:"Method '__len__' is abstract in class 'AbstractSizable' but is not overridden"
+abstract-method:106:BadComplexMro:"Method 'length' is abstract in class 'AbstractSizable' but is not overridden"