From 99bdec970eabd02f50b1f835b8436ce52a392801 Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Fri, 16 Jan 2026 19:56:37 +0100 Subject: [PATCH 1/2] allow arbitrary types in dict.pop and MutableMapping.pop --- stdlib/@tests/test_cases/builtins/check_dict.py | 11 ++++++++++- stdlib/builtins.pyi | 6 +++--- stdlib/collections/__init__.pyi | 12 ++++++------ stdlib/mailbox.pyi | 2 +- stdlib/typing.pyi | 6 +++--- stdlib/weakref.pyi | 12 ++++++------ stubs/boltons/boltons/cacheutils.pyi | 4 ++-- stubs/boltons/boltons/dictutils.pyi | 2 +- stubs/yt-dlp/yt_dlp/utils/networking.pyi | 6 +++--- 9 files changed, 35 insertions(+), 26 deletions(-) diff --git a/stdlib/@tests/test_cases/builtins/check_dict.py b/stdlib/@tests/test_cases/builtins/check_dict.py index fe74ad49408e..e175f436c567 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 ################################################################### @@ -103,6 +103,15 @@ def test_iterable_tuple_overload(x: Iterable[tuple[int, str]]) -> dict[int, str] result = d_str.get("key", int_value) # type: ignore[arg-type] +def test_pop_literal(d: dict[Literal["foo", "bar"], int], 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. + d.pop(key) + + # Return values also make things weird # Pyright doesn't have a version of no-any-return, diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index efc51fe257ae..fa41eb9f9ede 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -1226,11 +1226,11 @@ class dict(MutableMapping[_KT, _VT]): @overload def get(self, key: _KT, default: _T, /) -> _VT | _T: ... @overload - def pop(self, key: _KT, /) -> _VT: ... + def pop(self, key: object, /) -> _VT: ... @overload - def pop(self, key: _KT, default: _VT, /) -> _VT: ... + def pop(self, key: object, default: _VT, /) -> _VT: ... @overload - def pop(self, key: _KT, default: _T, /) -> _VT | _T: ... + def pop(self, key: object, default: _T, /) -> _VT | _T: ... 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 95f13b0c8dd2..2fd7dd6378a3 100644 --- a/stdlib/collections/__init__.pyi +++ b/stdlib/collections/__init__.pyi @@ -382,11 +382,11 @@ 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: ... + def pop(self, key: object) -> _VT: ... @overload - def pop(self, key: _KT, default: _VT) -> _VT: ... + def pop(self, key: object, default: _VT) -> _VT: ... @overload - def pop(self, key: _KT, default: _T) -> _VT | _T: ... + def pop(self, key: object, default: _T) -> _VT | _T: ... def __eq__(self, value: object, /) -> bool: ... @overload def __or__(self, value: dict[_KT, _VT], /) -> Self: ... @@ -471,11 +471,11 @@ class ChainMap(MutableMapping[_KT, _VT]): @overload def setdefault(self, key: _KT, default: _VT) -> _VT: ... @overload - def pop(self, key: _KT) -> _VT: ... + def pop(self, key: Any) -> _VT: ... @overload - def pop(self, key: _KT, default: _VT) -> _VT: ... + def pop(self, key: Any, default: _VT) -> _VT: ... @overload - def pop(self, key: _KT, default: _T) -> _VT | _T: ... + def pop(self, key: Any, default: _T) -> _VT | _T: ... def copy(self) -> Self: ... __copy__ = copy # All arguments to `fromkeys` are passed to `dict.fromkeys` at runtime, diff --git a/stdlib/mailbox.pyi b/stdlib/mailbox.pyi index 89bd998b4dfe..7aacfb2ad0b8 100644 --- a/stdlib/mailbox.pyi +++ b/stdlib/mailbox.pyi @@ -88,7 +88,7 @@ class Mailbox(Generic[_MessageT]): def __len__(self) -> int: ... def clear(self) -> None: ... @overload - def pop(self, key: str, default: None = None) -> _MessageT | None: ... + def pop(self, key: str) -> _MessageT | None: ... @overload def pop(self, key: str, default: _T) -> _MessageT | _T: ... def popitem(self) -> tuple[str, _MessageT]: ... diff --git a/stdlib/typing.pyi b/stdlib/typing.pyi index 369af41a7ad9..e13d59a8bac6 100644 --- a/stdlib/typing.pyi +++ b/stdlib/typing.pyi @@ -795,11 +795,11 @@ class MutableMapping(Mapping[_KT, _VT]): def __delitem__(self, key: _KT, /) -> None: ... def clear(self) -> None: ... @overload - def pop(self, key: _KT, /) -> _VT: ... + def pop(self, key: Any, /) -> _VT: ... @overload - def pop(self, key: _KT, default: _VT, /) -> _VT: ... + def pop(self, key: Any, default: _VT, /) -> _VT: ... @overload - def pop(self, key: _KT, default: _T, /) -> _VT | _T: ... + def pop(self, key: Any, default: _T, /) -> _VT | _T: ... def popitem(self) -> tuple[_KT, _VT]: ... # This overload should be allowed only if the value type is compatible with None. # diff --git a/stdlib/weakref.pyi b/stdlib/weakref.pyi index 76ab86b957a1..3e2005744453 100644 --- a/stdlib/weakref.pyi +++ b/stdlib/weakref.pyi @@ -112,11 +112,11 @@ class WeakValueDictionary(MutableMapping[_KT, _VT]): def valuerefs(self) -> list[KeyedRef[_KT, _VT]]: ... def setdefault(self, key: _KT, default: _VT) -> _VT: ... @overload - def pop(self, key: _KT) -> _VT: ... + def pop(self, key: object) -> _VT: ... @overload - def pop(self, key: _KT, default: _VT) -> _VT: ... + def pop(self, key: object, default: _VT) -> _VT: ... @overload - def pop(self, key: _KT, default: _T) -> _VT | _T: ... + def pop(self, key: object, default: _T) -> _VT | _T: ... @overload def update(self, other: SupportsKeysAndGetItem[_KT, _VT], /, **kwargs: _VT) -> None: ... @overload @@ -168,11 +168,11 @@ class WeakKeyDictionary(MutableMapping[_KT, _VT]): @overload def setdefault(self, key: _KT, default: _VT) -> _VT: ... @overload - def pop(self, key: _KT) -> _VT: ... + def pop(self, key: object) -> _VT: ... @overload - def pop(self, key: _KT, default: _VT) -> _VT: ... + def pop(self, key: object, default: _VT) -> _VT: ... @overload - def pop(self, key: _KT, default: _T) -> _VT | _T: ... + def pop(self, key: object, default: _T) -> _VT | _T: ... @overload def update(self, dict: SupportsKeysAndGetItem[_KT, _VT], /, **kwargs: _VT) -> None: ... @overload diff --git a/stubs/boltons/boltons/cacheutils.pyi b/stubs/boltons/boltons/cacheutils.pyi index 40e786e61bd8..966e72123d7b 100644 --- a/stubs/boltons/boltons/cacheutils.pyi +++ b/stubs/boltons/boltons/cacheutils.pyi @@ -31,9 +31,9 @@ class LRI(dict[_KT, _VT]): def get(self, key: _KT, default: _T) -> _T | _VT: ... def __delitem__(self, key: _KT) -> None: ... @overload - def pop(self, key: _KT) -> _VT: ... + def pop(self, key: object) -> _VT: ... @overload - def pop(self, key: _KT, default: _T) -> _T | _VT: ... + def pop(self, key: object, default: _T) -> _T | _VT: ... def popitem(self) -> tuple[_KT, _VT]: ... def clear(self) -> None: ... def copy(self) -> Self: ... diff --git a/stubs/boltons/boltons/dictutils.pyi b/stubs/boltons/boltons/dictutils.pyi index 5e609ef9af13..e725160fb806 100644 --- a/stubs/boltons/boltons/dictutils.pyi +++ b/stubs/boltons/boltons/dictutils.pyi @@ -55,7 +55,7 @@ class OneToOne(dict[_KT, _VT]): inv: OneToOne[_VT, _KT] def clear(self) -> None: ... def copy(self) -> Self: ... - def pop(self, key: _KT, default: _VT | _T = ...) -> _VT | _T: ... + def pop(self, key: object, default: _VT | _T = ...) -> _VT | _T: ... def popitem(self) -> tuple[_KT, _VT]: ... def setdefault(self, key: _KT, default: _VT | None = None) -> _VT: ... @classmethod diff --git a/stubs/yt-dlp/yt_dlp/utils/networking.pyi b/stubs/yt-dlp/yt_dlp/utils/networking.pyi index 1d3d18310dd3..fa60f5c66e42 100644 --- a/stubs/yt-dlp/yt_dlp/utils/networking.pyi +++ b/stubs/yt-dlp/yt_dlp/utils/networking.pyi @@ -21,11 +21,11 @@ class HTTPHeaderDict(dict[str, str]): @overload def get(self, key: str, /, default: type[NO_DEFAULT] | _T = ...) -> str | _T | type[NO_DEFAULT]: ... @overload - def pop(self, key: str, /) -> str: ... + def pop(self, key: object, /) -> str: ... @overload - def pop(self, key: str, /, default: _T) -> str | _T: ... + def pop(self, key: object, /, default: _T) -> str | _T: ... @overload - def pop(self, key: str, /, default: type[NO_DEFAULT] | _T | str = ...) -> str | _T | type[NO_DEFAULT]: ... + def pop(self, key: object, /, default: type[NO_DEFAULT] | _T | str = ...) -> str | _T | type[NO_DEFAULT]: ... @overload def setdefault(self, key: str, /) -> str: ... @overload From a56bd36039ae27108e6d208ac233cc73edf2c310 Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Fri, 6 Feb 2026 18:15:35 +0100 Subject: [PATCH 2/2] added comments to dict.pop and MutableMapping.pop --- stdlib/builtins.pyi | 2 ++ stdlib/typing.pyi | 2 ++ 2 files changed, 4 insertions(+) diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index fa41eb9f9ede..3a35b714dc90 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -1225,6 +1225,8 @@ class dict(MutableMapping[_KT, _VT]): def get(self, key: _KT, default: _VT, /) -> _VT: ... @overload def get(self, key: _KT, default: _T, /) -> _VT | _T: ... + # dict.pop allows arbitrary types, which matches runtime semantics. + # linters may choose to warn if the given type does not overlap with the key type @overload def pop(self, key: object, /) -> _VT: ... @overload diff --git a/stdlib/typing.pyi b/stdlib/typing.pyi index e13d59a8bac6..8236ce364883 100644 --- a/stdlib/typing.pyi +++ b/stdlib/typing.pyi @@ -794,6 +794,8 @@ class MutableMapping(Mapping[_KT, _VT]): @abstractmethod def __delitem__(self, key: _KT, /) -> None: ... def clear(self) -> None: ... + # We annotate this with `Any` rather than `object` as in dict.pop, since + # specific implementations of `MutableMapping` may wish to restrict the type. @overload def pop(self, key: Any, /) -> _VT: ... @overload