From cd4b2926c707eb661d6514e2ee18a5ca93929dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 4 Oct 2023 07:37:43 +0200 Subject: [PATCH] Fix false positive for ``inherit-non-class`` for generic Protocols (#9108) --- doc/whatsnew/fragments/9106.false_positive | 3 +++ pylint/checkers/classes/class_checker.py | 5 +++-- tests/functional/i/inherit_non_class.py | 16 ++++++++++++++++ tests/functional/i/inherit_non_class.txt | 22 +++++++++++----------- 4 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 doc/whatsnew/fragments/9106.false_positive diff --git a/doc/whatsnew/fragments/9106.false_positive b/doc/whatsnew/fragments/9106.false_positive new file mode 100644 index 0000000000..58edbc0bfa --- /dev/null +++ b/doc/whatsnew/fragments/9106.false_positive @@ -0,0 +1,3 @@ +Fixed false positive for ``inherit-non-class`` for generic Protocols. + +Closes #9106 diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index c2ec70acfa..996c59dcc2 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -957,8 +957,9 @@ def _check_proper_bases(self, node: nodes.ClassDef) -> None: ancestor = safe_infer(base) if not ancestor: continue - if isinstance(ancestor, astroid.Instance) and ancestor.is_subtype_of( - "builtins.type" + if isinstance(ancestor, astroid.Instance) and ( + ancestor.is_subtype_of("builtins.type") + or ancestor.is_subtype_of(".Protocol") ): continue diff --git a/tests/functional/i/inherit_non_class.py b/tests/functional/i/inherit_non_class.py index fb00d6f99d..79d4358e81 100644 --- a/tests/functional/i/inherit_non_class.py +++ b/tests/functional/i/inherit_non_class.py @@ -4,6 +4,7 @@ # pylint: disable=import-error, invalid-name, using-constant-test # pylint: disable=missing-docstring, too-few-public-methods, useless-object-inheritance +from typing import Protocol, TypeVar from missing import Missing if 1: @@ -101,3 +102,18 @@ class Child2(ParentBad[int]): # [inherit-non-class] # Classes that don't implement '__class_getitem__' are marked as unsubscriptable class Child3(Empty[int]): # [unsubscriptable-object] pass + + +T = TypeVar("T") + + +class Channel(Protocol[T]): + """A generic Protocol.""" + + async def get(self) -> T: + """A get method using the generic.""" + + +class DirectChannel(Channel[T]): + async def get(self) -> T: + """An implementation of the generic.""" diff --git a/tests/functional/i/inherit_non_class.txt b/tests/functional/i/inherit_non_class.txt index 2e94b4001f..62b05f4b9b 100644 --- a/tests/functional/i/inherit_non_class.txt +++ b/tests/functional/i/inherit_non_class.txt @@ -1,11 +1,11 @@ -inherit-non-class:21:0:21:9:Bad:Inheriting '1', which is not a class.:UNDEFINED -inherit-non-class:24:0:24:10:Bad1:"Inheriting 'lambda abc: 42', which is not a class.":UNDEFINED -inherit-non-class:27:0:27:10:Bad2:Inheriting 'object()', which is not a class.:UNDEFINED -inherit-non-class:30:0:30:10:Bad3:Inheriting 'return_class', which is not a class.:UNDEFINED -inherit-non-class:33:0:33:10:Bad4:Inheriting 'Empty()', which is not a class.:UNDEFINED -inherit-non-class:68:0:68:24:NotInheritableBool:Inheriting 'bool', which is not a class.:UNDEFINED -inherit-non-class:72:0:72:25:NotInheritableRange:Inheriting 'range', which is not a class.:UNDEFINED -inherit-non-class:76:0:76:25:NotInheritableSlice:Inheriting 'slice', which is not a class.:UNDEFINED -inherit-non-class:80:0:80:30:NotInheritableMemoryView:Inheriting 'memoryview', which is not a class.:UNDEFINED -inherit-non-class:98:0:98:12:Child2:Inheriting 'ParentBad[int]', which is not a class.:UNDEFINED -unsubscriptable-object:102:13:102:18:Child3:Value 'Empty' is unsubscriptable:UNDEFINED +inherit-non-class:22:0:22:9:Bad:Inheriting '1', which is not a class.:UNDEFINED +inherit-non-class:25:0:25:10:Bad1:"Inheriting 'lambda abc: 42', which is not a class.":UNDEFINED +inherit-non-class:28:0:28:10:Bad2:Inheriting 'object()', which is not a class.:UNDEFINED +inherit-non-class:31:0:31:10:Bad3:Inheriting 'return_class', which is not a class.:UNDEFINED +inherit-non-class:34:0:34:10:Bad4:Inheriting 'Empty()', which is not a class.:UNDEFINED +inherit-non-class:69:0:69:24:NotInheritableBool:Inheriting 'bool', which is not a class.:UNDEFINED +inherit-non-class:73:0:73:25:NotInheritableRange:Inheriting 'range', which is not a class.:UNDEFINED +inherit-non-class:77:0:77:25:NotInheritableSlice:Inheriting 'slice', which is not a class.:UNDEFINED +inherit-non-class:81:0:81:30:NotInheritableMemoryView:Inheriting 'memoryview', which is not a class.:UNDEFINED +inherit-non-class:99:0:99:12:Child2:Inheriting 'ParentBad[int]', which is not a class.:UNDEFINED +unsubscriptable-object:103:13:103:18:Child3:Value 'Empty' is unsubscriptable:UNDEFINED