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