Skip to content

Commit cfb04c3

Browse files
committed
Add broken NoReturn check
1 parent 48d584b commit cfb04c3

File tree

6 files changed

+101
-5
lines changed

6 files changed

+101
-5
lines changed

ChangeLog

+10-5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Release date: TBA
3333
* Properly identify parameters with no documentation and add new message called ``missing-any-param-doc``
3434

3535
Closes #3799
36+
3637
* Add checkers ``overridden-final-method`` & ``subclassed-final-class``
3738

3839
Closes #3197
@@ -182,6 +183,15 @@ Release date: TBA
182183

183184
* Make yn validator case insensitive, to allow for ``True`` and ``False`` in config files.
184185

186+
* ``TypingChecker``
187+
188+
* Fix false-negative for ``deprecated-typing-alias`` and ``consider-using-alias``
189+
with ``typing.Type`` + ``typing.Callable``.
190+
191+
* Added new check ``broken-noreturn`` to detect broken uses of ``typing.NoReturn``
192+
if ``py-version`` is set to Python ``3.7.1`` or below.
193+
https://bugs.python.org/issue34921
194+
185195

186196
What's New in Pylint 2.11.2?
187197
============================
@@ -260,11 +270,6 @@ Release date: TBA
260270
Closes #3507
261271
Closes #5087
262272

263-
* ``TypingChecker``
264-
265-
* Fix false-negative for ``deprecated-typing-alias`` and ``consider-using-alias``
266-
with ``typing.Type`` + ``typing.Callable``.
267-
268273

269274
What's New in Pylint 2.11.1?
270275
============================

doc/whatsnew/2.12.rst

+10
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Removed checkers
6969

7070
Extensions
7171
==========
72+
7273
* Added an optional extension ``consider-using-any-or-all``: Emitted when a ``for`` loop only
7374
produces a boolean and could be replaced by ``any`` or ``all`` using a generator. Also suggests
7475
a suitable any/all statement if it is concise.
@@ -81,6 +82,15 @@ Extensions
8182

8283
Closes #1064
8384

85+
* ``TypingChecker``
86+
87+
* Fix false-negative for ``deprecated-typing-alias`` and ``consider-using-alias``
88+
with ``typing.Type`` + ``typing.Callable``.
89+
90+
* Added new check ``broken-noreturn`` to detect broken uses of ``typing.NoReturn``
91+
if ``py-version`` is set to Python ``3.7.1`` or below.
92+
https://bugs.python.org/issue34921
93+
8494
Other Changes
8595
=============
8696

pylint/extensions/typing.py

+33
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ class TypingAlias(NamedTuple):
6666

6767
ALIAS_NAMES = frozenset(key.split(".")[1] for key in DEPRECATED_TYPING_ALIASES)
6868
UNION_NAMES = ("Optional", "Union")
69+
TYPING_NORETURN = frozenset(
70+
(
71+
"typing.NoReturn",
72+
"typing_extensions.NoReturn",
73+
)
74+
)
6975

7076

7177
class DeprecatedTypingAliasMsg(NamedTuple):
@@ -101,6 +107,14 @@ class TypingChecker(BaseChecker):
101107
"Emitted when 'typing.Union' or 'typing.Optional' is used "
102108
"instead of the alternative Union syntax 'int | None'.",
103109
),
110+
"E6004": (
111+
"'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1",
112+
"broken-noreturn",
113+
"``typing.NoReturn`` inside compound types is broken in "
114+
"Python 3.7.0 and 3.7.1. If not dependend on runtime introspection, "
115+
"use string annotation instead. E.g. "
116+
"``Callable[..., 'NoReturn']``. https://bugs.python.org/issue34921",
117+
),
104118
}
105119
options = (
106120
(
@@ -151,6 +165,8 @@ def open(self) -> None:
151165
self._py37_plus and self.config.runtime_typing is False
152166
)
153167

168+
self._should_check_noreturn = py_version < (3, 7, 2)
169+
154170
def _msg_postponed_eval_hint(self, node) -> str:
155171
"""Message hint if postponed evaluation isn't enabled."""
156172
if self._py310_plus or "annotations" in node.root().future_imports:
@@ -161,23 +177,29 @@ def _msg_postponed_eval_hint(self, node) -> str:
161177
"deprecated-typing-alias",
162178
"consider-using-alias",
163179
"consider-alternative-union-syntax",
180+
"broken-noreturn",
164181
)
165182
def visit_name(self, node: nodes.Name) -> None:
166183
if self._should_check_typing_alias and node.name in ALIAS_NAMES:
167184
self._check_for_typing_alias(node)
168185
if self._should_check_alternative_union_syntax and node.name in UNION_NAMES:
169186
self._check_for_alternative_union_syntax(node, node.name)
187+
if self._should_check_noreturn and node.name == "NoReturn":
188+
self._check_broken_noreturn(node)
170189

171190
@check_messages(
172191
"deprecated-typing-alias",
173192
"consider-using-alias",
174193
"consider-alternative-union-syntax",
194+
"broken-noreturn",
175195
)
176196
def visit_attribute(self, node: nodes.Attribute) -> None:
177197
if self._should_check_typing_alias and node.attrname in ALIAS_NAMES:
178198
self._check_for_typing_alias(node)
179199
if self._should_check_alternative_union_syntax and node.attrname in UNION_NAMES:
180200
self._check_for_alternative_union_syntax(node, node.attrname)
201+
if self._should_check_noreturn and node.attrname == "NoReturn":
202+
self._check_broken_noreturn(node)
181203

182204
def _check_for_alternative_union_syntax(
183205
self,
@@ -281,6 +303,17 @@ def leave_module(self, node: nodes.Module) -> None:
281303
self._alias_name_collisions.clear()
282304
self._consider_using_alias_msgs.clear()
283305

306+
def _check_broken_noreturn(self, node: Union[nodes.Name, nodes.Attribute]) -> None:
307+
"""Check for 'NoReturn' inside compound types."""
308+
for inferred in node.infer():
309+
if (
310+
isinstance(inferred, (nodes.FunctionDef, nodes.ClassDef))
311+
and inferred.qname() in TYPING_NORETURN
312+
and isinstance(node.parent, nodes.BaseContainer)
313+
):
314+
self.add_message("broken-noreturn", node=node)
315+
break
316+
284317

285318
def register(linter: PyLinter) -> None:
286319
linter.register_checker(TypingChecker(linter))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""
2+
'typing.NoReturn' is broken inside compond types for Python 3.7.0
3+
https://bugs.python.org/issue34921
4+
5+
If no runtime introspection is required, use string annotations instead.
6+
"""
7+
# pylint: disable=missing-docstring
8+
import sys
9+
import typing
10+
from typing import Callable, Union
11+
12+
import typing_extensions
13+
14+
if sys.version_info >= (3, 6, 2):
15+
from typing import NoReturn
16+
else:
17+
from typing_extensions import NoReturn
18+
19+
20+
def func1() -> NoReturn:
21+
raise Exception
22+
23+
def func2() -> Union[None, NoReturn]: # [broken-noreturn]
24+
pass
25+
26+
def func3() -> Union[None, "NoReturn"]:
27+
pass
28+
29+
def func4() -> Union[None, typing.NoReturn]: # [broken-noreturn]
30+
pass
31+
32+
def func5() -> Union[None, typing_extensions.NoReturn]: # [broken-noreturn]
33+
pass
34+
35+
36+
Alias1 = NoReturn
37+
Alias2 = Callable[..., NoReturn] # [broken-noreturn]
38+
Alias3 = Callable[..., "NoReturn"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[master]
2+
py-version=3.7
3+
load-plugins=pylint.extensions.typing
4+
5+
[testoptions]
6+
min_pyver=3.7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
broken-noreturn:23:27:func2:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1
2+
broken-noreturn:29:27:func4:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1
3+
broken-noreturn:32:27:func5:'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1
4+
broken-noreturn:37:23::'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1

0 commit comments

Comments
 (0)