diff --git a/stdlib/@tests/test_cases/builtins/check_dict.py b/stdlib/@tests/test_cases/builtins/check_dict.py index fe74ad49408e..405993bbf95b 100644 --- a/stdlib/@tests/test_cases/builtins/check_dict.py +++ b/stdlib/@tests/test_cases/builtins/check_dict.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from typing import Any, Dict, Generic, Iterable, Mapping, TypeVar, Union +from typing import Any, Dict, Generic, Iterable, Literal, Mapping, TypeVar, Union from typing_extensions import Self, assert_type ################################################################### @@ -74,35 +74,51 @@ def test_iterable_tuple_overload(x: Iterable[tuple[int, str]]) -> dict[int, str] assert_type(d_any.get("key"), Union[Any, None]) assert_type(d_any.get("key", None), Union[Any, None]) assert_type(d_any.get("key", any_value), Any) -assert_type(d_any.get("key", str_value), Any) -assert_type(d_any.get("key", int_value), Any) +assert_type(d_any.get("key", str_value), Union[Any, str]) +assert_type(d_any.get("key", int_value), Union[Any, int]) assert_type(d_str["key"], str) assert_type(d_str.get("key"), Union[str, None]) assert_type(d_str.get("key", None), Union[str, None]) # Pyright has str instead of Any here -assert_type(d_str.get("key", any_value), Any) # pyright: ignore[reportAssertTypeFailure] +assert_type(d_str.get("key", any_value), Union[str, Any]) assert_type(d_str.get("key", str_value), str) assert_type(d_str.get("key", int_value), Union[str, int]) # Now with context! result: str result = d_any["key"] -result = d_any.get("key") # type: ignore[assignment] -result = d_any.get("key", None) # type: ignore[assignment] +# FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[assignment] here +result = d_any.get("key") # type: ignore +# FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[assignment] here +result = d_any.get("key", None) # type: ignore result = d_any.get("key", any_value) result = d_any.get("key", str_value) -result = d_any.get("key", int_value) +# FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[assignment] here +result = d_any.get("key", int_value) # type: ignore result = d_str["key"] -result = d_str.get("key") # type: ignore[assignment] -result = d_str.get("key", None) # type: ignore[assignment] -# Pyright has str | None here, see https://github.com/microsoft/pyright/discussions/9570 -result = d_str.get("key", any_value) # pyright: ignore[reportAssignmentType] +# FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[assignment] here +result = d_str.get("key") # type: ignore +# FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[assignment] here +result = d_str.get("key", None) # type: ignore +result = d_str.get("key", any_value) result = d_str.get("key", str_value) result = d_str.get("key", int_value) # type: ignore[arg-type] +def test_get_literal(d: dict[Literal["foo", "bar"], int], dynamic_key: str) -> None: + # Note: annotations also allow using keys of a disjoint type (e.g., int), + # linters / type checkers are free to issue warnings in such cases. + # statically, a .get(arg) is superfluous if the intersection of the + # dict key type and the argument type is empty. + # So we only test a case with non-empty intersection here. + + # check that dict wth Literal keys can get/pop a string key. + d.get(dynamic_key) + d.pop(dynamic_key) + + # Return values also make things weird # Pyright doesn't have a version of no-any-return, @@ -112,7 +128,8 @@ def test_iterable_tuple_overload(x: Iterable[tuple[int, str]]) -> dict[int, str] def test2() -> str: - return d_any.get("key") # type: ignore[return-value] + # FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[return-value] here + return d_any.get("key") # type: ignore # def test3() -> str: @@ -136,15 +153,18 @@ def test7() -> str: def test8() -> str: - return d_str.get("key") # type: ignore[return-value] + # FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[return-value] here + return d_str.get("key") # type: ignore def test9() -> str: - return d_str.get("key", None) # type: ignore[return-value] + # FIXME: https://github.com/python/mypy/issues/20576 prevents using ignore[return-value] here + return d_str.get("key", None) # type: ignore def test10() -> str: - return d_str.get("key", any_value) # type: ignore[no-any-return] + # OK, return is Union[str, Any] + return d_str.get("key", any_value) def test11() -> str: diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index 693dd0b77087..356551b44f00 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -79,6 +79,7 @@ if sys.version_info >= (3, 14): from _typeshed import AnnotateFunc _T = TypeVar("_T") +_N = TypeVar("_N", default=None) _I = TypeVar("_I", default=int) _T_co = TypeVar("_T_co", covariant=True) _T_contra = TypeVar("_T_contra", contravariant=True) @@ -1218,19 +1219,8 @@ class dict(MutableMapping[_KT, _VT]): @classmethod @overload def fromkeys(cls, iterable: Iterable[_T], value: _S, /) -> dict[_T, _S]: ... - # Positional-only in dict, but not in MutableMapping - @overload # type: ignore[override] - def get(self, key: _KT, default: None = None, /) -> _VT | None: ... - @overload - def get(self, key: _KT, default: _VT, /) -> _VT: ... - @overload - def get(self, key: _KT, default: _T, /) -> _VT | _T: ... - @overload - def pop(self, key: _KT, /) -> _VT: ... - @overload - def pop(self, key: _KT, default: _VT, /) -> _VT: ... - @overload - def pop(self, key: _KT, default: _T, /) -> _VT | _T: ... + def get(self, key: object, default: _N = None, /) -> _VT | _N: ... + def pop(self, key: object, default: _N = None, /) -> _VT | _N: ... def __len__(self) -> int: ... def __getitem__(self, key: _KT, /) -> _VT: ... def __setitem__(self, key: _KT, value: _VT, /) -> None: ... diff --git a/stdlib/collections/__init__.pyi b/stdlib/collections/__init__.pyi index 8636e6cdbdc3..e806e01ef231 100644 --- a/stdlib/collections/__init__.pyi +++ b/stdlib/collections/__init__.pyi @@ -105,12 +105,15 @@ class UserDict(MutableMapping[_KT, _VT]): @overload def __ior__(self, other: Iterable[tuple[_KT, _VT]]) -> Self: ... if sys.version_info >= (3, 12): + # UserDict allows key and default as keyword arguments @overload - def get(self, key: _KT, default: None = None) -> _VT | None: ... + def get(self, key: object, default: _T) -> _VT | _T: ... @overload - def get(self, key: _KT, default: _VT) -> _VT: ... + def get(self, key: object, default: None = None) -> _VT | None: ... @overload - def get(self, key: _KT, default: _T) -> _VT | _T: ... + def pop(self, key: object, default: _T) -> _VT | _T: ... + @overload # type: ignore[override] + def pop(self, key: object, default: None = None) -> _VT | None: ... class UserList(MutableSequence[_T]): data: list[_T] @@ -382,11 +385,9 @@ class OrderedDict(dict[_KT, _VT]): def setdefault(self, key: _KT, default: _VT) -> _VT: ... # Same as dict.pop, but accepts keyword arguments @overload - def pop(self, key: _KT) -> _VT: ... - @overload - def pop(self, key: _KT, default: _VT) -> _VT: ... - @overload - def pop(self, key: _KT, default: _T) -> _VT | _T: ... + def pop(self, key: object, default: _T) -> _VT | _T: ... + @overload # type: ignore[override] + def pop(self, key: object, default: None = None) -> _VT | None: ... def __eq__(self, value: object, /) -> bool: ... @overload def __or__(self, value: dict[_KT, _VT], /) -> Self: ... diff --git a/stdlib/importlib/metadata/__init__.pyi b/stdlib/importlib/metadata/__init__.pyi index 9286e92331c8..e66c0c3c5faf 100644 --- a/stdlib/importlib/metadata/__init__.pyi +++ b/stdlib/importlib/metadata/__init__.pyi @@ -165,11 +165,9 @@ if sys.version_info >= (3, 10) and sys.version_info < (3, 12): class Deprecated(Generic[_KT, _VT]): def __getitem__(self, name: _KT) -> _VT: ... @overload - def get(self, name: _KT, default: None = None) -> _VT | None: ... + def get(self, key: object, default: _T) -> _VT | _T: ... @overload - def get(self, name: _KT, default: _VT) -> _VT: ... - @overload - def get(self, name: _KT, default: _T) -> _VT | _T: ... + def get(self, key: object, default: None = None) -> _VT | None: ... def __iter__(self) -> Iterator[_KT]: ... def __contains__(self, *args: object) -> bool: ... def keys(self) -> dict_keys[_KT, _VT]: ...