Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add broken NoReturn check #5304

Merged
merged 16 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,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

* The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests
without these will trigger a ``DeprecationWarning``.

Expand Down Expand Up @@ -333,6 +339,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
Expand Down
6 changes: 6 additions & 0 deletions doc/whatsnew/2.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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

* ``DocstringParameterChecker``

* Fixed incorrect classification of Numpy-style docstring as Google-style docstring for
Expand Down
36 changes: 36 additions & 0 deletions pylint/extensions/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,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 @@ -103,6 +109,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, "
cdce8p marked this conversation as resolved.
Show resolved Hide resolved
"use string annotation instead. E.g. "
"``Callable[..., 'NoReturn']``. https://bugs.python.org/issue34921",
),
}
options = (
(
Expand Down Expand Up @@ -153,6 +167,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 @@ -163,23 +179,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 @@ -279,6 +301,20 @@ 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 ( # pylint: disable=too-many-boolean-expressions
isinstance(inferred, (nodes.FunctionDef, nodes.ClassDef))
and inferred.qname() in TYPING_NORETURN
# 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)
cdce8p marked this conversation as resolved.
Show resolved Hide resolved
break


def register(linter: "PyLinter") -> None:
linter.register_checker(TypingChecker(linter))
1 change: 1 addition & 0 deletions requirements_test_min.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-e .
# astroid dependency is also defined in setup.cfg
astroid==2.9.3 # Pinned to a specific version for tests
typing-extensions>=3.10
pytest~=6.2
pytest-benchmark~=3.4
gitpython>3
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):
DanielNoord marked this conversation as resolved.
Show resolved Hide resolved
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.6
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: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