TypeForm: Spelling for a type annotation object at runtime

@erictraut thanks for taking the time to detail several questions RE the “value expression + type expression mixing” issue.

TypeForm[T] values that already fit in type[T]

Conceptually TypeForm[T] and type[T] behave very similarly in my mind. Calling out some specific examples:

  • As a value expression, Movie (referring to a typed dict) has type type[Movie] according to pyright. mypy says something similar:

    reveal_type(Movie)
        # pyright: "type[Movie]"
        # mypy: "def (*, title: builtins.str) -> TypedDict('typeddict.Movie', {'title': builtins.str})"
    
    def identity_type(t: type[T]) -> type[T]:
        return t
    reveal_type(identity_type(Movie))
        # pyright: "type[Movie]"
        # mypy: "type[TypedDict('typeddict.Movie', {'title': builtins.str})]"
    

TypeForm[T] values that do not fit in type[T]

For those type expressions that, when parsed as a value expression, do not currently have type type[t] (for some t), I’d like them to ideally have type TypeForm[t] instead:

  • Stringified type expressions

    I think the user would have to explicitly mark string literals that are intended to be treated as TypeForms with the TypeForm(...) syntax you recently proposed.

    • Proposal: As a value expression, TypeForm("bool") (with quotes) has a type of TypeForm[bool].
    • Proposal: As a value expression, "bool" (with quotes) continues to have type Literal["bool"].
  • Unions

    Given each operand of an x | y expression, x and y, is itself a value expression with a type:

    • Proposal: If x has type type[t1] or TypeForm[t1], and y has type type[t2] or TypeForm[t2], then x | y has type TypeForm[t1 | t2]. Otherwise x | y has type UnionType.
  • Bare Forms

    Currently pyright and mypy disagree on the type of various special forms, with mypy mostly saying they are type object:

    reveal_type(Any)
      # mypy: "builtins.object"
      # pyright: "Any"
    reveal_type(Never)
      # mypy: "typing._SpecialForm"
      # pyright: "Never"
    reveal_type(Literal["words"])
      # mypy: "builtins.object"
      # pyright: "type[Literal['words']]"
    reveal_type(Callable[[], None])
      # mypy: "builtins.object"
      # pyright: "type[() -> None]"
    
    • Proposal: As a value expression, Any has a type TypeForm[Any].
    • Proposal: As a value expression, Never has a type TypeForm[Never].
    • etc
  • None

    Users writing None are almost certainly are intending to spell the None value rather than the None type. So in the rare cases that the None type is intended, users would need to use the TypeForm(None) syntax to spell it:

    • Proposal: As a value expression, TypeForm(None) has a type of TypeForm[None].
    • Proposal: As a value expression, None continues to have a type of None.

TypeForm(T) as an explicit way to spell an object T with type TypeForm[T]

I think it could be useful to have an explicit spelling of TypeForm(T) to mean “T when treated as a value”. I think this explicit spelling may actually be necessary to disambiguate a few cases (mentioned above).

At runtime the callable TypeForm() would just return its single argument unchanged. Type checkers could recognize TypeForm(t) and specially parse the t inside as a type expression, and give it type TypeForm[t].

Fin

Thoughts @erictraut ?