Allowing TypedDict items to be defined conditionally via statically-known conditions

The typing spec currently prohibits the body of a (class-statement-defined) TypedDict from containing any statements other than field_name: annotation lines:

The class body should only contain lines with item definitions of the form key: value_type, optionally preceded by a docstring.

I think this is overly and unnecessarily restrictive. Specifically, I think this TypedDict definition should be accepted:

class TD(TypedDict):
    x: int
    if sys.version_info >= (3, 12):
        y: str

With the result that the y item exists if checking on Python 3.12+, and does not exist otherwise.

Today mypy and pyright both emit an error on this definition. Mypy then considers the item to never exist; pyright still (despite the error) treats the item as existing conditionally based on Python version. Pyrefly and ty both support the conditional definition with no error.

I would like to propose a typing spec change that would allow if statements in the body of a TypedDict to create conditionally-defined items, based on the same statically-known conditions that a type-checker understands anywhere else.

The effect of this on current type checkers would be greatest for mypy; it should start to support understanding such items as conditionally existing. (I would guess this shouldn’t be too difficult, since it already supports conditionally-defined attributes on simple classes, but I don’t know the mypy codebase.) Pyright would simply stop emitting an error, with no other change to its behavior. There would be no change to the behavior of ty and pyrefly.

10 Likes

Agree this should be allowed; it’s useful, the meaning is clear, and it’s consistent with the rest of the type system.

There’s a similar issue with NamedTuple. In that case, the spec does not say clearly what is allowed in the body, but mypy disallows sys.version_info conditions in a namedtuple body. Pyright allows this though.

One minor point is that this would create an additional feature supported by the class-based TypedDict syntax and not by the functional syntax. If you use the functional syntax, you’d have to repeat the entire TypedDict definition in the two branches of the if. I don’t think that’s a blocker, just wanted to note it.

4 Likes

I think this should be consistently allowed or disallowed in both NamedTuple and TypedDict, so I would support broadening this proposal to clarify that it is allowed for both.

I think it’s fine for the class syntax to allow some things that don’t have a direct parallel in the functional syntax.

4 Likes

I’m in favor of removing this limitation in the spec. The only reason I added the check in pyright was to conform with the spec. I’m happy to delete the check.

1 Like

It seems fine to remove the restriction. I don’t think it will be hard to implement this in mypy.

1 Like

About the functional way to spell a TypedDict:
In this reply in the tread about pass expressions, we were introduced (at least I’ve never heard of it before) to the idea of conditional key - value pairs in dictionaries. Said syntax could be used to do the same in TypedDicts too.

TD = TypedDict("TD", {"x": int, "y": str if sys.version_info >= (3, 12) else pass})

Let’s have that conversation after that syntax is actually added to Python.

1 Like