python / typeshed

Collection of library stubs for Python, with static types
Other
4.3k stars 1.73k forks source link

`os.environ |= dict[str, str]` results in `Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "_Environ[str]")` #6919

Closed posita closed 2 years ago

posita commented 2 years ago
# test_case.py
from __future__ import annotations
import os
moar_env: dict[str, str] = {}
os.environ.update(moar_env)  # <-- copacetic
os.environ |= moar_env  # <-- doesn't pass validation
% mypy --config=/dev/null test_case.py
/dev/null: No [mypy] section in config file
test_case.py:6: error: Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "_Environ[str]")
Found 1 error in 1 file (checked 1 source file)
% mypy --version
mypy 0.931
% python --version
Python 3.9.9
posita commented 2 years ago
UPDATE: Red herring preserved but hidden to avoid distractions What's ~~interesting~~ _entirely correct and unsurprising_ is that this … ``` python reveal_type(os.environ | moar_env) ``` … yields … ``` Revealed type is "builtins.dict[builtins.str, builtins.str]" ``` So it might be limited to the `|=` assignment operator?
Akuli commented 2 years ago

That seems correct:

>>> type(os.environ | {"x": "y"})
<class 'dict'>
>>> os.environ |= {"x": "y"}
>>> type(os.environ)
<class 'os._Environ'>

It's similar to how += and + are different with lists:

>>> foo = []
>>> foo + "lol"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "str") to list
>>> foo += "lol"
>>> foo
['l', 'o', 'l']
posita commented 2 years ago

That seems correct:

Yeah, but I think that's a red herring. The bug is that Mypy gives a false negative for |=. FWIW, I don't see __ior__ mentioned anywhere in os/__init__.pyi. UPDATE: I fixed my earlier comment to hide the distraction.

Akuli commented 2 years ago

Indeed, we should add an __ior__ similarly to list's __iadd__. It also appears to be specific to os.environ, not inherited from its base class MutableMapping:

>>> from collections.abc import MutableMapping
>>> MutableMapping.__ior__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'MutableMapping' has no attribute '__ior__'
>>> import os
>>> type(os.environ).__ior__
<function _Environ.__ior__ at 0x7f061ed26040>

PR welcome :)