Reading through the typing spec and conformance test suite, I have discovered a number of inconsistencies and ambiguities regarding type[]
which I believe should be clarified. Some of these I would consider uncontroversial bugs in the spec; others require discussion before we can update the specification. I list the uncontroversial ones first before detailing the ambiguous points at the end which I believe may require discussion.
Inconsistencies in the grammar for type expressions
The grammar at Type annotations — typing documentation currently permits only the following constructs inside type[]
:
| <type> '[' <Any> ']'
| <type> '[' name ']'
(where name must refer to a valid in-scope class
or TypeVar)
This would incorrectly imply that the following are invalid type expressions:
-
type[Union[str, int]]
,type[Optional[int]]
andtype[int | str]
. PEP 484 stated:Note that it is legal to use a union of classes as the parameter for
Type[]
, as in:def new_non_team_user(user_class: Type[Union[BasicUser, ProUser]]): user = new_user(user_class) ...
And this wording has been carried over into the spec at Special types in annotations — typing documentation. The grammar should reflect that these are valid type expressions.
-
type[Self]
. It’s clearly specified in PEP 673 and the spec at Generics — typing documentation thattype[Self]
should be permitted. -
type["quoted_forward_reference"]
. I’m not sure it’s anywhere clearly stated that this should be permitted, but I think it follows naturally from various principles implied by the spec as a whole. -
type[Annotated[int, "metadata]]
. The spec at Type qualifiers — typing documentation states that:As with most special forms,
Annotated
is not type compatible withtype
ortype[T]
:v1: type[int] = Annotated[int, ""] # Type error SmallInt: TypeAlias = Annotated[int, ValueRange(0, 100)] v2: type[Any] = SmallInt # Type error
However, it does not specify that
Annotated[]
should be disallowed insidetype[]
. Both mypy and pyright allowtype[Annotated[int, "metadata"]]
, and I think it follows naturally from reading the spec that this should be allowed.
Other problems with the spec as written
There are also several problems with the spec at Special types in annotations — typing documentation where it states:
Any other special constructs [other than
Union
] liketuple
orCallable
are not allowed as an argument to type.
Firstly, this would appear to disallow type[Self]
and type[Annotated[int, "metadata"]]
; but, as discussed above, those should probably be permitted type expressions. Secondly, it would appear to disallow type[tuple]
– but if this is disallowed, then it is impossible to express in the type system “the class object tuple
, or any subclass of that class object”. It looks to me like this sentence was copied over from PEP 484’s equivalent sentence, but the syntax in the example has been incorrectly modernised. PEP 484 mentions "any other special constructs like Tuple
and Callable
. But whereas typing.Tuple
was and is unambiguously a special form in the type system, builtins.tuple
serves a dual role following PEP 585: it is simultaneously a special form and a concrete class. As a concrete class, I think it should be valid as an argument to type[]
.
Suggested improvements to the conformance suite
I suggest that we enhance the conformance suite to ensure that type checkers allow the following constructs:
type[Union[int, str]]
type[int | str]
type[Optional[int]]
type[Annotated[int, "metadata"]]
type[int | Optional[Annotated[str, "metadata"]]]
type[type]
type[tuple]
type["forward_reference"]
(N.B. type[Self]
is already tested in the conformance suite tests for Self
.)
And that type checkers disallow the following constructs:
type[Protocol]
type[Generic]
type[TypedDict]
type[NamedTuple] # a factory function at runtime, not a class; this is unsound
type[Literal]
type[Literal[""]]
type[Unpack]
type[Unpack[Ts]]
type[LiteralString]
type[type[int]]
type[Callable]
type[Callable[[int], str]]
type[Tuple]
def foo(x: object) -> type[TypeGuard[str]]: ...
(N.B. x: type[int] = Annotated[int, ""]
is already tested in the conformance suite tests for Annotated
as something to be disallowed.)
Further ambiguities in the spec
The following questions all require discussion before we decide how to update the spec. I don’t think they would need to be clarified in the same PR as the above proposed clarifications.
- Should parameterised classes be acceptable as an argument to
type
? E.g.x: type[list[int]] = list
. I can’t see how this is any more unsound thantype[]
in general (type checkers generally unsoundly assume that for the set of class objects described bytype[T]
, all items in the set have the same constructor signature asT
, which is incorrect). So I would vote “yes” for this question. - Should parameterised tuples be acceptable as an argument to
type
? E.g.x: type[tuple[int, int]] = tuple
. I think the only way to make this vaguely sound would be to require that type checkers synthesize an accurate constructor signature forx
here – e.g. the synthesized signature in this instance would bedef __new__(cls, tup: tuple[int, int], /) -> Self
. If we don’t want to go down that route, I think we should bantype[tuple[int, int]]
. On the other hand, I thinkx: type[tuple[int, ...]] = tuple
should probably be allowed; again, it seems no more unsound thantype[T]
for any otherT
. - Should it be allowed to use a
TypedDict
class as the argument totype[]
? Currently both mypy and pyright permit this. I can see some use cases for it – if you want to introspect a TypedDict class’s__required_keys__
or__optional_keys__
class variables, for example. But it’s perhaps outside the scope of whattype[]
was originally intended for; whether or not this should be allowed, the spec should state it clearly. - Should it be allowed to use a
Protocol
class as the argument totype[]
? I know of at least one place where we do so in typeshed, so it’s not without its use cases. But at the very least, as withTypedDict
classes, this is underspecified. Whether or not we decide that this should be allowed, the spec should give a clear statement on the matter.
Previous discussion on type[]
in the spec
In Inconsistencies between `Type` and `type`, we previously discussed (and came to a consensus) on inconsistencies between type
and Type
, and the meaning of type[Any]
in a type expression. The outcome of that discussion has yet to be reflected in the typing spec; those updates should also be made. (The points being made in this thread are distinct to the points discussed in the earlier thread.)