Skip to content

TypeForm #605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@
ProperType,
RawExpressionType,
RequiredType,
SpecialFormType,
SyntheticTypeVisitor,
TrivialSyntheticTypeTranslator,
TupleType,
Type,
TypeAliasType,
TypedDictType,
TypeFormType,
TypeGuardType,
TypeList,
TypeOfAny,
Expand Down Expand Up @@ -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(
Expand Down
31 changes: 29 additions & 2 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3222,6 +3222,8 @@ class TypeType(ProperType):
assumption).
"""

name = "type"

__slots__ = ("item",)

# This can't be everything, but it can be a class reference,
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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"<placeholder {t.fullname}>"
Expand Down
5 changes: 3 additions & 2 deletions mypy/typeshed/stdlib/builtins.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,7 +52,7 @@ from typing import ( # noqa: Y022
SupportsFloat,
TypeVar,
overload,
type_check_only,
type_check_only, Union,
)
from typing_extensions import (
Concatenate,
Expand Down Expand Up @@ -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, ...]

Expand Down
23 changes: 16 additions & 7 deletions mypy/typeshed/stdlib/typing.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions mypy/typeshed/stdlib/unittest/case.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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, ...]

Expand Down