Spec change: Clarify that `tuple` should not be prohibited as an argument to `type`

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.

The current spec at Special types in annotations — typing documentation says the following are allowed:

  • type[C] where C is a class.
  • 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]] :cross_mark: :white_check_mark: :cross_mark: :white_check_mark:
type[LiteralString] :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark:
type[Never] :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark:
type[list[int]] :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark:
type[TD] :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark:
type[Self] :white_check_mark: :white_check_mark: :white_check_mark: :white_check_mark:
type[TypedDict] :cross_mark: :cross_mark: :cross_mark: :cross_mark:

(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.

3 Likes