mypy icon indicating copy to clipboard operation
mypy copied to clipboard

`.get(key)` should narrow final TypedDicts

Open roganov opened this issue 1 year ago • 2 comments

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 avatar Apr 02 '24 10:04 roganov

@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.

erictraut avatar Apr 02 '24 14:04 erictraut

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.

roganov avatar Apr 02 '24 15:04 roganov