python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.41k stars 2.82k forks source link

Type narrowing for dict.get("key") is not None #14766

Open JakobDev opened 1 year ago

JakobDev commented 1 year ago

Bug Report

See Code and Explanation below

To Reproduce

from typing import TypedDict, Optional

class DictA(TypedDict):
    my_value: str

class DictB(TypedDict, total=False):
    my_optional_dict: Optional[DictA]

def test(arg: DictB) -> None:
    if arg.get("my_optional_dict") is not None:
        print(arg["my_optional_dict"]["my_value"])

Playground Link

Expected Behavior According to the TypedDict, we have 3 cases for the Key my_optional_dict:

  1. The Dict did not have this Key (indicated by total=False
  2. The Value of this Key is None
  3. The Value of this Key is a DictA

If we look at the if in the testfunction, it does 2 things:

  1. Check if the Dict contains this Key
  2. Check if the value is not None

This means, that the my_optional_dict of arg is always of the type DictA, so mypy should not see a problem here.

Actual Behavior

main.py:14: error: Value of type "Optional[DictA]" is not indexable  [index]
Found 1 error in 1 file (checked 1 source file)

Your Environment

mgedmin commented 3 weeks ago

I had very similar code:

some_dict: dict = {}

if isinstance(some_dict.get('a_key'), dict):
    reveal_type(some_dict['a_key'])   # Revealed type is "Any"

rewriting it to a more verbose

some_dict: dict = {}

if 'a_key' in some_dict and isinstance(some_dict['a_key'], dict):
    reveal_type(some_dict['a_key'])   # Revealed type is "builtins.dict[Any, Any]"

suffices as a workaround, but I would much prefer the shorter expression.