Skip to content

Commit

Permalink
define Protocol cls as must inherit directly from Protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
clavedeluna committed Nov 28, 2022
1 parent b90b442 commit f15b2b5
Show file tree
Hide file tree
Showing 4 changed files with 20 additions and 18 deletions.
12 changes: 1 addition & 11 deletions pylint/checkers/classes/class_checker.py
Expand Up @@ -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(
Expand Down
17 changes: 13 additions & 4 deletions pylint/checkers/utils.py
Expand Up @@ -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:
Expand Down
8 changes: 5 additions & 3 deletions 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


Expand All @@ -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"""
Expand Down
1 change: 1 addition & 0 deletions 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

0 comments on commit f15b2b5

Please sign in to comment.