diff --git a/doc/data/messages/a/abstract-method/bad/abstract_method.py b/doc/data/messages/a/abstract-method/bad/abstract_method.py index 35e5c819dd..8501050906 100644 --- a/doc/data/messages/a/abstract-method/bad/abstract_method.py +++ b/doc/data/messages/a/abstract-method/bad/abstract_method.py @@ -1,7 +1,7 @@ import abc -class WildAnimal: +class WildAnimal(abc.ABC): @abc.abstractmethod def make_sound(self): pass diff --git a/doc/data/messages/a/abstract-method/good/abstract_method.py b/doc/data/messages/a/abstract-method/good/abstract_method.py index 695443e48d..66a24838c9 100644 --- a/doc/data/messages/a/abstract-method/good/abstract_method.py +++ b/doc/data/messages/a/abstract-method/good/abstract_method.py @@ -1,7 +1,7 @@ import abc -class WildAnimal: +class WildAnimal(abc.ABC): @abc.abstractmethod def make_sound(self): pass diff --git a/doc/whatsnew/fragments/9979.false_negative b/doc/whatsnew/fragments/9979.false_negative new file mode 100644 index 0000000000..5767b16de8 --- /dev/null +++ b/doc/whatsnew/fragments/9979.false_negative @@ -0,0 +1,3 @@ +Fix a false negative for ``abstract-method`` when an abstract class is the direct ancestor of a derived class. + +Closes #9979 diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index bfc4bc61da..ff71c0fc96 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1166,7 +1166,7 @@ def class_is_abstract(node: nodes.ClassDef) -> bool: if meta.name == "ABCMeta" and meta.root().name in ABC_MODULES: return True - for ancestor in node.ancestors(): + for ancestor in node.ancestors(recurs=False): if ancestor.name == "ABC" and ancestor.root().name in ABC_MODULES: # abc.ABC inheritance return True diff --git a/tests/functional/a/abstract/abstract_method.py b/tests/functional/a/abstract/abstract_method.py index 75ffda2209..618766ab0b 100644 --- a/tests/functional/a/abstract/abstract_method.py +++ b/tests/functional/a/abstract/abstract_method.py @@ -105,3 +105,18 @@ class GoodComplexMRO(Container, Iterator, Sizable, Hashable): # +1: [abstract-method, abstract-method, abstract-method] class BadComplexMro(Container, Iterator, AbstractSizable): pass + + +class Base(abc.ABC): + @abc.abstractmethod + def do_something(self): + pass + + +# +1: [abstract-method] +class Derived(Base): + """ Test that a class derived from an abstract class correctly triggers + `abstract-method`. + """ + def do_something_else(self): + pass diff --git a/tests/functional/a/abstract/abstract_method.txt b/tests/functional/a/abstract/abstract_method.txt index f2b2b6c74f..71d7da5f86 100644 --- a/tests/functional/a/abstract/abstract_method.txt +++ b/tests/functional/a/abstract/abstract_method.txt @@ -14,3 +14,4 @@ abstract-method:87:0:87:14:Iterator:Method '__len__' is abstract in class 'Struc abstract-method:106:0:106:19:BadComplexMro:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'BadComplexMro':INFERENCE abstract-method:106:0:106:19:BadComplexMro:Method '__len__' is abstract in class 'AbstractSizable' but is not overridden in child class 'BadComplexMro':INFERENCE abstract-method:106:0:106:19:BadComplexMro:Method 'length' is abstract in class 'AbstractSizable' but is not overridden in child class 'BadComplexMro':INFERENCE +abstract-method:117:0:117:13:Derived:Method 'do_something' is abstract in class 'Base' but is not overridden in child class 'Derived':INFERENCE