Skip to content

Commit

Permalink
Add broken NoReturn check
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p committed Nov 14, 2021
1 parent 48d584b commit cfb04c3
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 5 deletions.
15 changes: 10 additions & 5 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?
============================
Expand Down Expand Up @@ -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?
============================
Expand Down
10 changes: 10 additions & 0 deletions doc/whatsnew/2.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
=============

Expand Down
33 changes: 33 additions & 0 deletions pylint/extensions/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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 = (
(
Expand Down Expand Up @@ -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:
Expand All @@ -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,
Expand Down Expand Up @@ -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))
38 changes: 38 additions & 0 deletions tests/functional/ext/typing/typing_broken_noreturn.py
Original file line number Diff line number Diff line change
@@ -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"]
6 changes: 6 additions & 0 deletions tests/functional/ext/typing/typing_broken_noreturn.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[master]
py-version=3.7
load-plugins=pylint.extensions.typing

[testoptions]
min_pyver=3.7
4 changes: 4 additions & 0 deletions tests/functional/ext/typing/typing_broken_noreturn.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit cfb04c3

Please sign in to comment.