From cfb04c343b53c2311b645416e9d6b6210d51f0fa Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 14 Nov 2021 20:47:05 +0100 Subject: [PATCH] Add broken NoReturn check --- ChangeLog | 15 +++++--- doc/whatsnew/2.12.rst | 10 +++++ pylint/extensions/typing.py | 33 ++++++++++++++++ .../ext/typing/typing_broken_noreturn.py | 38 +++++++++++++++++++ .../ext/typing/typing_broken_noreturn.rc | 6 +++ .../ext/typing/typing_broken_noreturn.txt | 4 ++ 6 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 tests/functional/ext/typing/typing_broken_noreturn.py create mode 100644 tests/functional/ext/typing/typing_broken_noreturn.rc create mode 100644 tests/functional/ext/typing/typing_broken_noreturn.txt diff --git a/ChangeLog b/ChangeLog index a4998baf8db..00ed56dbc86 100644 --- a/ChangeLog +++ b/ChangeLog @@ -33,6 +33,7 @@ Release date: TBA * Properly identify parameters with no documentation and add new message called ``missing-any-param-doc`` Closes #3799 + * Add checkers ``overridden-final-method`` & ``subclassed-final-class`` Closes #3197 @@ -182,6 +183,15 @@ Release date: TBA * Make yn validator case insensitive, to allow for ``True`` and ``False`` in config files. +* ``TypingChecker`` + + * Fix false-negative for ``deprecated-typing-alias`` and ``consider-using-alias`` + with ``typing.Type`` + ``typing.Callable``. + + * Added new check ``broken-noreturn`` to detect broken uses of ``typing.NoReturn`` + if ``py-version`` is set to Python ``3.7.1`` or below. + https://bugs.python.org/issue34921 + What's New in Pylint 2.11.2? ============================ @@ -260,11 +270,6 @@ Release date: TBA Closes #3507 Closes #5087 -* ``TypingChecker`` - - * Fix false-negative for ``deprecated-typing-alias`` and ``consider-using-alias`` - with ``typing.Type`` + ``typing.Callable``. - What's New in Pylint 2.11.1? ============================ diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst index b69af328971..f18e6d1f216 100644 --- a/doc/whatsnew/2.12.rst +++ b/doc/whatsnew/2.12.rst @@ -69,6 +69,7 @@ Removed checkers Extensions ========== + * Added an optional extension ``consider-using-any-or-all``: Emitted when a ``for`` loop only produces a boolean and could be replaced by ``any`` or ``all`` using a generator. Also suggests a suitable any/all statement if it is concise. @@ -81,6 +82,15 @@ Extensions Closes #1064 +* ``TypingChecker`` + + * Fix false-negative for ``deprecated-typing-alias`` and ``consider-using-alias`` + with ``typing.Type`` + ``typing.Callable``. + + * Added new check ``broken-noreturn`` to detect broken uses of ``typing.NoReturn`` + if ``py-version`` is set to Python ``3.7.1`` or below. + https://bugs.python.org/issue34921 + Other Changes ============= diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 92282b98373..76202516feb 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -66,6 +66,12 @@ class TypingAlias(NamedTuple): ALIAS_NAMES = frozenset(key.split(".")[1] for key in DEPRECATED_TYPING_ALIASES) UNION_NAMES = ("Optional", "Union") +TYPING_NORETURN = frozenset( + ( + "typing.NoReturn", + "typing_extensions.NoReturn", + ) +) class DeprecatedTypingAliasMsg(NamedTuple): @@ -101,6 +107,14 @@ class TypingChecker(BaseChecker): "Emitted when 'typing.Union' or 'typing.Optional' is used " "instead of the alternative Union syntax 'int | None'.", ), + "E6004": ( + "'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1", + "broken-noreturn", + "``typing.NoReturn`` inside compound types is broken in " + "Python 3.7.0 and 3.7.1. If not dependend on runtime introspection, " + "use string annotation instead. E.g. " + "``Callable[..., 'NoReturn']``. https://bugs.python.org/issue34921", + ), } options = ( ( @@ -151,6 +165,8 @@ def open(self) -> None: self._py37_plus and self.config.runtime_typing is False ) + self._should_check_noreturn = py_version < (3, 7, 2) + def _msg_postponed_eval_hint(self, node) -> str: """Message hint if postponed evaluation isn't enabled.""" if self._py310_plus or "annotations" in node.root().future_imports: @@ -161,23 +177,29 @@ def _msg_postponed_eval_hint(self, node) -> str: "deprecated-typing-alias", "consider-using-alias", "consider-alternative-union-syntax", + "broken-noreturn", ) def visit_name(self, node: nodes.Name) -> None: if self._should_check_typing_alias and node.name in ALIAS_NAMES: self._check_for_typing_alias(node) if self._should_check_alternative_union_syntax and node.name in UNION_NAMES: self._check_for_alternative_union_syntax(node, node.name) + if self._should_check_noreturn and node.name == "NoReturn": + self._check_broken_noreturn(node) @check_messages( "deprecated-typing-alias", "consider-using-alias", "consider-alternative-union-syntax", + "broken-noreturn", ) def visit_attribute(self, node: nodes.Attribute) -> None: if self._should_check_typing_alias and node.attrname in ALIAS_NAMES: self._check_for_typing_alias(node) if self._should_check_alternative_union_syntax and node.attrname in UNION_NAMES: self._check_for_alternative_union_syntax(node, node.attrname) + if self._should_check_noreturn and node.attrname == "NoReturn": + self._check_broken_noreturn(node) def _check_for_alternative_union_syntax( self, @@ -281,6 +303,17 @@ def leave_module(self, node: nodes.Module) -> None: self._alias_name_collisions.clear() self._consider_using_alias_msgs.clear() + def _check_broken_noreturn(self, node: Union[nodes.Name, nodes.Attribute]) -> None: + """Check for 'NoReturn' inside compound types.""" + for inferred in node.infer(): + if ( + isinstance(inferred, (nodes.FunctionDef, nodes.ClassDef)) + and inferred.qname() in TYPING_NORETURN + and isinstance(node.parent, nodes.BaseContainer) + ): + self.add_message("broken-noreturn", node=node) + break + def register(linter: PyLinter) -> None: linter.register_checker(TypingChecker(linter)) diff --git a/tests/functional/ext/typing/typing_broken_noreturn.py b/tests/functional/ext/typing/typing_broken_noreturn.py new file mode 100644 index 00000000000..a182c16806d --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn.py @@ -0,0 +1,38 @@ +""" +'typing.NoReturn' is broken inside compond types for Python 3.7.0 +https://bugs.python.org/issue34921 + +If no runtime introspection is required, use string annotations instead. +""" +# pylint: disable=missing-docstring +import sys +import typing +from typing import Callable, Union + +import typing_extensions + +if sys.version_info >= (3, 6, 2): + from typing import NoReturn +else: + from typing_extensions import NoReturn + + +def func1() -> NoReturn: + raise Exception + +def func2() -> Union[None, NoReturn]: # [broken-noreturn] + pass + +def func3() -> Union[None, "NoReturn"]: + pass + +def func4() -> Union[None, typing.NoReturn]: # [broken-noreturn] + pass + +def func5() -> Union[None, typing_extensions.NoReturn]: # [broken-noreturn] + pass + + +Alias1 = NoReturn +Alias2 = Callable[..., NoReturn] # [broken-noreturn] +Alias3 = Callable[..., "NoReturn"] diff --git a/tests/functional/ext/typing/typing_broken_noreturn.rc b/tests/functional/ext/typing/typing_broken_noreturn.rc new file mode 100644 index 00000000000..92ef9524a38 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn.rc @@ -0,0 +1,6 @@ +[master] +py-version=3.7 +load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.7 diff --git a/tests/functional/ext/typing/typing_broken_noreturn.txt b/tests/functional/ext/typing/typing_broken_noreturn.txt new file mode 100644 index 00000000000..e89bcb9aa42 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn.txt @@ -0,0 +1,4 @@ +broken-noreturn:23:27:func2:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1 +broken-noreturn:29:27:func4:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1 +broken-noreturn:32:27:func5:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1 +broken-noreturn:37:23::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1