diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index b1cadda1d6..2189eb122f 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1164,9 +1164,14 @@ 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(): - if ancestor.name == "ABC" and ancestor.root().name in ABC_MODULES: - # abc.ABC inheritance + # As well as direct abc.ABC inheritance + for base in node.bases: + inferred_base = safe_infer(base) + if ( + isinstance(inferred_base, nodes.ClassDef) + and inferred_base.root().name in ABC_MODULES + and inferred_base.name == "ABC" + ): return True for method in node.methods(): diff --git a/pylint/testutils/lint_module_test.py b/pylint/testutils/lint_module_test.py index 48ee5a0b2f..6930c4fd3d 100644 --- a/pylint/testutils/lint_module_test.py +++ b/pylint/testutils/lint_module_test.py @@ -272,10 +272,15 @@ def error_msg_for_unequal_messages( ) if missing: msg.append("\nExpected in testdata:") - msg.extend(f" {msg[0]:3}: {msg[1]}" for msg in sorted(missing)) + msg.extend( + f" {msg[0]:3}: {msg[1]} ({cnt})" for msg, cnt in sorted(missing.items()) + ) if unexpected: msg.append("\nUnexpected in testdata:") - msg.extend(f" {msg[0]:3}: {msg[1]}" for msg in sorted(unexpected)) + msg.extend( + f" {msg[0]:3}: {msg[1]} ({cnt})" + for msg, cnt in sorted(unexpected.items()) + ) error_msg = "\n".join(msg) if self._config and self._config.getoption("verbose") > 0: error_msg += "\n\nActual pylint output for this file:\n" diff --git a/tests/functional/a/abstract/abstract_method.py b/tests/functional/a/abstract/abstract_method.py index 75ffda2209..3e0c518e5c 100644 --- a/tests/functional/a/abstract/abstract_method.py +++ b/tests/functional/a/abstract/abstract_method.py @@ -6,7 +6,7 @@ import abc -class Abstract: +class Abstract(abc.ABC): def aaaa(self): """should be overridden in concrete class""" raise NotImplementedError() @@ -16,7 +16,7 @@ def bbbb(self): raise NotImplementedError() -class AbstractB(Abstract): +class AbstractB(Abstract, abc.ABC): """Abstract class. this class is checking that it does not output an error msg for @@ -44,12 +44,43 @@ class AbstractD(AbstractB, metaclass=abc.ABCMeta): """ -class Concrete(Abstract): # [abstract-method] - """Concrete class""" +class ConcreteA(AbstractC): # [abstract-method, abstract-method, abstract-method] + """ + Incomplete concrete class. + + Should trigger a warning for unimplemented abstract + methods due to lack of explicit abc.ABC inheritance. + """ + + +class ConcreteB(Abstract): # [abstract-method] + """ + Incomplete concrete class. + + Should trigger a warning for unimplemented abstract + methods due to lack of explicit abc.ABC inheritance. + """ + + def aaaa(self): + """overridden form Abstract""" + +class ConcreteC(AbstractC): + """ + Complete concrete class + + Should not trigger a warning as all + abstract methods are implemented. + """ def aaaa(self): """overridden form Abstract""" + def bbbb(self): + """overridden form Abstract""" + + def cccc(self): + """overridden form AbstractB""" + class Structure(metaclass=abc.ABCMeta): @abc.abstractmethod diff --git a/tests/functional/a/abstract/abstract_method.txt b/tests/functional/a/abstract/abstract_method.txt index f2b2b6c74f..8ee1ffd6cf 100644 --- a/tests/functional/a/abstract/abstract_method.txt +++ b/tests/functional/a/abstract/abstract_method.txt @@ -1,16 +1,19 @@ -abstract-method:47:0:47:14:Concrete:Method 'bbbb' is abstract in class 'Abstract' but is not overridden in child class 'Concrete':INFERENCE -abstract-method:70:0:70:15:Container:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'Container':INFERENCE -abstract-method:70:0:70:15:Container:Method '__iter__' is abstract in class 'Structure' but is not overridden in child class 'Container':INFERENCE -abstract-method:70:0:70:15:Container:Method '__len__' is abstract in class 'Structure' but is not overridden in child class 'Container':INFERENCE -abstract-method:76:0:76:13:Sizable:Method '__contains__' is abstract in class 'Structure' but is not overridden in child class 'Sizable':INFERENCE -abstract-method:76:0:76:13:Sizable:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'Sizable':INFERENCE -abstract-method:76:0:76:13:Sizable:Method '__iter__' is abstract in class 'Structure' but is not overridden in child class 'Sizable':INFERENCE -abstract-method:82:0:82:14:Hashable:Method '__contains__' is abstract in class 'Structure' but is not overridden in child class 'Hashable':INFERENCE -abstract-method:82:0:82:14:Hashable:Method '__iter__' is abstract in class 'Structure' but is not overridden in child class 'Hashable':INFERENCE -abstract-method:82:0:82:14:Hashable:Method '__len__' is abstract in class 'Structure' but is not overridden in child class 'Hashable':INFERENCE -abstract-method:87:0:87:14:Iterator:Method '__contains__' is abstract in class 'Structure' but is not overridden in child class 'Iterator':INFERENCE -abstract-method:87:0:87:14:Iterator:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'Iterator':INFERENCE -abstract-method:87:0:87:14:Iterator:Method '__len__' is abstract in class 'Structure' but is not overridden in child class 'Iterator':INFERENCE -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:47:0:47:15:ConcreteA:Method 'aaaa' is abstract in class 'Abstract' but is not overridden in child class 'ConcreteA':INFERENCE +abstract-method:47:0:47:15:ConcreteA:Method 'bbbb' is abstract in class 'Abstract' but is not overridden in child class 'ConcreteA':INFERENCE +abstract-method:47:0:47:15:ConcreteA:Method 'cccc' is abstract in class 'AbstractB' but is not overridden in child class 'ConcreteA':INFERENCE +abstract-method:56:0:56:15:ConcreteB:Method 'bbbb' is abstract in class 'Abstract' but is not overridden in child class 'ConcreteB':INFERENCE +abstract-method:101:0:101:15:Container:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'Container':INFERENCE +abstract-method:101:0:101:15:Container:Method '__iter__' is abstract in class 'Structure' but is not overridden in child class 'Container':INFERENCE +abstract-method:101:0:101:15:Container:Method '__len__' is abstract in class 'Structure' but is not overridden in child class 'Container':INFERENCE +abstract-method:107:0:107:13:Sizable:Method '__contains__' is abstract in class 'Structure' but is not overridden in child class 'Sizable':INFERENCE +abstract-method:107:0:107:13:Sizable:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'Sizable':INFERENCE +abstract-method:107:0:107:13:Sizable:Method '__iter__' is abstract in class 'Structure' but is not overridden in child class 'Sizable':INFERENCE +abstract-method:113:0:113:14:Hashable:Method '__contains__' is abstract in class 'Structure' but is not overridden in child class 'Hashable':INFERENCE +abstract-method:113:0:113:14:Hashable:Method '__iter__' is abstract in class 'Structure' but is not overridden in child class 'Hashable':INFERENCE +abstract-method:113:0:113:14:Hashable:Method '__len__' is abstract in class 'Structure' but is not overridden in child class 'Hashable':INFERENCE +abstract-method:118:0:118:14:Iterator:Method '__contains__' is abstract in class 'Structure' but is not overridden in child class 'Iterator':INFERENCE +abstract-method:118:0:118:14:Iterator:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'Iterator':INFERENCE +abstract-method:118:0:118:14:Iterator:Method '__len__' is abstract in class 'Structure' but is not overridden in child class 'Iterator':INFERENCE +abstract-method:137:0:137:19:BadComplexMro:Method '__hash__' is abstract in class 'Structure' but is not overridden in child class 'BadComplexMro':INFERENCE +abstract-method:137:0:137:19:BadComplexMro:Method '__len__' is abstract in class 'AbstractSizable' but is not overridden in child class 'BadComplexMro':INFERENCE +abstract-method:137:0:137:19:BadComplexMro:Method 'length' is abstract in class 'AbstractSizable' but is not overridden in child class 'BadComplexMro':INFERENCE