diff --git a/mypy/messages.py b/mypy/messages.py index 0871792ae..5ab8e3815 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -2740,7 +2740,7 @@ def format_literal_value(typ: LiteralType) -> str: else: return "Never" elif isinstance(typ, TypeType): - type_name = "type" if options.use_lowercase_names() else "Type" + type_name = typ.name if options.use_lowercase_names() else typ.name.title() return f"{type_name}[{format(typ.item)}]" elif isinstance(typ, FunctionLike): func = typ diff --git a/mypy/typeanal.py b/mypy/typeanal.py index ecc3acd75..955e1aa65 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -69,12 +69,14 @@ ProperType, RawExpressionType, RequiredType, + SpecialFormType, SyntheticTypeVisitor, TrivialSyntheticTypeTranslator, TupleType, Type, TypeAliasType, TypedDictType, + TypeFormType, TypeGuardType, TypeList, TypeOfAny, @@ -584,6 +586,12 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ elif fullname == "basedtyping.Intersection": items = self.anal_array(t.args) return IntersectionType.make_intersection(items) + elif fullname == "test.SpecialForm": # TODO: "basedtyping.SpecialForm + item = self.anal_type(t.args[0]) + return SpecialFormType(item, line=t.line, column=t.column) + elif fullname == "test.TypeForm": # TODO: "basedtyping.TypeForm + item = self.anal_type(t.args[0]) + return TypeFormType(item, line=t.line, column=t.column) elif fullname == "typing.Optional": if len(t.args) != 1: self.fail( diff --git a/mypy/types.py b/mypy/types.py index 37698bb1a..f7966ad2c 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -3222,6 +3222,8 @@ class TypeType(ProperType): assumption). """ + name = "type" + __slots__ = ("item",) # This can't be everything, but it can be a class reference, @@ -3278,6 +3280,30 @@ def deserialize(cls, data: JsonDict) -> Type: return TypeType.make_normalized(deserialize_type(data["item"])) +class TypeFormType(TypeType): + name = "TypeForm" + + def serialize(self) -> JsonDict: + return {".class": "TypeFormType", "item": self.item.serialize()} + + @classmethod + def deserialize(cls, data: JsonDict) -> Type: + assert data[".class"] == "TypeFormType" + return cls(deserialize_type(data["item"])) + + +class SpecialFormType(TypeType): + name = "SpecialForm" + + def serialize(self) -> JsonDict: + return {".class": "SpecialFormType", "item": self.item.serialize()} + + @classmethod + def deserialize(cls, data: JsonDict) -> Type: + assert data[".class"] == "SpecialFormType" + return cls(deserialize_type(data["item"])) + + class PlaceholderType(ProperType): """Temporary, yet-unknown type during semantic analysis. @@ -3729,8 +3755,9 @@ def visit_ellipsis_type(self, t: EllipsisType) -> str: def visit_type_type(self, t: TypeType) -> str: if not mypy.options._based: - return f"Type[{t.item.accept(self)}]" - return f"type[{t.item.accept(self)}]" + name = t.name.title() + return f"{name}[{t.item.accept(self)}]" + return f"{t.name}[{t.item.accept(self)}]" def visit_placeholder_type(self, t: PlaceholderType) -> str: return f"" diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi index 17209d9b5..d2df842d8 100644 --- a/mypy/typeshed/stdlib/builtins.pyi +++ b/mypy/typeshed/stdlib/builtins.pyi @@ -30,6 +30,7 @@ from _typeshed import ( ) from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper +from mypy.types import SpecialFormType from types import CodeType, TracebackType, _Cell # mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping} are imported from collections.abc in builtins.pyi @@ -51,7 +52,7 @@ from typing import ( # noqa: Y022 SupportsFloat, TypeVar, overload, - type_check_only, + type_check_only, Union, ) from typing_extensions import ( Concatenate, @@ -1367,7 +1368,7 @@ def iter(__function: Callable[[], _T], __sentinel: object) -> Iterator[_T]: ... # Keep this alias in sync with unittest.case._ClassInfo if sys.version_info >= (3, 10): - _ClassInfo: TypeAlias = type | types.UnionType | tuple[_ClassInfo, ...] + _ClassInfo: TypeAlias = type | SpecialForm[Union[object]] | types.UnionType | tuple[_ClassInfo, ...] else: _ClassInfo: TypeAlias = type | tuple[_ClassInfo, ...] diff --git a/mypy/typeshed/stdlib/typing.pyi b/mypy/typeshed/stdlib/typing.pyi index 4932dc2db..d72602449 100644 --- a/mypy/typeshed/stdlib/typing.pyi +++ b/mypy/typeshed/stdlib/typing.pyi @@ -174,21 +174,30 @@ class TypeVar: # Used for an undocumented mypy feature. Does not exist at runtime. _promote = object() +_TSpecialInstance = TypeVar("_TSpecialForm", bound=_SpecialInstance) + + # N.B. Keep this definition in sync with typing_extensions._SpecialForm @_final -class _SpecialForm: - def __getitem__(self, parameters: Any) -> object: ... +class _SpecialForm(Generic[_TSpecialInstance]): + def __getitem__(self, parameters: Any) -> _TSpecialInstance: ... if sys.version_info >= (3, 10): - def __or__(self, other: Any) -> _SpecialForm: ... - def __ror__(self, other: Any) -> _SpecialForm: ... + def __or__(self, other: Any) -> _SpecialForm[_UnionInstance]: ... + def __ror__(self, other: Any) -> _SpecialForm[_UnionInstance]: ... _F = TypeVar("_F", bound=Callable[..., Any]) _P = _ParamSpec("_P") _T = TypeVar("_T") +_Ts = TypeVarTuple("_Ts") def overload(func: _F) -> _F: ... -Union: _SpecialForm +@type_check_only +class _SpecialInstance(Generic[Unpack[_Ts]]): ... +@type_check_only +class _UnionInstance(_SpecialInstance[Unpack[_Ts]]): ... + +Union: _SpecialForm[_UnionInstance] Generic: _SpecialForm # Protocol is only present in 3.8 and later, but mypy needs it unconditionally Protocol: _SpecialForm @@ -819,10 +828,10 @@ if sys.version_info >= (3, 9): else: def get_type_hints( obj: _get_type_hints_obj_allowed_types, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None - ) -> dict[str, Any]: ... + ) -> dict[str, object]: ... if sys.version_info >= (3, 8): - def get_args(tp: Any) -> tuple[Any, ...]: ... + def get_args(tp: Any) -> tuple[object, ...]: ... if sys.version_info >= (3, 10): @overload diff --git a/mypy/typeshed/stdlib/unittest/case.pyi b/mypy/typeshed/stdlib/unittest/case.pyi index aa04e16d6..a93ec4b00 100644 --- a/mypy/typeshed/stdlib/unittest/case.pyi +++ b/mypy/typeshed/stdlib/unittest/case.pyi @@ -6,7 +6,7 @@ from collections.abc import Callable, Container, Iterable, Mapping, Sequence, Se from contextlib import AbstractContextManager from re import Pattern from types import TracebackType -from typing import Any, AnyStr, ClassVar, Generic, NamedTuple, NoReturn, Protocol, SupportsAbs, SupportsRound, TypeVar, overload +from typing import Any, AnyStr, ClassVar, Generic, NamedTuple, NoReturn, Protocol, SupportsAbs, SupportsRound, TypeVar, _UnionInstance, overload from typing_extensions import ParamSpec, Self, TypeAlias from warnings import WarningMessage @@ -72,7 +72,7 @@ class _SupportsAbsAndDunderGE(SupportsDunderGE[Any], SupportsAbs[Any], Protocol) # We can't import it from builtins or pytype crashes, # due to the fact that pytype uses a custom builtins stub rather than typeshed's builtins stub if sys.version_info >= (3, 10): - _ClassInfo: TypeAlias = type | UnionType | tuple[_ClassInfo, ...] + _ClassInfo: TypeAlias = type | _UnionInstance | UnionType | tuple[_ClassInfo, ...] else: _ClassInfo: TypeAlias = type | tuple[_ClassInfo, ...]