`.get(key)` should narrow final TypedDicts
in keyword narrowing was implemented for TypedDicts, but not .get(). I understand that it may be hard to implement for arbitrary default arguments of .get(key, default), but the .get(key) variable, without a default value, can be useful as well.
Bug Report
To Reproduce
import typing
@typing.final
class Node(typing.TypedDict):
name: str
children: list[typing.Union['Node', 'Leaf']]
@typing.final
class Leaf(typing.TypedDict):
name: str
x: Node | Leaf
### INCORRECT ERROR
if x.get('children'):
x['children'] # error: TypedDict "Leaf" has no key "children"
### CORRECT
if 'children' in x:
x['children']
Expected Behavior
No error
Actual Behavior
error: TypedDict "Leaf" has no key "children" [typeddict-item]
Your Environment
- Mypy version used: 1.9.0
- Mypy command-line flags: non
- Mypy configuration options from
mypy.ini(and other config files):
warn_redundant_casts = True
warn_unused_ignores = True
warn_no_return = False
warn_return_any = True
disallow_untyped_defs = True
disallow_any_generics = True
- Python version used: Python 3.12.1
@roganov, a TypedDict defines a structural type, similar to a protocol. That means inheritance isn't required for a type match, and it also means that @final is meaningless in this context.
There has been significant discussion about this topic in the typing forum, and the typing community has come to the conclusion that the type system must be extended to accommodate this use case. What you're looking for is a "closed" TypedDict — one that is guaranteed not to have any additional items beyond what is defined. There is a draft PEP 728 that introduces this concept to the type system.
This bug report should probably be closed, since mypy appears to be working correctly here — in accordance with the typing spec. You could open a new feature request instead that requests support for PEP 728. It's still in draft form, however, and it may still change (or be rejected).
If you would like to play with PEP 728 as it's currently defined, I've added provisional support for it in pyright.
Thanks @erictraut, it's great to see there's work in this direction.
I understand that @final is meaningless, but MyPy requires it for in narrowing to work. My issue points out a slight inconsistency in current behavior of MyPy -- given that if 'key' in d narrows, if d.get('key') should probably narrow as well.