Idea: Simpler and More Expressive Type Annotations

@Jelle suggested a similar approach to expose the AST in Inlined typed dicts and typed dict comprehensions - #13 by Jelle, in this case by having <...> as an explicit marker that would return an object allowing AST introspection.

Here are some thoughts about this approach of exposing the AST:

In both cases, new syntax for the new DSL can’t be used because the AST is being exposed (and new syntax would require.. new AST nodes).

Pros/cons of using the explicit <...> markers:

  • + Can be used everywhere, not only in type annotations (e.g. cast(<...>, var)).
  • - Renders quite nice (imo) for inline dictionaries (and maybe tuples): <{k: int}>, <(int, str)> as my proposal was only scoped to typed dicts; but doesn’t fit well for a general purpose [1].
  • Requires a syntax change.

Pros/cons of having the DSL “implicit”:

  • + No syntax change required.
  • - Cannot use it outside type annotations. There will be some confusion for users to discover that {K: int for K in ...} can be used as an annotation in, say, a function param. annotation, but not as a argument to Pydantic’s TypeAdapter or cattrs’s structure(). Apart from the third party runtime checkers, this would be more or less an issue. Lets check the valid type expressions locations:
    • cast() and assert_type() are fine, they are meant for static type checkers and the typ argument is discarded.
    • PEP 695 type vars bounds/constraints/defaults and type aliases should be fine as well, they all have an __evaluate__() implementation.
    • The type arguments of a generic class, TypedDict’s extra_items and the base type in the definition of a NewType are introspectable at runtime, and as such couldn’t make use of the new DSL; unless the annotation is stringified/a type_expr(typ: str) helper (or using the <...> marker?) is used.

I personally support this AST approach and having it implicit. I also support @Daverball’s opinion that the new DSL would be for more advanced features. Imo the limitations of the last bullet points would be pretty uncommon, especially if the DSL is only for advanced typing features.


This new AST annotation format may be disruptive though. Until now get_type_hints()/get_annotations(format=Format.VALUE) is the recommended way to get annotations at runtime. Apart from the possible NameError that can be raised, any runtime exception could happen with Format.VALUE/FORWARDREF [2]. I guess to be specified is whether the Format.AST only returns the AST representation (and the stdlib provides utilities to convert it to proper typing objects [3]), or it takes care of converting to the actual typing objects directly.


  1. e.g. for a literal type using the | operator, it feels weird having to use <'string_lit' | 1 | True> ↩︎

  2. e.g. {'key': int} | int ↩︎

  3. e.g. from {'key':int} | int to SomeEquivalentTDClass | int ↩︎

4 Likes