Skip to content

No type narrowing for in on dict.keys() #13360

Closed
@matthewhughes934

Description

@matthewhughes934

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 and mypy 0.980+dev.e69bd9a7270daac8db409e8d08400d9d32367c32 (compiled: no) (current master)
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions