From d87b313f25b94f29d7a2fadc47ab52b43f6c5b9e 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 01/13] Add broken NoReturn check --- ChangeLog | 7 ++++ doc/whatsnew/2.13.rst | 6 +++ 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, 94 insertions(+) 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 f77b7e8706..67618bfeaf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -28,6 +28,12 @@ Release date: TBA Closes #5371 +* ``TypingChecker`` + + * 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 + .. Insert your changelog randomly, it will reduce merge conflicts (Ie. not necessarily at the end) @@ -154,6 +160,7 @@ Release date: 2021-11-24 * 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 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index 0713e343ae..eee23cc937 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -19,6 +19,12 @@ Extensions * Pyreverse - add output in mermaid-js format and html which is an mermaid js diagram with html boilerplate +* ``TypingChecker`` + + * 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 01bfe1b285..290999e52f 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, @@ -277,6 +299,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 0000000000..a182c16806 --- /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 0000000000..92ef9524a3 --- /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 0000000000..e89bcb9aa4 --- /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 From 20059c6b78d136cc603545eed61846da726dcfc8 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 14 Nov 2021 21:13:00 +0100 Subject: [PATCH 02/13] Fix python 3.8 --- pylint/extensions/typing.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 290999e52f..c1a6fe47ba 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -302,10 +302,14 @@ def leave_module(self, node: nodes.Module) -> None: def _check_broken_noreturn(self, node: Union[nodes.Name, nodes.Attribute]) -> None: """Check for 'NoReturn' inside compound types.""" for inferred in node.infer(): - if ( + if ( # pylint: disable=too-many-boolean-expressions isinstance(inferred, (nodes.FunctionDef, nodes.ClassDef)) and inferred.qname() in TYPING_NORETURN and isinstance(node.parent, nodes.BaseContainer) + # In Python < 3.9, 'NoReturn' is alias of '_SpecialForm' + or isinstance(inferred, astroid.bases.BaseInstance) + and isinstance(inferred._proxied, nodes.ClassDef) + and inferred._proxied.qname() == "typing._SpecialForm" ): self.add_message("broken-noreturn", node=node) break From 180d6643bc599bae4d0dee1083ffd5deaefa26f2 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 14 Nov 2021 21:20:15 +0100 Subject: [PATCH 03/13] Fix parent check --- pylint/extensions/typing.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index c1a6fe47ba..31dec80ee8 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -305,12 +305,11 @@ def _check_broken_noreturn(self, node: Union[nodes.Name, nodes.Attribute]) -> No if ( # pylint: disable=too-many-boolean-expressions isinstance(inferred, (nodes.FunctionDef, nodes.ClassDef)) and inferred.qname() in TYPING_NORETURN - and isinstance(node.parent, nodes.BaseContainer) # In Python < 3.9, 'NoReturn' is alias of '_SpecialForm' or isinstance(inferred, astroid.bases.BaseInstance) and isinstance(inferred._proxied, nodes.ClassDef) and inferred._proxied.qname() == "typing._SpecialForm" - ): + ) and isinstance(node.parent, nodes.BaseContainer): self.add_message("broken-noreturn", node=node) break From 5edeed839762dce7af7a3a2a072e42d7a823e438 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 14 Nov 2021 21:32:06 +0100 Subject: [PATCH 04/13] Add typing-extensions as test requirement --- requirements_test_min.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements_test_min.txt b/requirements_test_min.txt index a0e339897d..e878ab5835 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,7 @@ -e . # astroid dependency is also defined in setup.cfg astroid==2.9.0 # Pinned to a specific version for tests +typing-extensions>=3.10 pytest~=6.2 pytest-benchmark~=3.4 gitpython>3 From 59c3f7d7d3ee37048eb0b24c7192ac98b942cd2a Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:38:02 +0100 Subject: [PATCH 05/13] Update test output --- tests/functional/ext/typing/typing_broken_noreturn.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functional/ext/typing/typing_broken_noreturn.txt b/tests/functional/ext/typing/typing_broken_noreturn.txt index e89bcb9aa4..9564316714 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn.txt +++ b/tests/functional/ext/typing/typing_broken_noreturn.txt @@ -1,4 +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 +broken-noreturn:23:27:23:35:func2:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED +broken-noreturn:29:27:29:42:func4:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED +broken-noreturn:32:27:32:53:func5:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED +broken-noreturn:37:23:37:31::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED From 5300d313c7d151abff2ea2bd57d43c52094162b8 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 4 Dec 2021 17:38:45 +0100 Subject: [PATCH 06/13] Rename tests --- .../ext/typing/{typing_broken_noreturn.py => broken_noreturn.py} | 0 .../ext/typing/{typing_broken_noreturn.rc => broken_noreturn.rc} | 0 .../typing/{typing_broken_noreturn.txt => broken_noreturn.txt} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tests/functional/ext/typing/{typing_broken_noreturn.py => broken_noreturn.py} (100%) rename tests/functional/ext/typing/{typing_broken_noreturn.rc => broken_noreturn.rc} (100%) rename tests/functional/ext/typing/{typing_broken_noreturn.txt => broken_noreturn.txt} (100%) diff --git a/tests/functional/ext/typing/typing_broken_noreturn.py b/tests/functional/ext/typing/broken_noreturn.py similarity index 100% rename from tests/functional/ext/typing/typing_broken_noreturn.py rename to tests/functional/ext/typing/broken_noreturn.py diff --git a/tests/functional/ext/typing/typing_broken_noreturn.rc b/tests/functional/ext/typing/broken_noreturn.rc similarity index 100% rename from tests/functional/ext/typing/typing_broken_noreturn.rc rename to tests/functional/ext/typing/broken_noreturn.rc diff --git a/tests/functional/ext/typing/typing_broken_noreturn.txt b/tests/functional/ext/typing/broken_noreturn.txt similarity index 100% rename from tests/functional/ext/typing/typing_broken_noreturn.txt rename to tests/functional/ext/typing/broken_noreturn.txt From dfcef404e7df1ed9ad92909ce6a618f53170e670 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 5 Dec 2021 03:19:16 +0100 Subject: [PATCH 07/13] Revert "Rename tests" This reverts commit 5300d313c7d151abff2ea2bd57d43c52094162b8. --- .../ext/typing/{broken_noreturn.py => typing_broken_noreturn.py} | 0 .../ext/typing/{broken_noreturn.rc => typing_broken_noreturn.rc} | 0 .../typing/{broken_noreturn.txt => typing_broken_noreturn.txt} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tests/functional/ext/typing/{broken_noreturn.py => typing_broken_noreturn.py} (100%) rename tests/functional/ext/typing/{broken_noreturn.rc => typing_broken_noreturn.rc} (100%) rename tests/functional/ext/typing/{broken_noreturn.txt => typing_broken_noreturn.txt} (100%) diff --git a/tests/functional/ext/typing/broken_noreturn.py b/tests/functional/ext/typing/typing_broken_noreturn.py similarity index 100% rename from tests/functional/ext/typing/broken_noreturn.py rename to tests/functional/ext/typing/typing_broken_noreturn.py diff --git a/tests/functional/ext/typing/broken_noreturn.rc b/tests/functional/ext/typing/typing_broken_noreturn.rc similarity index 100% rename from tests/functional/ext/typing/broken_noreturn.rc rename to tests/functional/ext/typing/typing_broken_noreturn.rc diff --git a/tests/functional/ext/typing/broken_noreturn.txt b/tests/functional/ext/typing/typing_broken_noreturn.txt similarity index 100% rename from tests/functional/ext/typing/broken_noreturn.txt rename to tests/functional/ext/typing/typing_broken_noreturn.txt From a0942511eeb4a67829314ef32ccdfc4c1a2bd588 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 18 Dec 2021 00:02:30 +0100 Subject: [PATCH 08/13] Update tests/functional/ext/typing/typing_broken_noreturn.rc --- tests/functional/ext/typing/typing_broken_noreturn.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/ext/typing/typing_broken_noreturn.rc b/tests/functional/ext/typing/typing_broken_noreturn.rc index 92ef9524a3..4d81ab6fb6 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn.rc +++ b/tests/functional/ext/typing/typing_broken_noreturn.rc @@ -3,4 +3,4 @@ py-version=3.7 load-plugins=pylint.extensions.typing [testoptions] -min_pyver=3.7 +min_pyver=3.6 From 17fd5acb1465187a52d8859b40e09133bbc39191 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 10 Mar 2022 15:16:31 +0100 Subject: [PATCH 09/13] Fix Python 3.6 --- pylint/extensions/typing.py | 15 +++++--- requirements_test_min.txt | 2 +- .../ext/typing/typing_broken_noreturn.py | 8 +---- .../ext/typing/typing_broken_noreturn.rc | 3 -- .../ext/typing/typing_broken_noreturn.txt | 8 ++--- .../typing/typing_broken_noreturn_py372.py | 34 +++++++++++++++++++ .../typing/typing_broken_noreturn_py372.rc | 3 ++ 7 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 tests/functional/ext/typing/typing_broken_noreturn_py372.py create mode 100644 tests/functional/ext/typing/typing_broken_noreturn_py372.rc diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 68a60c2343..aaa464e14c 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -303,15 +303,22 @@ def leave_module(self, node: nodes.Module) -> None: def _check_broken_noreturn(self, node: Union[nodes.Name, nodes.Attribute]) -> None: """Check for 'NoReturn' inside compound types.""" + if not isinstance(node.parent, nodes.BaseContainer): + # NoReturn not part of a Union or Callable type + return + for inferred in node.infer(): - if ( # pylint: disable=too-many-boolean-expressions + # To deal with typing_extensions, don't use safe_infer + if ( isinstance(inferred, (nodes.FunctionDef, nodes.ClassDef)) and inferred.qname() in TYPING_NORETURN - # In Python < 3.9, 'NoReturn' is alias of '_SpecialForm' + # In Python 3.6, NoReturn is alias of '_NoReturn' + # In Python 3.7 - 3.8, NoReturn is alias of '_SpecialForm' or isinstance(inferred, astroid.bases.BaseInstance) and isinstance(inferred._proxied, nodes.ClassDef) - and inferred._proxied.qname() == "typing._SpecialForm" - ) and isinstance(node.parent, nodes.BaseContainer): + and inferred._proxied.qname() + in {"typing._NoReturn", "typing._SpecialForm"} + ): self.add_message("broken-noreturn", node=node) break diff --git a/requirements_test_min.txt b/requirements_test_min.txt index b28c9014e5..8c5b0f9c38 100644 --- a/requirements_test_min.txt +++ b/requirements_test_min.txt @@ -1,6 +1,6 @@ -e .[testutil] # astroid dependency is also defined in setup.cfg astroid==2.9.3 # Pinned to a specific version for tests -typing-extensions>=3.10 +typing-extensions~=4.0 pytest~=7.0 pytest-benchmark~=3.4 diff --git a/tests/functional/ext/typing/typing_broken_noreturn.py b/tests/functional/ext/typing/typing_broken_noreturn.py index a182c16806..f675fce6aa 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn.py +++ b/tests/functional/ext/typing/typing_broken_noreturn.py @@ -5,17 +5,11 @@ If no runtime introspection is required, use string annotations instead. """ # pylint: disable=missing-docstring -import sys import typing -from typing import Callable, Union +from typing import Callable, NoReturn, 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 diff --git a/tests/functional/ext/typing/typing_broken_noreturn.rc b/tests/functional/ext/typing/typing_broken_noreturn.rc index 4d81ab6fb6..56e0bed570 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn.rc +++ b/tests/functional/ext/typing/typing_broken_noreturn.rc @@ -1,6 +1,3 @@ [master] py-version=3.7 load-plugins=pylint.extensions.typing - -[testoptions] -min_pyver=3.6 diff --git a/tests/functional/ext/typing/typing_broken_noreturn.txt b/tests/functional/ext/typing/typing_broken_noreturn.txt index 9564316714..ab10d26486 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn.txt +++ b/tests/functional/ext/typing/typing_broken_noreturn.txt @@ -1,4 +1,4 @@ -broken-noreturn:23:27:23:35:func2:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED -broken-noreturn:29:27:29:42:func4:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED -broken-noreturn:32:27:32:53:func5:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED -broken-noreturn:37:23:37:31::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED +broken-noreturn:17:27:17:35:func2:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED +broken-noreturn:23:27:23:42:func4:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED +broken-noreturn:26:27:26:53:func5:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED +broken-noreturn:31:23:31:31::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED diff --git a/tests/functional/ext/typing/typing_broken_noreturn_py372.py b/tests/functional/ext/typing/typing_broken_noreturn_py372.py new file mode 100644 index 0000000000..c4b18a0337 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn_py372.py @@ -0,0 +1,34 @@ +""" +'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. + +Don't emit errors if py-version set to >= 3.7.2. +""" +# pylint: disable=missing-docstring +import typing +from typing import Callable, NoReturn, Union + +import typing_extensions + + +def func1() -> NoReturn: + raise Exception + +def func2() -> Union[None, NoReturn]: + pass + +def func3() -> Union[None, "NoReturn"]: + pass + +def func4() -> Union[None, typing.NoReturn]: + pass + +def func5() -> Union[None, typing_extensions.NoReturn]: + pass + + +Alias1 = NoReturn +Alias2 = Callable[..., NoReturn] +Alias3 = Callable[..., "NoReturn"] diff --git a/tests/functional/ext/typing/typing_broken_noreturn_py372.rc b/tests/functional/ext/typing/typing_broken_noreturn_py372.rc new file mode 100644 index 0000000000..24550b2c9d --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn_py372.rc @@ -0,0 +1,3 @@ +[master] +py-version=3.7.2 +load-plugins=pylint.extensions.typing From 1ca6a85bae0e25d7fc7b281ef646bb2e5576af88 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 10 Mar 2022 15:25:40 +0100 Subject: [PATCH 10/13] Code review --- pylint/extensions/typing.py | 6 +++--- tests/functional/ext/typing/typing_broken_noreturn.txt | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index aaa464e14c..8c1be6d525 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -9,7 +9,7 @@ is_node_in_type_annotation_context, safe_infer, ) -from pylint.interfaces import IAstroidChecker +from pylint.interfaces import INFERENCE, IAstroidChecker from pylint.utils.utils import get_global_option if TYPE_CHECKING: @@ -113,7 +113,7 @@ class TypingChecker(BaseChecker): "'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, " + "Python 3.7.0 and 3.7.1. If not dependent on runtime introspection, " "use string annotation instead. E.g. " "``Callable[..., 'NoReturn']``. https://bugs.python.org/issue34921", ), @@ -319,7 +319,7 @@ def _check_broken_noreturn(self, node: Union[nodes.Name, nodes.Attribute]) -> No and inferred._proxied.qname() in {"typing._NoReturn", "typing._SpecialForm"} ): - self.add_message("broken-noreturn", node=node) + self.add_message("broken-noreturn", node=node, confidence=INFERENCE) break diff --git a/tests/functional/ext/typing/typing_broken_noreturn.txt b/tests/functional/ext/typing/typing_broken_noreturn.txt index ab10d26486..ce4503341f 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn.txt +++ b/tests/functional/ext/typing/typing_broken_noreturn.txt @@ -1,4 +1,4 @@ -broken-noreturn:17:27:17:35:func2:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED -broken-noreturn:23:27:23:42:func4:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED -broken-noreturn:26:27:26:53:func5:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED -broken-noreturn:31:23:31:31::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:UNDEFINED +broken-noreturn:17:27:17:35:func2:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE +broken-noreturn:23:27:23:42:func4:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE +broken-noreturn:26:27:26:53:func5:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE +broken-noreturn:31:23:31:31::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE From cc7fb0807597e9664df3e07d384f918cf1e9fe67 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 10 Mar 2022 15:47:52 +0100 Subject: [PATCH 11/13] Check for future import --- pylint/extensions/typing.py | 6 +++ .../typing_broken_noreturn_future_import.py | 37 +++++++++++++++++++ .../typing_broken_noreturn_future_import.rc | 3 ++ .../typing_broken_noreturn_future_import.txt | 1 + 4 files changed, 47 insertions(+) create mode 100644 tests/functional/ext/typing/typing_broken_noreturn_future_import.py create mode 100644 tests/functional/ext/typing/typing_broken_noreturn_future_import.rc create mode 100644 tests/functional/ext/typing/typing_broken_noreturn_future_import.txt diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 8c1be6d525..949156fde5 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -7,6 +7,7 @@ from pylint.checkers.utils import ( check_messages, is_node_in_type_annotation_context, + is_postponed_evaluation_enabled, safe_infer, ) from pylint.interfaces import INFERENCE, IAstroidChecker @@ -307,6 +308,11 @@ def _check_broken_noreturn(self, node: Union[nodes.Name, nodes.Attribute]) -> No # NoReturn not part of a Union or Callable type return + if is_postponed_evaluation_enabled(node) and is_node_in_type_annotation_context( + node + ): + return + for inferred in node.infer(): # To deal with typing_extensions, don't use safe_infer if ( diff --git a/tests/functional/ext/typing/typing_broken_noreturn_future_import.py b/tests/functional/ext/typing/typing_broken_noreturn_future_import.py new file mode 100644 index 0000000000..83ec547499 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn_future_import.py @@ -0,0 +1,37 @@ +""" +'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. + +With 'from __future__ import annotations', only emit errors for nodes +not in a type annotation context. +""" +# pylint: disable=missing-docstring +from __future__ import annotations + +import typing +from typing import Callable, NoReturn, Union + +import typing_extensions + + +def func1() -> NoReturn: + raise Exception + +def func2() -> Union[None, NoReturn]: + pass + +def func3() -> Union[None, "NoReturn"]: + pass + +def func4() -> Union[None, typing.NoReturn]: + pass + +def func5() -> Union[None, typing_extensions.NoReturn]: + pass + + +Alias1 = NoReturn +Alias2 = Callable[..., NoReturn] # [broken-noreturn] +Alias3 = Callable[..., "NoReturn"] diff --git a/tests/functional/ext/typing/typing_broken_noreturn_future_import.rc b/tests/functional/ext/typing/typing_broken_noreturn_future_import.rc new file mode 100644 index 0000000000..56e0bed570 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn_future_import.rc @@ -0,0 +1,3 @@ +[master] +py-version=3.7 +load-plugins=pylint.extensions.typing diff --git a/tests/functional/ext/typing/typing_broken_noreturn_future_import.txt b/tests/functional/ext/typing/typing_broken_noreturn_future_import.txt new file mode 100644 index 0000000000..98a70dd5a7 --- /dev/null +++ b/tests/functional/ext/typing/typing_broken_noreturn_future_import.txt @@ -0,0 +1 @@ +broken-noreturn:33:23:33:31::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE From 8a6c619152ff0e3fe7f6fec50065ab2da3fda42f Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 10 Mar 2022 15:58:58 +0100 Subject: [PATCH 12/13] Fix lineno --- .../ext/typing/typing_broken_noreturn_future_import.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/ext/typing/typing_broken_noreturn_future_import.txt b/tests/functional/ext/typing/typing_broken_noreturn_future_import.txt index 98a70dd5a7..891a3a4d8b 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn_future_import.txt +++ b/tests/functional/ext/typing/typing_broken_noreturn_future_import.txt @@ -1 +1 @@ -broken-noreturn:33:23:33:31::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE +broken-noreturn:36:23:36:31::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1:INFERENCE From b73e3f6c18446f0aa43bc91f67405d8606c401a2 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 10 Mar 2022 16:21:05 +0100 Subject: [PATCH 13/13] Update tests/functional/ext/typing/typing_broken_noreturn_future_import.rc --- .../ext/typing/typing_broken_noreturn_future_import.rc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/functional/ext/typing/typing_broken_noreturn_future_import.rc b/tests/functional/ext/typing/typing_broken_noreturn_future_import.rc index 56e0bed570..92ef9524a3 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn_future_import.rc +++ b/tests/functional/ext/typing/typing_broken_noreturn_future_import.rc @@ -1,3 +1,6 @@ [master] py-version=3.7 load-plugins=pylint.extensions.typing + +[testoptions] +min_pyver=3.7