diff --git a/CHANGELOG.md b/CHANGELOG.md index b3af780..af3a319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,20 @@ flake8-pyi uses Calendar Versioning (CalVer). ## Unreleased +### New Error Codes + +* Y069: Flags imports of `typing.NoReturn` and `typing_extensions.NoReturn` + as they are now unconditionally deprecated. + +### Removed Error Codes + +* Y050: Old error code that flagged `typing.NoReturn` and + `typing_extensions.NoReturn` in argument positions only. + ### Other changes * Support Python 3.15. -* Recommend to use `typing.TypeAlias` instead of `typing_extensions.TypeAlias`. +* Y026: Recommend to use `typing.TypeAlias` instead of `typing_extensions.TypeAlias`. ## 26.5.0 diff --git a/ERRORCODES.md b/ERRORCODES.md index 0c8f984..a1440cd 100644 --- a/ERRORCODES.md +++ b/ERRORCODES.md @@ -63,7 +63,6 @@ The following warnings are currently emitted by default: | Y047 | A private `TypeAlias` should be used at least once in the file in which it is defined. | Redundant code | Y048 | Function bodies should contain exactly one statement. This is because stub files are never executed at runtime, so any more than one statement would be redundant. (Note that if a function body includes a docstring, the docstring counts as a "statement".) | Understanding stubs | Y049 | A private `TypedDict` should be used at least once in the file in which it is defined. | Redundant code -| Y050 | Prefer `typing_extensions.Never` over `typing.NoReturn` for argument annotations. | Style | Y051 | Y051 detects redundant unions between `Literal` types and builtin supertypes. For example, `Literal[5]` is redundant in the union `int \| Literal[5]`, and `Literal[True]` is redundant in the union `Literal[True] \| bool`. | Redundant code | Y052 | Y052 disallows assignments to constant values where the assignment does not have a type annotation. For example, `x = 0` in the global namespace is ambiguous in a stub, as there are four different types that could be inferred for the variable `x`: `int`, `Final[int]`, `Literal[0]`, or `Final[Literal[0]]`. Enum members are excluded from this check, as are various special assignments such as `__all__` and `__match_args__`. | Correctness | Y053 | Only string and bytes literals <=50 characters long are permitted. (There are some exceptions, such as `Literal` subscripts, metadata strings inside `Annotated` subscripts, and strings passed to `@deprecated`.) | Style @@ -82,6 +81,7 @@ The following warnings are currently emitted by default: | Y066 | When using if/else with `sys.version_info`, put the code for new Python versions first. | Style | Y067 | Don't use `Incomplete \| None = None` in argument annotations. Instead, just use `=None`. | Style | Y068 | Don't use `@override` in stub files. Problems with a function signature deviating from its superclass are inherited from the implementation, and other tools such as stubtest are better placed to recognize deviations between stubs and the implementation. | Understanding stubs +| Y069 | Prefer `typing_extensions.Never` over `typing.NoReturn`. | Style ## Warnings disabled by default diff --git a/flake8_pyi/errors.py b/flake8_pyi/errors.py index 23616ca..1492cbd 100644 --- a/flake8_pyi/errors.py +++ b/flake8_pyi/errors.py @@ -90,9 +90,6 @@ class Error(NamedTuple): Y047 = 'Y047 Type alias "{alias_name}" is not used' Y048 = "Y048 Function body should contain exactly one statement" Y049 = 'Y049 TypedDict "{typeddict_name}" is not used' -Y050 = ( - 'Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations' -) Y051 = 'Y051 "{literal_subtype}" is redundant in a union with "{builtin_supertype}"' Y052 = 'Y052 Need type annotation for "{variable}"' Y053 = "Y053 String and bytes literals >50 characters long are not permitted" @@ -128,6 +125,7 @@ class Error(NamedTuple): ) Y067 = 'Y067 Use "=None" instead of "Incomplete | None = None"' Y068 = 'Y068 Do not use "@override" in stub files.' +Y069 = 'Y069 Use "typing_extensions.Never" instead of "{module}.NoReturn"' Y090 = ( 'Y090 "{original}" means ' diff --git a/flake8_pyi/visitor.py b/flake8_pyi/visitor.py index 4bb8d53..77e850a 100644 --- a/flake8_pyi/visitor.py +++ b/flake8_pyi/visitor.py @@ -217,8 +217,6 @@ def _is_object(node: ast.AST | None, name: str, *, from_: Container[str]) -> boo _is_AsyncIterable = partial( _is_object, name="AsyncIterable", from_=_TYPING_OR_COLLECTIONS_ABC ) -_is_Protocol = partial(_is_object, name="Protocol", from_=_TYPING_MODULES) -_is_NoReturn = partial(_is_object, name="NoReturn", from_=_TYPING_MODULES) _is_Final = partial(_is_object, name="Final", from_=_TYPING_MODULES) _is_Generator = partial(_is_object, name="Generator", from_=_TYPING_OR_COLLECTIONS_ABC) _is_AsyncGenerator = partial( @@ -812,6 +810,10 @@ def _check_import_or_attribute( if object_name == "Text": return errors.Y039.format(module=module_name) + # Y069 errors + if object_name == "NoReturn": + return errors.Y069.format(module=module_name) + # Y023 errors if module_name == "typing_extensions": if object_name in _BAD_TYPINGEXTENSIONS_Y023_IMPORTS: @@ -2065,8 +2067,6 @@ def _visit_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None: self.check_for_override(node) def visit_arg(self, node: ast.arg) -> None: - if _is_NoReturn(node.annotation): - self.error(node, errors.Y050) if _is_Incomplete(node.annotation): self.error(node, errors.Y065.format(what=f'parameter "{node.arg}"')) with self.visiting_arg.enabled(): diff --git a/tests/imports.pyi b/tests/imports.pyi index 23a2d5d..f6fc618 100644 --- a/tests/imports.pyi +++ b/tests/imports.pyi @@ -63,15 +63,15 @@ from typing import ( TypedDict, AnyStr, NewType, - NoReturn, final, overload, ) from typing_extensions import ( + Annotated, Concatenate, + Never, ParamSpec, TypeGuard, - Annotated, ) # BAD IMPORTS (Y022 code) @@ -153,6 +153,8 @@ from typing import AbstractSet # Y038 Use "from collections.abc import Set as A from typing_extensions import AbstractSet # Y038 Use "from collections.abc import Set as AbstractSet" instead of "from typing_extensions import AbstractSet" (PEP 585 syntax) from typing import Text # Y039 Use "str" instead of "typing.Text" from typing_extensions import Text # Y039 Use "str" instead of "typing_extensions.Text" +from typing import NoReturn # Y069 Use "typing_extensions.Never" instead of "typing.NoReturn" +from typing_extensions import NoReturn # Y069 Use "typing_extensions.Never" instead of "typing_extensions.NoReturn" from typing import ByteString # Y057 Do not use typing.ByteString, which has unclear semantics and is deprecated from collections.abc import ByteString # Y057 Do not use collections.abc.ByteString, which has unclear semantics and is deprecated diff --git a/tests/never_vs_noreturn.pyi b/tests/never_vs_noreturn.pyi index 2ef5643..0914f0a 100644 --- a/tests/never_vs_noreturn.pyi +++ b/tests/never_vs_noreturn.pyi @@ -1,15 +1,24 @@ +# isort: skip_file import typing -from typing import NoReturn + +from typing import NoReturn # Y069 Use "typing_extensions.Never" instead of "typing.NoReturn" import typing_extensions from typing_extensions import Never +from typing_extensions import NoReturn as NR # Y069 Use "typing_extensions.Never" instead of "typing_extensions.NoReturn" + +# NoReturn is now flagged at import time, so it should never be flagged elsewhere. + +x: NR -def badfunc0(arg: NoReturn) -> None: ... # Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations -def badfunc1(*args: typing.NoReturn) -> None: ... # Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations -def badfunc2(**kwargs: typing_extensions.NoReturn) -> None: ... # Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations # Y023 Use "typing.NoReturn" instead of "typing_extensions.NoReturn" -def badfunc3(*, arg: NoReturn) -> None: ... # Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations +def badfunc0(arg: NoReturn) -> None: ... +def badfunc1(*args: typing.NoReturn) -> None: ... # Y069 Use "typing_extensions.Never" instead of "typing.NoReturn" +def badfunc2(**kwargs: typing_extensions.NoReturn) -> None: ... # Y069 Use "typing_extensions.Never" instead of "typing_extensions.NoReturn" +def badfunc3(*, arg: NoReturn) -> None: ... +def badfunc4() -> NoReturn: ... def goodfunc0(arg: Never) -> None: ... def goodfunc1(*args: typing.Never) -> None: ... def goodfunc2(**kwargs: typing_extensions.Never) -> None: ... def goodfunc3(*, arg: Never) -> None: ... +def goodfunc4() -> Never: ... diff --git a/tests/pep695_py312.pyi b/tests/pep695_py312.pyi index 7bdc1f4..e155d47 100644 --- a/tests/pep695_py312.pyi +++ b/tests/pep695_py312.pyi @@ -5,7 +5,6 @@ from typing import ( Any, Literal, NamedTuple, - NoReturn, Protocol, Self, TypedDict, @@ -30,7 +29,6 @@ class GenericPEP695Class[T]: pass # Y012 Class body must not contain "pass" def __exit__(self, *args: Any) -> None: ... # Y036 Badly defined __exit__ method: Star-args in an __exit__ method should be annotated with "object", not "Any" async def __aexit__(self) -> None: ... # Y036 Badly defined __aexit__ method: If there are no star-args, there should be at least 3 non-keyword-only args in an __aexit__ method (excluding "self") - def never_call_me(self, arg: NoReturn) -> None: ... # Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations class GenericPEP695InheritingFromObject[T](object): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3 x: T