# Clarifications to the typing spec regarding \`type\[\]\`

**URL:** https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596
**Category:** Typing
**Tags:** typing, typing-spec
**Created:** [May 30, 2024, 1:31pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596 "2024-05-30T13:31:21Z")
**Posts on this page:** 14
**Page:** 1

<div class="post-metadata">

### Author: ![AlexWaygood](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/alexwaygood/32/4722_2.png) [@AlexWaygood](https://discuss.python.org/u/AlexWaygood)
#### Post date: [May 30, 2024, 1:31pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/1 "2024-05-30T13:31:21Z")

</div>

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](https://typing.readthedocs.io/en/latest/spec/annotations.html#type-and-annotation-expressions) currently permits only the following constructs inside `type[]`:

```none
| <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]]` and `type[int | str]`. PEP 484 stated:

- `type[Self]`. It’s clearly specified in PEP 673 and the spec at [Generics — typing documentation](https://typing.readthedocs.io/en/latest/spec/generics.html#self) that `type[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](https://typing.readthedocs.io/en/latest/spec/qualifiers.html#id4) states that:

## Other problems with the spec as written

There are also several problems with the spec at [Special types in annotations — typing documentation](https://typing.readthedocs.io/en/latest/spec/special-types.html#type) where it states:

> Any other special constructs [other than `Union`] like `tuple` or `Callable` 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:

```py
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:

```py
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.

1. 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 than `type[]` in general (type checkers generally unsoundly assume that for the set of class objects described by `type[T]`, all items in the set have the same constructor signature as `T`, which is incorrect). So I would vote “yes” for this question.
2. 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 for `x` here – e.g. the synthesized signature in this instance would be `def __new__ (cls, tup: tuple[int, int], /) -> Self`. If we don’t want to go down that route, I think we should ban `type[tuple[int, int]]`. On the other hand, I think `x: type[tuple[int, ...]] = tuple` should probably be allowed; again, it seems no more unsound than `type[T]` for any other `T`.
3. Should it be allowed to use a `TypedDict` class as the argument to `type[]`? 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 what `type[]` was originally intended for; whether or not this should be allowed, the spec should state it clearly.
4. Should it be allowed to use a `Protocol` class as the argument to `type[]`? I know of [at least one place where we do so in typeshed](https://github.com/python/typeshed/blob/321c0ce75e458d14f07ae7b0860001f854450327/stdlib/dataclasses.pyi#L217), so it’s not without its use cases. But at the very least, as with `TypedDict` 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`](https://discuss.python.org/t/inconsistencies-between-type-and-type/37404/1), 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.)

---

<div class="post-metadata">

### Author: ![Jelle](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jelle/32/1049_2.png) [@Jelle](https://discuss.python.org/u/Jelle)
#### Post date: [May 30, 2024, 1:38pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/2 "2024-05-30T13:38:02Z")

</div>

> [@AlexWaygood](#):
>
> `type[Optional[int]]`

If this is allowed, that implies that `type[None]` should also be allowed (and should be listed separately as an allowed argument to `type[]`). Mypy indeed allows it, treating it as equivalent to `type[types.NoneType]`: [mypy Playground](https://mypy-play.net/?mypy=latest&python=3.12&gist=39fd544037b37c0080fdf874770350e1). Pyright does too and behaves the same on this sample.

Otherwise, agree with your writeup.

---

<div class="post-metadata">

### Author: ![AlexWaygood](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/alexwaygood/32/4722_2.png) [@AlexWaygood](https://discuss.python.org/u/AlexWaygood)
#### Post date: [May 30, 2024, 1:46pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/3 "2024-05-30T13:46:11Z")

</div>

> [@Jelle](#):
>
> > [@AlexWaygood](#):
> >
> > `type[Optional[int]]`
> 
> If this is allowed, that implies that `type[None]` should also be allowed (and should be listed separately as an allowed argument to `type[]`). Mypy indeed allows it, treating it as equivalent to `type[types.NoneType]`: [mypy Playground](https://mypy-play.net/?mypy=latest&python=3.12&gist=39fd544037b37c0080fdf874770350e1). Pyright does too and behaves the same on this sample.

Great point; I hadn’t properly considered the question of `type[None]`. This should have gone into the final section of debatable questions rather than the first section of hopefully-uncontroversial points.

I weakly prefer mypy’s behaviour here. Treating `type[None]` as `type[types.NoneType]` (as mypy does) makes sense, since when we use `None` in type annotations, we actually _mean_ `types.NoneType`; mypy’s interpretation seems to follow naturally from that. On Python 3.10+, you can say what you mean more clearly by using `type[types.NoneType]`, but that option isn’t available for people using older versions of Python.

---

<div class="post-metadata">

### Author: ![AlexWaygood](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/alexwaygood/32/4722_2.png) [@AlexWaygood](https://discuss.python.org/u/AlexWaygood)
#### Post date: [May 30, 2024, 3:43pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/4 "2024-05-30T15:43:10Z")

</div>

One further question that I missed when writing this up is: should `type[Never]` (and `type[NoReturn]` be allowed in annotation expressions? Although it appears to be forbidden both by the typing spec’s prose describing the `type[]` special form and the typing spec’s formal grammar for annotation expressions, both mypy and pyright currently allow annotating a variable with `type[Never]`. It is also possible to get at least mypy to infer a variable as having type `type[Never]` in some situations where it fails to solve a `TypeVar`: [mypy Playground](https://mypy-play.net/?mypy=master&python=3.12&gist=a63fab5a130f18832e44bb03b563328f).

Nonetheless, I would argue that `type[Never]` and `type[NoReturn]` should be forbidden in type annotations. I think the logical interpretation of `type[Never]` as a type is “the empty set of values, where all values would be class objects if there were actually any values in the set”. As a philosophical concept, that feels indistinguishable from `Never` and somewhat incoherent, so I see no use in changing the spec to explicitly allow `Never` or `NoReturn` in annotation expressions. (This does not forbid type checkers from having an internal representation of `type[Never]`, though it _does_ mean that they should treat a value inferred as having type `type[Never]` in exactly the same way as they would treat a value inferred as having type `Never`.)

I therefore also propose that we add tests to the conformance suite that ensure that type checkers emit an error complaining about an invalid annotation if an object is annotated as having type `type[Never]` or `type[NoReturn]`.

---

<div class="post-metadata">

### Author: ![erictraut](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/erictraut/32/15190_2.png) [@erictraut](https://discuss.python.org/u/erictraut)
#### Post date: [May 30, 2024, 4:03pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/5 "2024-05-30T16:03:32Z")

</div>

Would your logic about `Never` apply more generally to type arguments for generic types? If we want to disallow `type[Never]`, shouldn’t we also disallow `list[Never]`? What about `Union[int, Never]`? Clearly we shouldn’t disallow `Callable[[Never], None]` or `Callable[[Any], NoReturn]` because those have legit use cases.

My point is that if we want to disallow `type[Never]`, it seems like we should try to define a more general principle for why it would be disallowed and then apply this same principle everywhere. I don’t know if that’s worth it though. Maybe we just leave it unspecified since no one has (to my knowledge) ever run into a problem with `type[Never]`?

---

<div class="post-metadata">

### Author: ![AlexWaygood](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/alexwaygood/32/4722_2.png) [@AlexWaygood](https://discuss.python.org/u/AlexWaygood)
#### Post date: [May 30, 2024, 4:10pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/6 "2024-05-30T16:10:02Z")

</div>

> [@erictraut](#):
>
> Maybe we just leave it unspecified since no one has (to my knowledge) ever run into a problem with `type[Never]`?

I don’t think this is an option, since the typing spec already specifies in the grammar at [Type annotations — typing documentation](https://typing.readthedocs.io/en/latest/spec/annotations.html#type-and-annotation-expressions) that `type[Never]` is _not_ a legal annotation expression, and this is also implied by the prose description of `type[]` where the typing spec states that “any other special constructs [other than `Union`] like `tuple` and `Callable` are not valid as a type”. Some change must be made here one way or the other: both mypy and pyright are, strictly speaking, in violation of the spec as it is currently written when they allow variables to be annotated with `type[Never]`.

Nonetheless, I agree that this is both a low-priority issue and one that should perhaps be considered in the broader context of when and if `Never` should be valid as an argument to a generic type. I’m fine with deferring the question for now.

---

<div class="post-metadata">

### Author: ![erictraut](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/erictraut/32/15190_2.png) [@erictraut](https://discuss.python.org/u/erictraut)
#### Post date: [May 30, 2024, 4:25pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/7 "2024-05-30T16:25:56Z")

</div>

> [@AlexWaygood](#):
>
> Some change must be made here one way or the other

Yeah, I agree that the grammar should be fixed. My suggestion is to simply remove `<type>` as a special case in the grammar and treat it just like any other generic class. After all, `type` is a class, not a special form. That would mean `type[Never]` would fall under the same grammar rule as, for example, `list[Never]`. There are additional semantic rules and limitations that apply for `type`, but those can be spelled out specifically for `type` outside of the grammar.

---

<div class="post-metadata">

### Author: ![AlexWaygood](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/alexwaygood/32/4722_2.png) [@AlexWaygood](https://discuss.python.org/u/AlexWaygood)
#### Post date: [May 30, 2024, 4:32pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/8 "2024-05-30T16:32:09Z")

</div>

I feel somewhat reluctant to remove the `<type>` special case from the grammar, because there are so many constructs that are legal in type expressions generally but are not legal inside `type[]`, and I think the grammar is a really useful way of clarifying the vast majority of these.

As a compromise position, perhaps for now we could update the grammar to reflect the “reality on the ground”, which is that type checkers view `type[Never]` as a legal annotation, but with the understanding that this could be reconsidered in the future.

---

<div class="post-metadata">

### Author: ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)
#### Post date: [May 30, 2024, 7:52pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/9 "2024-05-30T19:52:59Z")

</div>

I don’t see a reason to forbid this at the grammar level, there’s probably a reasonable interpretation for this (the one I see is that it’s a type that can’t exist), and it might be useful in either error messages or places where tests of exhaustiveness is happening.

`type[Never]` could be the result of `type[int | str]` after successive negative TypeIs checks, and it could be useful to leave `type[Never]` as that rather than instantly turning it to `Never` in some displays.

---

<div class="post-metadata">

### Author: ![AlexWaygood](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/alexwaygood/32/4722_2.png) [@AlexWaygood](https://discuss.python.org/u/AlexWaygood)
#### Post date: [May 31, 2024, 10:33am UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/10 "2024-05-31T10:33:15Z")

</div>

> [@mikeshardmind](#):
>
> there’s probably a reasonable interpretation for this (the one I see is that it’s a type that can’t exist)

What practical implications would this have as compared to the more general type `Never`? What differences would you expect to see in how type checkers treat variables with the two types respectively?

> [@mikeshardmind](#):
>
> `type[Never]` could be the result of `type[int | str]` after successive negative TypeIs checks, and it could be useful to leave `type[Never]` as that rather than instantly turning it to `Never` in some displays.

Sure – as I mentioned above, I don’t want to forbid type checkers having an internal representation of this type or even displaying it to users as an inferred type if they think it’s a helpful type for users. I think it naturally arises as an inferred type from situations where a type parameter is inferred as being impossible to solve, and probably in other situations I haven’t thought of as well.

There are many types that Python’s type checkers can (and must) have internal representations for, however, which are nonetheless inexpressible or illegal as type annotations. This debate is narrowly concerned with whether it makes sense to allow users to annotate a variable as being of `type[Never]`: does it ever make sense to do so? What would that imply? How would it be treated differently from `Never` by a type checker?

---

<div class="post-metadata">

### Author: ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)
#### Post date: [May 31, 2024, 4:27pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/11 "2024-05-31T16:27:27Z")

</div>

> [@AlexWaygood](#):
>
> What practical implications would this have as compared to the more general type `Never`? What differences would you expect to see in how type checkers treat variables with the two types respectively?

The practical interpretation I’m imagining is that `type[Never]` should behave identically to `Never` in the type system.

> [@AlexWaygood](#):
>
> This debate is narrowly concerned with whether it makes sense to allow users to annotate a variable as being of `type[Never]`: does it ever make sense to do so?

I can see an argument that if `type[Never]` should be identical to `Never`, then just tell users to type `Never` instead. However, I think this is better enforced by an opinionated linter, or even a message in a type checker (something like “info: This is equivalent to Never”), and not at the grammar level if there’s a reason that type checkers might use it in a display.

It would be a good goal if all types that arise from a type checker’s diagnostic messages are valid for a user to copy. While this isn’t the case currently, and there are uphill challenges to have that be the case in more places, the fewer internal types not denotable by users the better. It’s still something I’d prefer to help with user intuition and being able to use what they get as useful diagnostic output in other places.

This one in particular is not a strong preference, if other people feel like it’s definitely better to not have this in the type system, I’ve said what I intend and won’t argue this one.

---

<div class="post-metadata">

### Author: ![fonini](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/fonini/32/20258_2.png) [@fonini](https://discuss.python.org/u/fonini)
#### Post date: [June 4, 2024, 11:46pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/12 "2024-06-04T23:46:53Z")

</div>

> [@AlexWaygood](#):
>
> > [@mikeshardmind](#):
> >
> > `type[Never]` could be the result of `type[int | str]` after successive negative TypeIs checks, and it could be useful to leave `type[Never]` as that rather than instantly turning it to `Never` in some displays.
> 
> Sure – as I mentioned above, I don’t want to forbid type checkers having an internal representation of this type or even displaying it to users as an inferred type if they think it’s a helpful type for users. I think it naturally arises as an inferred type from situations where a type parameter is inferred as being impossible to solve, and probably in other situations I haven’t thought of as well.

I’d like to argue in favor of allowing the user to explicitly write `type[Never]`. Continuing with the `type[str | int]` example:

```python
t: type[str | int]
if issubclass(t, str):
    assert_type(t, type[str])
    ...
elif issubclass(t, int):
    assert_type(t, type[int])
    ...
else:
    assert... # What do I write here?

```

In the last line, I would personally write `assert_never(t)`, but I guess it would be legitimate if someone preferred `assert_type(t, type[Never])` for consistency.

> [@erictraut](#):
>
> Would your logic about `Never` apply more generally to type arguments for generic types? If we want to disallow `type[Never]`, shouldn’t we also disallow `list[Never]`? What about `Union[int, Never]`?

I’d also like to argue in favor of allowing `Never` as a type argument for generics. I’ve used `Sequence[Never]` and `Mapping[Any, Never]` when I wanted to specify that the container is empty (e.g. a variable of type `Sequence[Never]` can safely be used as argument to functions that want to iterate over the sequence and do something to each element; also, a function that accepts `Sequence[Never]` promises not to access the elements.)

As a last note, forbidding `Union[int, Never]` feels completely unnecessary – type checkers should just treat it as equivalent to `int`, and let linters complain about the redundancy.

---

<div class="post-metadata">

### Author: ![InSync](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/insync/32/12531_2.png) [@InSync](https://discuss.python.org/u/InSync)
#### Post date: [January 21, 2025, 7:41pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/13 "2025-01-21T19:41:28Z")

</div>

According to the proposal, this is invalid:

```python
type[Literal['']]

```

In that case, are these valid?

```python
type A[T] = type[T]

# Somewhere else
def f(v: A[Literal['']]) -> None: ... # ???

```

```python
def g[T](v: T) -> type[T]: ...

# Another place
s: Final = ''
reveal_type(g(s)) # ???

```

---

<div class="post-metadata">

### Author: ![carljm](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/carljm/32/959_2.png) [@carljm](https://discuss.python.org/u/carljm)
#### Post date: [February 1, 2025, 5:29pm UTC](https://discuss.python.org/t/clarifications-to-the-typing-spec-regarding-type/54596/14 "2025-02-01T17:29:06Z")

</div>

> [@InSync](#):
>
> In that case, are these valid?
> 
> `type A[T] = type[T]`

I don’t think that type alias should be valid, without a bound on `T` guaranteeing that it is a type which is valid as argument to `type`.
