From 2d8a894abb8a59c09c57826aeedf65cce1145c5c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 05:57:28 +0000 Subject: [PATCH 1/3] Fix false positive for ``inherit-non-class`` for generic Protocols (#9108) (#9111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit cd4b2926c707eb661d6514e2ee18a5ca93929dee) Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --- 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 From a1443ed5b83e1e574d8fd15ff5bd2f8e42368247 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:25:52 +0200 Subject: [PATCH 2/3] Fix a crash for Enum class decorated with dataclass (#9104) (#9112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix a crash when an enum class which is also decorated with a ``dataclasses.dataclass`` decorator is defined. Closes #9100 * Update test to mention the `astroid` issue that addresses the missing ``__members__`` object. * Use an `if` instead of the `try/except` block. * Update pylint/checkers/utils.py Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> --------- Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com> (cherry picked from commit 2c3425dc429293a77a86cd11361a8b268aa8fc91) Co-authored-by: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- doc/whatsnew/fragments/9100.other | 3 +++ pylint/checkers/utils.py | 7 +++++-- .../i/invalid/invalid_name/invalid_name_enum.py | 9 +++++++++ .../i/invalid/invalid_name/invalid_name_enum.txt | 3 ++- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 doc/whatsnew/fragments/9100.other diff --git a/doc/whatsnew/fragments/9100.other b/doc/whatsnew/fragments/9100.other new file mode 100644 index 0000000000..75e19175b9 --- /dev/null +++ b/doc/whatsnew/fragments/9100.other @@ -0,0 +1,3 @@ +Fix a crash when an enum class which is also decorated with a ``dataclasses.dataclass`` decorator is defined. + +Closes #9100 diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index d3a344c9c6..eb9d52d4d5 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -2281,5 +2281,8 @@ def is_enum_member(node: nodes.AssignName) -> bool: ): return False - enum_member_objects = frame.locals.get("__members__")[0].items - return node.name in [name_obj.name for value, name_obj in enum_member_objects] + members = frame.locals.get("__members__") + # A dataclass is one known case for when `members` can be `None` + if members is None: + return False + return node.name in [name_obj.name for value, name_obj in members[0].items] diff --git a/tests/functional/i/invalid/invalid_name/invalid_name_enum.py b/tests/functional/i/invalid/invalid_name/invalid_name_enum.py index 5338a03693..0857b25a5c 100644 --- a/tests/functional/i/invalid/invalid_name/invalid_name_enum.py +++ b/tests/functional/i/invalid/invalid_name/invalid_name_enum.py @@ -2,6 +2,7 @@ # pylint: disable=too-few-public-methods +from dataclasses import dataclass from enum import Enum @@ -28,3 +29,11 @@ def __init__(self, red: int, green: int, blue: int) -> None: def as_hex(self) -> str: """Get hex 'abcdef' representation for a color.""" return f'{self.red:0{2}x}{self.green:0{2}x}{self.blue:0{2}x}' + + +@dataclass +class Something(str, Enum): + """ A false positive for ``invalid-name`` + which should be fixed by https://github.com/pylint-dev/astroid/issues/2317 + """ + ASD: str = 1 # [invalid-name] diff --git a/tests/functional/i/invalid/invalid_name/invalid_name_enum.txt b/tests/functional/i/invalid/invalid_name/invalid_name_enum.txt index 9383a49dbf..9e1de87595 100644 --- a/tests/functional/i/invalid/invalid_name/invalid_name_enum.txt +++ b/tests/functional/i/invalid/invalid_name/invalid_name_enum.txt @@ -1 +1,2 @@ -invalid-name:16:4:16:14:Color:"Class constant name ""aquamarine"" doesn't conform to UPPER_CASE naming style":HIGH +invalid-name:17:4:17:14:Color:"Class constant name ""aquamarine"" doesn't conform to UPPER_CASE naming style":HIGH +invalid-name:39:4:None:None:Something:"Attribute name ""ASD"" doesn't conform to snake_case naming style":HIGH From f2cded41f7f3d4b45236cbe107b366b6caf84bde Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 5 Oct 2023 22:09:46 +0200 Subject: [PATCH 3/3] Bump pylint to 3.0.1, update changelog (#9119) --- doc/exts/pylint_extensions.py | 4 ++-- doc/whatsnew/3/3.0/index.rst | 22 ++++++++++++++++++++++ doc/whatsnew/fragments/9100.other | 3 --- doc/whatsnew/fragments/9106.false_positive | 3 --- pylint/__pkginfo__.py | 2 +- tbump.toml | 2 +- towncrier.toml | 2 +- 7 files changed, 27 insertions(+), 11 deletions(-) delete mode 100644 doc/whatsnew/fragments/9100.other delete mode 100644 doc/whatsnew/fragments/9106.false_positive diff --git a/doc/exts/pylint_extensions.py b/doc/exts/pylint_extensions.py index cadcbdcba0..936b341989 100755 --- a/doc/exts/pylint_extensions.py +++ b/doc/exts/pylint_extensions.py @@ -145,9 +145,9 @@ def get_plugins_info( return by_checker -def setup(app: Sphinx) -> dict[str, str]: +def setup(app: Sphinx) -> dict[str, str | bool]: app.connect("builder-inited", builder_inited) - return {"version": sphinx.__display_version__} + return {"version": sphinx.__display_version__, "parallel_read_safe": True} if __name__ == "__main__": diff --git a/doc/whatsnew/3/3.0/index.rst b/doc/whatsnew/3/3.0/index.rst index 22222d73c7..0690002fc5 100644 --- a/doc/whatsnew/3/3.0/index.rst +++ b/doc/whatsnew/3/3.0/index.rst @@ -65,6 +65,28 @@ easier to parse and provides more info, here's a sample output. .. towncrier release notes start +What's new in Pylint 3.0.1? +--------------------------- +Release date: 2023-10-05 + + +False Positives Fixed +--------------------- + +- Fixed false positive for ``inherit-non-class`` for generic Protocols. + + Closes #9106 (`#9106 `_) + + + +Other Changes +------------- + +- Fix a crash when an enum class which is also decorated with a ``dataclasses.dataclass`` decorator is defined. + + Closes #9100 (`#9100 `_) + + What's new in Pylint 3.0.0? --------------------------- Release date: 2023-10-02 diff --git a/doc/whatsnew/fragments/9100.other b/doc/whatsnew/fragments/9100.other deleted file mode 100644 index 75e19175b9..0000000000 --- a/doc/whatsnew/fragments/9100.other +++ /dev/null @@ -1,3 +0,0 @@ -Fix a crash when an enum class which is also decorated with a ``dataclasses.dataclass`` decorator is defined. - -Closes #9100 diff --git a/doc/whatsnew/fragments/9106.false_positive b/doc/whatsnew/fragments/9106.false_positive deleted file mode 100644 index 58edbc0bfa..0000000000 --- a/doc/whatsnew/fragments/9106.false_positive +++ /dev/null @@ -1,3 +0,0 @@ -Fixed false positive for ``inherit-non-class`` for generic Protocols. - -Closes #9106 diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py index 770c4710b2..95d97c2da6 100644 --- a/pylint/__pkginfo__.py +++ b/pylint/__pkginfo__.py @@ -9,7 +9,7 @@ from __future__ import annotations -__version__ = "3.0.0" +__version__ = "3.0.1" def get_numversion_from_version(v: str) -> tuple[int, int, int]: diff --git a/tbump.toml b/tbump.toml index 20083fcdb6..86a1431932 100644 --- a/tbump.toml +++ b/tbump.toml @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/pylint" [version] -current = "3.0.0" +current = "3.0.1" regex = ''' ^(?P0|[1-9]\d*) \. diff --git a/towncrier.toml b/towncrier.toml index 3ef3d18f6c..d26a9e4b4e 100644 --- a/towncrier.toml +++ b/towncrier.toml @@ -1,5 +1,5 @@ [tool.towncrier] -version = "3.0.0" +version = "3.0.1" directory = "doc/whatsnew/fragments" filename = "doc/whatsnew/3/3.0/index.rst" template = "doc/whatsnew/fragments/_template.rst"