The typing spec is less clear than it could be when it comes to indexing a TypedDict with a variable whose type is not a literal string. Here’s what it says:
A key that is not a literal should generally be rejected, since its value is unknown during type checking, and thus can cause some of the above violations.
The use of the word “generally” seems to imply that there are cases where this rule does not need to be enforced, but it doesn’t explain what exceptions exist. Does this allow individual type checkers to decide if and when to enforce this rule?
Mypy (which was the reference implementation for PEP 589), is stricter in this regard than pyright. Pyright’s current behavior was implemented based on feedback from both pyright and mypy users. There are a number of common idioms in Python that are safe and lead to false positive errors if this rule is enforced. Here are a few examples:
def func1(d: TD):
for x in d:
print(f"{x}: {d[x]}") # Mypy error: TypedDict key must be a string literal
def func2(d: TD, s: str):
if s in d:
print(d[s]) # Mypy error: TypedDict key must be a string literal
This enforcement extends to methods other than __getitem__
, __setitem__
, and __delitem__
. It also affects the synthesized methods pop
and setdefault
.
The behavioral difference between mypy and pyright hasn’t been an issue before now, but a recent PR proposes to add test cases to the conformance test suite that interpret the current language in the spec in a strict manner. If this interpretation is codified in the conformance tests, I’ll feel compelled to make pyright adopt this stricter interpretation. I can say from experience that this change will be unpopular among pyright users.
Before going down that road, I wanted to get input from the typing community and see if there is consensus on this topic — and to see if we can crisp up the language in the spec to eliminate ambiguity.
I think there are two options:
- Mandate Strictness: Modify the spec to simply remove the word “generally”. (Can anyone think of a case where it should be exempted other than
Any
?) The wording should also probably be more precise than “not a literal” sinceLiteralString
is a literal but should not be exempt from this stricter interpretation of the rule. - Optional Enforcement: Modify the spec to allow compliant type checkers to choose whether to enforce this rule by replacing “generally should” with “may”. This would allow both mypy and pyright to retain their current behaviors.
My personal preference is option 2 because I think pyright’s current behavior strikes a good balance between identifying common bugs and allowing common use cases without incurring false positive errors. However, if there is consensus around option 1, then I will honor that decision and modify pyright accordingly.