From bb3969cb0a2172db133643a871e021cdc63efe00 Mon Sep 17 00:00:00 2001 From: KotlinIsland Date: Thu, 4 Jan 2024 08:38:19 +1000 Subject: [PATCH 1/2] WIP TypeForm --- mypy/messages.py | 4 ++-- mypy/typeanal.py | 8 +++++++- mypy/types.py | 28 ++++++++++++++++++++++++-- mypy/typeshed/stdlib/builtins.pyi | 5 +++-- mypy/typeshed/stdlib/typing.pyi | 23 ++++++++++++++------- mypy/typeshed/stdlib/unittest/case.pyi | 4 ++-- 6 files changed, 56 insertions(+), 16 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 0871792ae..66cb25d31 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -94,7 +94,7 @@ UnionType, UnpackType, get_proper_type, - get_proper_types, + get_proper_types, TypeFormType, SpecialFormType, ) from mypy.typetraverser import TypeTraverserVisitor from mypy.util import plural_s, unmangle @@ -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..0f2a32c71 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -93,7 +93,7 @@ flatten_nested_tuples, flatten_nested_unions, get_proper_type, - has_type_vars, + has_type_vars, SpecialFormType, TypeFormType, ) from mypy.types_utils import is_bad_type_type_item from mypy.typevars import fill_typevars @@ -584,6 +584,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..7e929e31d 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, @@ -3277,6 +3279,27 @@ def deserialize(cls, data: JsonDict) -> Type: assert data[".class"] == "TypeType" 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 +3752,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, ...] From e099d2ccd09da1563f011f840d0447d4a1b6b8e5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 12:26:18 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/messages.py | 2 +- mypy/typeanal.py | 8 +++++--- mypy/types.py | 5 ++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 66cb25d31..5ab8e3815 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -94,7 +94,7 @@ UnionType, UnpackType, get_proper_type, - get_proper_types, TypeFormType, SpecialFormType, + get_proper_types, ) from mypy.typetraverser import TypeTraverserVisitor from mypy.util import plural_s, unmangle diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 0f2a32c71..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, @@ -93,7 +95,7 @@ flatten_nested_tuples, flatten_nested_unions, get_proper_type, - has_type_vars, SpecialFormType, TypeFormType, + has_type_vars, ) from mypy.types_utils import is_bad_type_type_item from mypy.typevars import fill_typevars @@ -584,10 +586,10 @@ 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 + 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 + 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": diff --git a/mypy/types.py b/mypy/types.py index 7e929e31d..f7966ad2c 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -3279,8 +3279,10 @@ def deserialize(cls, data: JsonDict) -> Type: assert data[".class"] == "TypeType" return TypeType.make_normalized(deserialize_type(data["item"])) + class TypeFormType(TypeType): name = "TypeForm" + def serialize(self) -> JsonDict: return {".class": "TypeFormType", "item": self.item.serialize()} @@ -3289,9 +3291,10 @@ def deserialize(cls, data: JsonDict) -> Type: assert data[".class"] == "TypeFormType" return cls(deserialize_type(data["item"])) -class SpecialFormType(TypeType): +class SpecialFormType(TypeType): name = "SpecialForm" + def serialize(self) -> JsonDict: return {".class": "SpecialFormType", "item": self.item.serialize()}