`d or {}` where `d` is something other than builtin `dict` gets errors about `Never` for indexing and splatting
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 20.6k
- Forks
- 3.3k
- PR merge metrics
- PR metrics pending
Description
Bug Report
When using d or {} where d is a subclass of dict or something that implements the SupportsKeysAndGetItem protocol can lead to errors/different behaviour in downstream code than when it is a dict directly. There errors are things indicating the Never key type of {} is causing problem:
Invalid index type "str" for "DictSubclass | dict[Never, Never]"; expected type "Never" [index]
Unpacked dict entry 0 has incompatible type "SKAGI | dict[Never, Never]"; expected "SupportsKeysAndGetItem[str, int]" [dict-item]
This is potentially the same as #17684, but felt different.
(As always, thanks for Mypy!)
To Reproduce
https://mypy-play.net/?mypy=latest&python=3.12&gist=a7075c4918e6e95f4f9429bd261aa4c0
from typing import Iterable
class DictSubclass(dict[str, int]): pass
class SKAGI: # SupportsKeysAndGetItem
def keys(self) -> Iterable[str]:
return ['foo']
def __getitem__(self, __key: str) -> int:
return 1
def dict_subclass(d: None | DictSubclass):
if d is not None: # proving the basic operations work:
d["x"] # no error
{**d} # no error
index = (d or {})["x"] # error: Invalid index type "str" for "DictSubclass | dict[Never, Never]"; expected type "Never" [index]
get = (d or {}).get("x") # error: No overload variant of "get" of "dict" matches argument type "str" [call-overload]
splat = {**(d or {})} # error: Unpacked dict entry 0 has incompatible type "DictSubclass | dict[Never, Never]"; expected "SupportsKeysAndGetItem[str, int]" [dict-item]
def skagi(d: None | SKAGI):
if d is not None: # proving the basic operations work:
d["x"] # no error
{**d} # no error
index = (d or {})["x"] # error: Invalid index type "str" for "SKAGI | dict[Never, Never]"; expected type "Never" [index]
splat = {**(d or {})} # error: Unpacked dict entry 0 has incompatible type "SKAGI | dict[Never, Never]"; expected "SupportsKeysAndGetItem[str, int]" [dict-item]
# using the normal dict for comparison:
def dict_builtin(d: None | dict[str, int]):
index = (d or {})["x"] # no error
get = (d or {}).get("x") # no error
splat = {**(d or {})} # no error
Expected Behavior
I'd expect the subclass and SupportsKeysAndGetItem-implementing type to behave the same as the normal dict instance, where the Never key arg is unified and/or ignored appropriately.
Actual Behavior
main.py:17: error: Invalid index type "str" for "DictSubclass | dict[Never, Never]"; expected type "Never" [index]
main.py:18: error: No overload variant of "get" of "dict" matches argument type "str" [call-overload]
main.py:18: note: Possible overload variants:
main.py:18: note: def get(self, Never, /) -> None
main.py:18: note: def get(self, Never, Never, /) -> Never
main.py:18: note: def [_T] get(self, Never, _T, /) -> _T
main.py:19: error: Unpacked dict entry 0 has incompatible type "DictSubclass | dict[Never, Never]"; expected "SupportsKeysAndGetItem[str, int]" [dict-item]
main.py:26: error: Invalid index type "str" for "SKAGI | dict[Never, Never]"; expected type "Never" [index]
main.py:27: error: Unpacked dict entry 0 has incompatible type "SKAGI | dict[Never, Never]"; expected "SupportsKeysAndGetItem[str, int]" [dict-item]
Found 5 errors in 1 file (checked 1 source file)
Your Environment
- Mypy version used: 1.11.2
- Mypy command-line flags: N/A
- Mypy configuration options from
mypy.ini(and other config files): N/A - Python version used: 3.12
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the linked mypy-play reproduction and compare the behavior of d or {} for builtin dicts, dict subclasses, and SupportsKeysAndGetItem implementations. Trace the relevant type inference and union handling; done means the reproduced indexing, get, and dictionary-splat examples no longer report the Never-related errors while the builtin-dict behavior remains correct.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- compilers, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100