diff --git a/stdlib/@tests/test_cases/builtins/check_frozendict-py315.py b/stdlib/@tests/test_cases/builtins/check_frozendict-py315.py index dd222a4c796f..ec96ff5b2853 100644 --- a/stdlib/@tests/test_cases/builtins/check_frozendict-py315.py +++ b/stdlib/@tests/test_cases/builtins/check_frozendict-py315.py @@ -13,3 +13,17 @@ assert_type(frozendict({"x": 1}), frozendict[str, int]) assert_type(frozendict([("k", 2)]), frozendict[str, int]) assert_type(frozendict({"x": 1}, y=2), frozendict[str, int]) + + # gh-16668: like ``Mapping``, ``frozendict`` is covariant in its value type + # (but not in its key type). + def takes_optional_values(d: frozendict[str, int | None]) -> None: ... + + int_values: frozendict[str, int] = frozendict(a=1) + takes_optional_values(int_values) + assert_type(int_values.get("a"), int | None) + assert_type(int_values.get("a", 0), int) + assert_type(int_values.get("a", "x"), int | str) + + def takes_object_keys(d: frozendict[object, int]) -> None: ... + + takes_object_keys(int_values) # type: ignore diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index 0075adf561c2..1628f1bbf760 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -78,6 +78,7 @@ _T_contra = TypeVar("_T_contra", contravariant=True) _R_co = TypeVar("_R_co", covariant=True) _KT = TypeVar("_KT") _VT = TypeVar("_VT") +_VT_co = TypeVar("_VT_co", covariant=True) _S = TypeVar("_S") _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") @@ -1378,7 +1379,7 @@ class dict(MutableMapping[_KT, _VT]): if sys.version_info >= (3, 15): @disjoint_base - class frozendict(Mapping[_KT, _VT]): + class frozendict(Mapping[_KT, _VT_co]): @overload def __new__(cls, /) -> frozendict[Any, Any]: ... @overload @@ -1396,7 +1397,7 @@ if sys.version_info >= (3, 15): cls: type[frozendict[str, _VT]], iterable: Iterable[tuple[str, _VT]], /, **kwargs: _VT ) -> frozendict[str, _VT]: ... - def copy(self) -> frozendict[_KT, _VT]: ... + def copy(self) -> frozendict[_KT, _VT_co]: ... @overload @classmethod @@ -1406,27 +1407,27 @@ if sys.version_info >= (3, 15): def fromkeys(cls, iterable: Iterable[_T], value: _S, /) -> frozendict[_T, _S]: ... @overload # type: ignore[override] - def get(self, key: _KT, default: None = None, /) -> _VT | None: ... + def get(self, key: _KT, default: None = None, /) -> _VT_co | None: ... @overload - def get(self, key: _KT, default: _VT, /) -> _VT: ... + def get(self, key: _KT, default: _VT_co, /) -> _VT_co: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] # Covariant type as parameter # pyrefly: ignore [invalid-variance] @overload - def get(self, key: _KT, default: _T, /) -> _VT | _T: ... + def get(self, key: _KT, default: _T, /) -> _VT_co | _T: ... - def keys(self) -> dict_keys[_KT, _VT]: ... - def values(self) -> dict_values[_KT, _VT]: ... - def items(self) -> dict_items[_KT, _VT]: ... + def keys(self) -> dict_keys[_KT, _VT_co]: ... + def values(self) -> dict_values[_KT, _VT_co]: ... + def items(self) -> dict_items[_KT, _VT_co]: ... def __len__(self) -> int: ... - def __getitem__(self, key: _KT, /) -> _VT: ... + def __getitem__(self, key: _KT, /) -> _VT_co: ... def __reversed__(self) -> Iterator[_KT]: ... def __iter__(self) -> Iterator[_KT]: ... def __hash__(self) -> int: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... - def __or__(self, value: dict[_T1, _T2] | frozendict[_T1, _T2], /) -> frozendict[_KT | _T1, _VT | _T2]: ... + def __or__(self, value: dict[_T1, _T2] | frozendict[_T1, _T2], /) -> frozendict[_KT | _T1, _VT_co | _T2]: ... @overload - def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT_co | _T2]: ... @overload - def __ror__(self, value: frozendict[_T1, _T2], /) -> frozendict[_KT | _T1, _VT | _T2]: ... + def __ror__(self, value: frozendict[_T1, _T2], /) -> frozendict[_KT | _T1, _VT_co | _T2]: ... @disjoint_base class set(MutableSet[_T]):