Skip to content

Commit 58fe50b

Browse files
allow disjoint types in Mapping.get MutableMapping.pop, dict.get, dict.pop
1 parent 236a8c0 commit 58fe50b

File tree

3 files changed

+37
-36
lines changed

3 files changed

+37
-36
lines changed

stdlib/@tests/test_cases/builtins/check_dict.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import os
4-
from typing import Any, Dict, Generic, Iterable, Mapping, TypeVar, Union
4+
from typing import Any, Dict, Generic, Iterable, Literal, Mapping, TypeVar, Union
55
from typing_extensions import Self, assert_type
66

77
###################################################################
@@ -74,35 +74,49 @@ def test_iterable_tuple_overload(x: Iterable[tuple[int, str]]) -> dict[int, str]
7474
assert_type(d_any.get("key"), Union[Any, None])
7575
assert_type(d_any.get("key", None), Union[Any, None])
7676
assert_type(d_any.get("key", any_value), Any)
77-
assert_type(d_any.get("key", str_value), Any)
78-
assert_type(d_any.get("key", int_value), Any)
77+
assert_type(d_any.get("key", str_value), Union[Any, str])
78+
assert_type(d_any.get("key", int_value), Union[Any, int])
7979

8080
assert_type(d_str["key"], str)
8181
assert_type(d_str.get("key"), Union[str, None])
8282
assert_type(d_str.get("key", None), Union[str, None])
8383
# Pyright has str instead of Any here
84-
assert_type(d_str.get("key", any_value), Any) # pyright: ignore[reportAssertTypeFailure]
84+
assert_type(d_str.get("key", any_value), Union[str, Any])
8585
assert_type(d_str.get("key", str_value), str)
8686
assert_type(d_str.get("key", int_value), Union[str, int])
8787

8888
# Now with context!
8989
result: str
9090
result = d_any["key"]
9191
result = d_any.get("key") # type: ignore[assignment]
92-
result = d_any.get("key", None) # type: ignore[assignment]
92+
# FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[assignment] here
93+
result = d_any.get("key", None) # type: ignore
9394
result = d_any.get("key", any_value)
9495
result = d_any.get("key", str_value)
95-
result = d_any.get("key", int_value)
96+
# FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[assignment] here
97+
result = d_any.get("key", int_value) # type: ignore
9698

9799
result = d_str["key"]
98100
result = d_str.get("key") # type: ignore[assignment]
99-
result = d_str.get("key", None) # type: ignore[assignment]
100-
# Pyright has str | None here, see https://github.com/microsoft/pyright/discussions/9570
101-
result = d_str.get("key", any_value) # pyright: ignore[reportAssignmentType]
101+
# FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[assignment] here
102+
result = d_str.get("key", None) # type: ignore
103+
result = d_str.get("key", any_value)
102104
result = d_str.get("key", str_value)
103105
result = d_str.get("key", int_value) # type: ignore[arg-type]
104106

105107

108+
def test_get_literal(d: dict[Literal["foo", "bar"], int], dynamic_key: str) -> None:
109+
# Note: annotations also allow using keys of a disjoint type (e.g., int),
110+
# linters / type checkers are free to issue warnings in such cases.
111+
# statically, a .get(arg) is superfluous if the intersection of the
112+
# dict key type and the argument type is empty.
113+
# So we only test a case with non-empty intersection here.
114+
115+
# check that dict wth Literal keys can get/pop a string key.
116+
d.get(dynamic_key)
117+
d.pop(dynamic_key)
118+
119+
106120
# Return values also make things weird
107121

108122
# Pyright doesn't have a version of no-any-return,
@@ -140,11 +154,13 @@ def test8() -> str:
140154

141155

142156
def test9() -> str:
143-
return d_str.get("key", None) # type: ignore[return-value]
157+
# FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[return-value] here
158+
return d_str.get("key", None) # type: ignore
144159

145160

146161
def test10() -> str:
147-
return d_str.get("key", any_value) # type: ignore[no-any-return]
162+
# OK, return is Union[str, Any]
163+
return d_str.get("key", any_value)
148164

149165

150166
def test11() -> str:

stdlib/builtins.pyi

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,19 +1218,8 @@ class dict(MutableMapping[_KT, _VT]):
12181218
@classmethod
12191219
@overload
12201220
def fromkeys(cls, iterable: Iterable[_T], value: _S, /) -> dict[_T, _S]: ...
1221-
# Positional-only in dict, but not in MutableMapping
1222-
@overload # type: ignore[override]
1223-
def get(self, key: _KT, default: None = None, /) -> _VT | None: ...
1224-
@overload
1225-
def get(self, key: _KT, default: _VT, /) -> _VT: ...
1226-
@overload
1227-
def get(self, key: _KT, default: _T, /) -> _VT | _T: ...
1228-
@overload
1229-
def pop(self, key: _KT, /) -> _VT: ...
1230-
@overload
1231-
def pop(self, key: _KT, default: _VT, /) -> _VT: ...
1232-
@overload
1233-
def pop(self, key: _KT, default: _T, /) -> _VT | _T: ...
1221+
# get: inherited from Mapping
1222+
# pop: inherited from MutableMapping
12341223
def __len__(self) -> int: ...
12351224
def __getitem__(self, key: _KT, /) -> _VT: ...
12361225
def __setitem__(self, key: _KT, value: _VT, /) -> None: ...

stdlib/typing.pyi

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -773,12 +773,10 @@ class Mapping(Collection[_KT], Generic[_KT, _VT_co]):
773773
@abstractmethod
774774
def __getitem__(self, key: _KT, /) -> _VT_co: ...
775775
# Mixin methods
776-
@overload
777-
def get(self, key: _KT, /) -> _VT_co | None: ...
778-
@overload
779-
def get(self, key: _KT, default: _VT_co, /) -> _VT_co: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] # Covariant type as parameter
780-
@overload
781-
def get(self, key: _KT, default: _T, /) -> _VT_co | _T: ...
776+
@overload # intentionally positional-only
777+
def get(self, key: Any, /) -> _VT_co | None: ...
778+
@overload # intentionally positional-only
779+
def get(self, key: Any, default: _T, /) -> _VT_co | _T: ...
782780
def items(self) -> ItemsView[_KT, _VT_co]: ...
783781
def keys(self) -> KeysView[_KT]: ...
784782
def values(self) -> ValuesView[_VT_co]: ...
@@ -791,12 +789,10 @@ class MutableMapping(Mapping[_KT, _VT]):
791789
@abstractmethod
792790
def __delitem__(self, key: _KT, /) -> None: ...
793791
def clear(self) -> None: ...
794-
@overload
795-
def pop(self, key: _KT, /) -> _VT: ...
796-
@overload
797-
def pop(self, key: _KT, default: _VT, /) -> _VT: ...
798-
@overload
799-
def pop(self, key: _KT, default: _T, /) -> _VT | _T: ...
792+
@overload # intentionally positional-only
793+
def pop(self, key: Any, /) -> _VT | None: ...
794+
@overload # intentionally positional-only
795+
def pop(self, key: Any, default: _T, /) -> _VT | _T: ...
800796
def popitem(self) -> tuple[_KT, _VT]: ...
801797
# This overload should be allowed only if the value type is compatible with None.
802798
#

0 commit comments

Comments
 (0)