From 6ff8091dff1493aed1ae951934384d9e977bbc5f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 9 Feb 2022 12:36:23 -0800 Subject: [PATCH] Add support for Never (#12153) --- mypy/semanal.py | 8 ++------ mypy/typeanal.py | 4 ++-- mypy/types.py | 8 ++++++++ test-data/unit/check-unreachable-code.test | 20 +++++++++++++++++++ test-data/unit/lib-stub/typing.pyi | 1 + test-data/unit/lib-stub/typing_extensions.pyi | 3 ++- 6 files changed, 35 insertions(+), 9 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 876866963e6b..2786411936bf 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -94,7 +94,7 @@ from mypy.errorcodes import ErrorCode from mypy import message_registry, errorcodes as codes from mypy.types import ( - FunctionLike, UnboundType, TypeVarType, TupleType, UnionType, StarType, + NEVER_NAMES, FunctionLike, UnboundType, TypeVarType, TupleType, UnionType, StarType, CallableType, Overloaded, Instance, Type, AnyType, LiteralType, LiteralValue, TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeVarLikeType, @@ -2227,11 +2227,7 @@ def is_type_ref(self, rv: Expression, bare: bool = False) -> bool: # Assignment color = Color['RED'] defines a variable, not an alias. return not rv.node.is_enum if isinstance(rv.node, Var): - return rv.node.fullname in ( - 'typing.NoReturn', - 'typing_extensions.NoReturn', - 'mypy_extensions.NoReturn', - ) + return rv.node.fullname in NEVER_NAMES if isinstance(rv, NameExpr): n = self.lookup(rv.name, rv) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 2a14162cf558..a35f48337d93 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -12,7 +12,7 @@ from mypy.messages import MessageBuilder, quote_type_string, format_type_bare from mypy.options import Options from mypy.types import ( - Type, UnboundType, TupleType, TypedDictType, UnionType, Instance, AnyType, + NEVER_NAMES, Type, UnboundType, TupleType, TypedDictType, UnionType, Instance, AnyType, CallableType, NoneType, ErasedType, DeletedType, TypeList, TypeVarType, SyntheticTypeVisitor, StarType, PartialType, EllipsisType, UninhabitedType, TypeType, CallableArgument, TypeQuery, union_items, TypeOfAny, LiteralType, RawExpressionType, @@ -348,7 +348,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt self.fail('ClassVar[...] must have at most one type argument', t) return AnyType(TypeOfAny.from_error) return self.anal_type(t.args[0]) - elif fullname in ('mypy_extensions.NoReturn', 'typing.NoReturn'): + elif fullname in NEVER_NAMES: return UninhabitedType(is_noreturn=True) elif fullname in LITERAL_TYPE_NAMES: return self.analyze_literal_type(t) diff --git a/mypy/types.py b/mypy/types.py index 5aecb8194f9e..21f560ed4e23 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -132,6 +132,14 @@ 'typing_extensions.reveal_type', ) +NEVER_NAMES: Final = ( + 'typing.NoReturn', + 'typing_extensions.NoReturn', + 'mypy_extensions.NoReturn', + 'typing.Never', + 'typing_extensions.Never', +) + # A placeholder used for Bogus[...] parameters _dummy: Final[Any] = object() diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index de85f5188bb8..ad604b474ad4 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -936,6 +936,26 @@ if False: reveal_type(x) [builtins fixtures/exception.pyi] +[case testNeverVariants] +from typing import Never +from typing_extensions import Never as TENever +from typing import NoReturn +from typing_extensions import NoReturn as TENoReturn +from mypy_extensions import NoReturn as MENoReturn + +bottom1: Never +reveal_type(bottom1) # N: Revealed type is "" +bottom2: TENever +reveal_type(bottom2) # N: Revealed type is "" +bottom3: NoReturn +reveal_type(bottom3) # N: Revealed type is "" +bottom4: TENoReturn +reveal_type(bottom4) # N: Revealed type is "" +bottom5: MENoReturn +reveal_type(bottom5) # N: Revealed type is "" + +[builtins fixtures/tuple.pyi] + [case testUnreachableFlagExpressions] # flags: --warn-unreachable def foo() -> bool: ... diff --git a/test-data/unit/lib-stub/typing.pyi b/test-data/unit/lib-stub/typing.pyi index 7283b47a96e7..1a6c5d36a367 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -23,6 +23,7 @@ Type = 0 ClassVar = 0 Final = 0 NoReturn = 0 +Never = 0 NewType = 0 ParamSpec = 0 diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index 7c6e14e404cd..38ff331f45d8 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -1,4 +1,4 @@ -from typing import TypeVar, Any, Mapping, Iterator, NoReturn, Dict, Type +from typing import TypeVar, Any, Mapping, Iterator, NoReturn as NoReturn, Dict, Type from typing import TYPE_CHECKING as TYPE_CHECKING from typing import NewType as NewType @@ -27,6 +27,7 @@ Concatenate: _SpecialForm TypeAlias: _SpecialForm TypeGuard: _SpecialForm +Never: _SpecialForm # Fallback type for all typed dicts (does not exist at runtime). class _TypedDict(Mapping[str, object]):