diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index e2806ef4331..67acd25af56 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -2098,19 +2098,9 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No if node_frame_class(method) in parents_with_called_inits: return - # Return if klass is protocol - if klass.qname() in utils.TYPING_PROTOCOLS: + if utils.is_protocol_class(klass): return - # Return if any of the klass' first-order bases is protocol - for base in klass.bases: - try: - for inf_base in base.infer(): - if inf_base.qname() in utils.TYPING_PROTOCOLS: - return - except astroid.InferenceError: - continue - if decorated_with(node, ["typing.overload"]): continue self.add_message( diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 65df5096d00..585c3f894b0 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1657,14 +1657,23 @@ def is_protocol_class(cls: nodes.NodeNG) -> bool: """Check if the given node represents a protocol class. :param cls: The node to check - :returns: True if the node is a typing protocol class, false otherwise. + :returns: True if the node is or inherits from typing.Protocol directly, false otherwise. """ if not isinstance(cls, nodes.ClassDef): return False - # Use .ancestors() since not all protocol classes can have - # their mro deduced. - return any(parent.qname() in TYPING_PROTOCOLS for parent in cls.ancestors()) + # Return if klass is protocol + if cls.qname() in TYPING_PROTOCOLS: + return True + + for base in cls.bases: + try: + for inf_base in base.infer(): + if inf_base.qname() in TYPING_PROTOCOLS: + return True + except astroid.InferenceError: + continue + return False def is_call_of_name(node: nodes.NodeNG, name: str) -> bool: diff --git a/tests/functional/p/protocol_classes_abstract.py b/tests/functional/p/protocol_classes_abstract.py index f417d9729f5..7b7b89f42da 100644 --- a/tests/functional/p/protocol_classes_abstract.py +++ b/tests/functional/p/protocol_classes_abstract.py @@ -1,8 +1,8 @@ -"""Test that classes inheriting from protocols should not warn about abstract-method.""" +"""Test that classes inheriting directly from Protocol should not warn about abstract-method.""" # pylint: disable=too-few-public-methods,disallowed-name,invalid-name -from abc import abstractmethod +from abc import abstractmethod, ABCMeta from typing import Protocol, Literal @@ -28,9 +28,11 @@ class FooBarProtocol(FooProtocol, BarProtocol, Protocol): """FooBar Protocol""" -class IndirectProtocol(FooProtocol): +class IndirectProtocol(FooProtocol): # [abstract-method] """Doesn't subclass typing.Protocol directly""" +class AbcProtocol(FooProtocol, metaclass=ABCMeta): + """Doesn't subclass typing.Protocol but uses metaclass directly""" class FooBar(FooBarProtocol): """FooBar object""" diff --git a/tests/functional/p/protocol_classes_abstract.txt b/tests/functional/p/protocol_classes_abstract.txt new file mode 100644 index 00000000000..70e9b16b3ca --- /dev/null +++ b/tests/functional/p/protocol_classes_abstract.txt @@ -0,0 +1 @@ +abstract-method:31:0:31:22:IndirectProtocol:Method 'foo' is abstract in class 'FooProtocol' but is not overridden in child class 'IndirectProtocol':INFERENCE