Description
Bug Report
A an if
statement like if x in some_dict.keys()
doesn't narrow the type of x
to the type of the key of some_dict
.
This is in contrast with if x in some_dict
which does correctly narrow.
(I had a search and didn't find a similar existing discussion, apologies if this isn't the case)
To Reproduce
Run mypy
over the following:
from collections.abc import KeysView
def get_via_keys(key: str | None, data: dict[str, str]) -> str:
if key in data.keys():
# error: Incompatible return value type (got "Optional[str]", expected "str")
return key
return "value"
def get_via_keys_explicit_typing(key: str | None, data: dict[str, str]) -> str:
keys: KeysView[str] = data.keys()
if key in keys:
# error: Incompatible return value type (got "Optional[str]", expected "str")
return key
return "value"
def get_check_dict_membership(key: str | None, data: dict[str, str]) -> str:
if key in data:
# OK
return key
return "value"
Expected Behavior
mypy
correctly narrows the type in each of the if
statements and the script passes.
Actual Behavior
mypy
reports failures as:
script.py:7: error: Incompatible return value type (got "Optional[str]", expected "str")
script.py:15: error: Incompatible return value type (got "Optional[str]", expected "str")
Found 2 errors in 1 file (checked 1 source file)
Your Environment
- Mypy version used:
0.971
andmypy 0.980+dev.e69bd9a7270daac8db409e8d08400d9d32367c32 (compiled: no)
(currentmaster
) - Mypy command-line flags:
mypy <name-of-file-above>
- Mypy configuration options from
mypy.ini
(and other config files): the above was run from the root of this project (so whatever's configured there) - Python version used:
Python 3.10.5
- Operating system and version: Arch Linux (kernel 5.18.15)
The following allows get_via_keys
above to pass under mypy
diff --git a/mypy/checker.py b/mypy/checker.py
index e64cea7b4..852fe8fab 100644
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -6285,6 +6285,7 @@ def builtin_item_type(tp: Type) -> Optional[Type]:
"builtins.dict",
"builtins.set",
"builtins.frozenset",
+ "_collections_abc.dict_keys",
]:
if not tp.args:
# TODO: fix tuple in lib-stub/builtins.pyi (it should be generic).
Though I'm not sure how appropriate it would be given the note in the docs for that function:
Note: this is only OK for built-in containers, where we know the behavior of __contains__.
Also, dict_keys
is undocumented and a quick git grep --word-regexp 'dict_keys'
didn't show much usage for it outside of typeshed
.