diff options
| author | Slavfox <slavfoxman@gmail.com> | 2020-03-24 10:47:17 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-03-24 10:47:17 +0100 |
| commit | 4024949f6caf5eff5f3da7ab2b4c3cf2e296472b (patch) | |
| tree | ce63c74ff3961954656ff68ea081755a03b4a8d0 | |
| parent | 8a89edf3bf1aa00b93cc09507c8a32e71f9dc315 (diff) | |
| download | pylint-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.txt | 2 | ||||
| -rw-r--r-- | ChangeLog | 5 | ||||
| -rw-r--r-- | pylint/checkers/utils.py | 12 | ||||
| -rw-r--r-- | tests/functional/a/abstract_method_py3.py | 19 | ||||
| -rw-r--r-- | tests/functional/a/abstract_method_py3.txt | 32 |
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 @@ -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"
|
