I did not think about the possibility that tuples could have been read without the quotes. However, I traced the original sentence back to PEP 484 PEP 484 – Type Hints | peps.python.org and to me it didn’t feel like the spec meant tuples (in the form of type[A, B])
PEP 484 mentions Tuple, so my first guess was correct.
Any other special constructs like Tuple or Callable are not allowed as an argument to Type.
Then typing generics were replaced by simple builtin types. But there is an important difference between Tuple and tuple – the latter can be used as a base class, in issublass() and isinstance(), but the former cannot. This is why replacing Tuple with tuple caused confusion – the wording is now ambiguous, it can be interpreted in multiple ways, as was shown above.
I would simply add []: “tuple[] or Callable[]”. type[] is already used, so there is precedence.
The proposed change is probably an improvement but there’s room for a broader improvement. Instead of saying what isn’t allowed inside type[], we should specify exactly what is allowed. Then it will follow naturally that anything that isn’t explicitly allowed is outside the spec.
type[A | B] where A and B are both classes. Presumably type[Union[A, B]] is also OK but it doesn’t say that.
type[Any]
type[T] where T is a TypeVar. (This is said to only work “when annotating the first argument of a class method”—which doesn’t match reality.)
Meanwhile the the grammar at Type annotations — typing documentation only lists Any and name, “where name must refer to a valid in-scope class or TypeVar”. I forgot about type[A | B] when I wrote that.
Self is not mentioned but the section for Self does use type[Self].
My reading is therefore that something like type[list[int]] is disallowed. Current type checkers allow it (e.g. mypy, pyright and ty behave equivalently). I’m not sure though what this is supposed to mean, as there’s only one list class at runtime, so how is type[list[int]] a different type from type[list[str]]?
The proposed wording just says that special forms are disallowed. I am not sure everyone means the same thing when they say the term “special form”, but the spec defines this term as a name (like Any) that has a special meaning in the type system. Under that meaning, it’s indeed true that most special forms (other than Any and Self) are not allowed inside type[], but a bunch of things that are not special forms probably should not be allowed either—such as type[Literal[1]].
However, existing type checkers are rather more liberal about what they accept inside type. I tried this script on four type checkers and got these results:
Feature
mypy
pyright
ty
pyrefly
type[Literal[1]]
type[LiteralString]
type[Never]
type[list[int]]
type[TD]
type[Self]
type[TypedDict]
(where TD is a TypedDict class)
I don’t even know what some of these would mean; for example, type[Literal[1]] is allowed by pyright but I haven’t found any way to get a type[Literal[1]] that it would allow.
I like the idea of a broader fix here. As you’ve highlighted, there are numerous inconsistencies, omissions, and ambiguities in this part of the spec.
These variants arguably make sense when considering subclasses. For example, class MyIntList(list[int]): ... is assignable to type[list[int]] but not type[list[str]].