From c22ec9f861091b8ac2944dfcefee0d35c7c467a9 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 10 Feb 2025 16:27:08 -0500 Subject: [PATCH 01/12] Improve operator.itemgetter generic following mypy 1.11 fix --- .../test_cases/check_SupportsGetItem.py | 30 +++++++++++++++++++ stdlib/operator.pyi | 7 ++--- 2 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 stdlib/@tests/test_cases/check_SupportsGetItem.py diff --git a/stdlib/@tests/test_cases/check_SupportsGetItem.py b/stdlib/@tests/test_cases/check_SupportsGetItem.py new file mode 100644 index 000000000000..e17b8391415b --- /dev/null +++ b/stdlib/@tests/test_cases/check_SupportsGetItem.py @@ -0,0 +1,30 @@ +from _typeshed import SupportsGetItem +from operator import itemgetter +from collections.abc import Callable +from typing import TypeVar +from _typeshed import SupportsDunderGT, SupportsDunderLT +from collections.abc import Callable +from typing import Any, Tuple, TypeVar, Union + +_T = TypeVar("_T") + + +# This should be equivalent to itemgetter.__call__ +def standalone_call(obj: SupportsGetItem[int, _T]) -> _T: ... + + +# Regression tests for https://github.com/python/mypy/issues/14032 + +test_dict = min({"first": 1, "second": 2}, key=itemgetter(1)) +test_items = min({"first": 1, "second": 2}.items(), key=itemgetter(1)) +test_dict_standalone = min({"first": 1, "second": 2}, key=standalone_call) +test_items_standalone = min({"first": 1, "second": 2}.items(), key=standalone_call) + +expected_type_form_min_param_1: Callable[[Tuple[str, int]], Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]] +revealed_type_itemgetter_call: Callable[[SupportsGetItem[Any, _T]], _T] # pyright: ignore[reportGeneralTypeIssues] + +revealed_type_itemgetter_call = itemgetter(1) +expected_type_form_min_param_1 = itemgetter(1) + +revealed_type_itemgetter_call = standalone_call +expected_type_form_min_param_1 = standalone_call diff --git a/stdlib/operator.pyi b/stdlib/operator.pyi index bc2b5e026617..33268b3b504d 100644 --- a/stdlib/operator.pyi +++ b/stdlib/operator.pyi @@ -203,11 +203,8 @@ class itemgetter(Generic[_T_co]): # __key: _KT_contra in SupportsGetItem seems to be causing variance issues, ie: # TypeVar "_KT_contra@SupportsGetItem" is contravariant # "tuple[int, int]" is incompatible with protocol "SupportsIndex" - # preventing [_T_co, ...] instead of [Any, ...] - # - # A suspected mypy issue prevents using [..., _T] instead of [..., Any] here. - # https://github.com/python/mypy/issues/14032 - def __call__(self, obj: SupportsGetItem[Any, Any]) -> Any: ... + # preventing [_T_co, _T] instead of [Any, _T] + def __call__(self, obj: SupportsGetItem[Any, _T]) -> _T: ... @final class methodcaller: From 36314daa64b723cb097ee10cc19ca7ed34a15623 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 21:31:33 +0000 Subject: [PATCH 02/12] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/@tests/test_cases/check_SupportsGetItem.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/stdlib/@tests/test_cases/check_SupportsGetItem.py b/stdlib/@tests/test_cases/check_SupportsGetItem.py index e17b8391415b..ab40be8daf7e 100644 --- a/stdlib/@tests/test_cases/check_SupportsGetItem.py +++ b/stdlib/@tests/test_cases/check_SupportsGetItem.py @@ -1,9 +1,6 @@ -from _typeshed import SupportsGetItem -from operator import itemgetter -from collections.abc import Callable -from typing import TypeVar -from _typeshed import SupportsDunderGT, SupportsDunderLT +from _typeshed import SupportsDunderGT, SupportsDunderLT, SupportsGetItem from collections.abc import Callable +from operator import itemgetter from typing import Any, Tuple, TypeVar, Union _T = TypeVar("_T") From ee422a8558527e4ff16649509f81e5e9e4d0dc2f Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 10 Feb 2025 16:34:11 -0500 Subject: [PATCH 03/12] Possibly fix six.byte2int entry from stubtest allowlist --- stubs/six/@tests/stubtest_allowlist.txt | 2 -- stubs/six/six/__init__.pyi | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/stubs/six/@tests/stubtest_allowlist.txt b/stubs/six/@tests/stubtest_allowlist.txt index b00e2bf52be9..5c638491dd63 100644 --- a/stubs/six/@tests/stubtest_allowlist.txt +++ b/stubs/six/@tests/stubtest_allowlist.txt @@ -14,8 +14,6 @@ six.get_method_self six.viewitems six.viewkeys six.viewvalues -# Should be `operator.itemgetter[int]`. But a bug in mypy prevents using TypeVar in itemgetter__call__ -six.byte2int # Utils six.Module_six_moves_urllib diff --git a/stubs/six/six/__init__.pyi b/stubs/six/six/__init__.pyi index e5a8fdbaa3c6..95e4d13b2bac 100644 --- a/stubs/six/six/__init__.pyi +++ b/stubs/six/six/__init__.pyi @@ -2,7 +2,7 @@ import builtins import operator import types import unittest -from _typeshed import IdentityFunction, SupportsGetItem, Unused +from _typeshed import IdentityFunction, Unused from builtins import next as next from collections.abc import Callable, ItemsView, Iterable, Iterator as _Iterator, KeysView, Mapping, ValuesView from functools import wraps as wraps @@ -61,8 +61,7 @@ unichr = chr def int2byte(i: int) -> bytes: ... -# Should be `byte2int: operator.itemgetter[int]`. But a bug in mypy prevents using TypeVar in itemgetter.__call__ -def byte2int(obj: SupportsGetItem[int, _T]) -> _T: ... +byte2int: operator.itemgetter[int] indexbytes = operator.getitem iterbytes = iter From 11bded322624980ba5c173867d0b3a45619ebe23 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 10 Feb 2025 16:35:40 -0500 Subject: [PATCH 04/12] remove extra space --- stubs/six/six/__init__.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/stubs/six/six/__init__.pyi b/stubs/six/six/__init__.pyi index 95e4d13b2bac..b2a140f403b4 100644 --- a/stubs/six/six/__init__.pyi +++ b/stubs/six/six/__init__.pyi @@ -62,7 +62,6 @@ unichr = chr def int2byte(i: int) -> bytes: ... byte2int: operator.itemgetter[int] - indexbytes = operator.getitem iterbytes = iter From 8ce6cfb2bf112af3526085e1a6a392cdbad1327a Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 10 Feb 2025 17:21:11 -0500 Subject: [PATCH 05/12] Add from __future__ import annotations --- stdlib/@tests/test_cases/check_SupportsGetItem.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/stdlib/@tests/test_cases/check_SupportsGetItem.py b/stdlib/@tests/test_cases/check_SupportsGetItem.py index ab40be8daf7e..1e0c9da15032 100644 --- a/stdlib/@tests/test_cases/check_SupportsGetItem.py +++ b/stdlib/@tests/test_cases/check_SupportsGetItem.py @@ -1,7 +1,9 @@ +from __future__ import annotations + from _typeshed import SupportsDunderGT, SupportsDunderLT, SupportsGetItem from collections.abc import Callable from operator import itemgetter -from typing import Any, Tuple, TypeVar, Union +from typing import Any, TypeVar _T = TypeVar("_T") @@ -17,7 +19,7 @@ def standalone_call(obj: SupportsGetItem[int, _T]) -> _T: ... test_dict_standalone = min({"first": 1, "second": 2}, key=standalone_call) test_items_standalone = min({"first": 1, "second": 2}.items(), key=standalone_call) -expected_type_form_min_param_1: Callable[[Tuple[str, int]], Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]] +expected_type_form_min_param_1: Callable[[tuple[str, int]], SupportsDunderLT[Any] | SupportsDunderGT[Any]] revealed_type_itemgetter_call: Callable[[SupportsGetItem[Any, _T]], _T] # pyright: ignore[reportGeneralTypeIssues] revealed_type_itemgetter_call = itemgetter(1) From ad8f4d18d388270dcab1a28b7d8e5ba03ed8528d Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 10 Feb 2025 18:11:52 -0500 Subject: [PATCH 06/12] clearer, more relevant tests --- .../test_cases/check_SupportsGetItem.py | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/stdlib/@tests/test_cases/check_SupportsGetItem.py b/stdlib/@tests/test_cases/check_SupportsGetItem.py index 1e0c9da15032..7f3e8898b983 100644 --- a/stdlib/@tests/test_cases/check_SupportsGetItem.py +++ b/stdlib/@tests/test_cases/check_SupportsGetItem.py @@ -3,27 +3,34 @@ from _typeshed import SupportsDunderGT, SupportsDunderLT, SupportsGetItem from collections.abc import Callable from operator import itemgetter -from typing import Any, TypeVar +from typing import Any, TypeVar, assert_type _T = TypeVar("_T") -# This should be equivalent to itemgetter.__call__ -def standalone_call(obj: SupportsGetItem[int, _T]) -> _T: ... +# This should be equivalent to itemgetter().__call__ +def standalone_call(obj: SupportsGetItem[Any, _T]) -> _T: ... -# Regression tests for https://github.com/python/mypy/issues/14032 - -test_dict = min({"first": 1, "second": 2}, key=itemgetter(1)) -test_items = min({"first": 1, "second": 2}.items(), key=itemgetter(1)) -test_dict_standalone = min({"first": 1, "second": 2}, key=standalone_call) -test_items_standalone = min({"first": 1, "second": 2}.items(), key=standalone_call) +# Expected type of itemgetter(1).__call__ +expected_type_itemgetter_call: Callable[[SupportsGetItem[int, _T]], _T] # pyright: ignore[reportGeneralTypeIssues] -expected_type_form_min_param_1: Callable[[tuple[str, int]], SupportsDunderLT[Any] | SupportsDunderGT[Any]] -revealed_type_itemgetter_call: Callable[[SupportsGetItem[Any, _T]], _T] # pyright: ignore[reportGeneralTypeIssues] +# Expecting itemgetter(1) to be assignable to this +# based on the example below: min({"first": 1, "second": 2}.items(), key=itemgetter(1)) +expected_assignable_to: Callable[[tuple[str, int]], SupportsDunderLT[Any] | SupportsDunderGT[Any]] -revealed_type_itemgetter_call = itemgetter(1) -expected_type_form_min_param_1 = itemgetter(1) -revealed_type_itemgetter_call = standalone_call -expected_type_form_min_param_1 = standalone_call +# Regression tests for https://github.com/python/mypy/issues/14032 +assert_type(itemgetter("")({"first": 1, "second": 2}), int) +assert_type(min({"first": 1, "second": 2}, key=itemgetter(1)), str) +assert_type(min({"first": 1, "second": 2}.items(), key=itemgetter(1)), tuple[str, int]) +assert_type(standalone_call({"first": 1, "second": 2}), int) +assert_type(min({"first": 1, "second": 2}, key=standalone_call), str) +assert_type(min({"first": 1, "second": 2}.items(), key=standalone_call), tuple[str, int]) + +expected_itemgetter_call_type = itemgetter(1).__call__ +expected_itemgetter_call_type = itemgetter(1) +expected_assignable_to = itemgetter(1) + +expected_itemgetter_call_type = standalone_call +expected_assignable_to = standalone_call From 6662f2287a7b103d2c3d26b6793ccdca226f5bec Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 10 Feb 2025 18:14:15 -0500 Subject: [PATCH 07/12] Additional comments --- stdlib/@tests/test_cases/check_SupportsGetItem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/@tests/test_cases/check_SupportsGetItem.py b/stdlib/@tests/test_cases/check_SupportsGetItem.py index 7f3e8898b983..5319665a6be3 100644 --- a/stdlib/@tests/test_cases/check_SupportsGetItem.py +++ b/stdlib/@tests/test_cases/check_SupportsGetItem.py @@ -17,6 +17,7 @@ def standalone_call(obj: SupportsGetItem[Any, _T]) -> _T: ... # Expecting itemgetter(1) to be assignable to this # based on the example below: min({"first": 1, "second": 2}.items(), key=itemgetter(1)) +# That example and assigning to this variable are what failed in https://github.com/python/mypy/issues/14032 expected_assignable_to: Callable[[tuple[str, int]], SupportsDunderLT[Any] | SupportsDunderGT[Any]] From c2cb0b6184a1b53c76a3cc69b479de21204a1d78 Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 26 Sep 2025 11:27:47 -0400 Subject: [PATCH 08/12] Fix assert_type import --- stdlib/@tests/test_cases/check_SupportsGetItem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stdlib/@tests/test_cases/check_SupportsGetItem.py b/stdlib/@tests/test_cases/check_SupportsGetItem.py index 5319665a6be3..7d808ce56d2e 100644 --- a/stdlib/@tests/test_cases/check_SupportsGetItem.py +++ b/stdlib/@tests/test_cases/check_SupportsGetItem.py @@ -3,7 +3,8 @@ from _typeshed import SupportsDunderGT, SupportsDunderLT, SupportsGetItem from collections.abc import Callable from operator import itemgetter -from typing import Any, TypeVar, assert_type +from typing import Any, TypeVar +from typing_extensions import assert_type _T = TypeVar("_T") From ce9ad31fc115600d9872f8841ecf21ee60af1894 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 30 Sep 2025 16:42:48 -0400 Subject: [PATCH 09/12] Update comments for `itemgetter.__call__` --- stdlib/operator.pyi | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/stdlib/operator.pyi b/stdlib/operator.pyi index 33268b3b504d..c916b043f266 100644 --- a/stdlib/operator.pyi +++ b/stdlib/operator.pyi @@ -195,7 +195,7 @@ class attrgetter(Generic[_T_co]): def __call__(self, obj: Any, /) -> _T_co: ... @final -class itemgetter(Generic[_T_co]): +class itemgetter(Generic[_T]): @overload def __new__(cls, item: _T, /) -> itemgetter[_T]: ... @overload @@ -203,8 +203,13 @@ class itemgetter(Generic[_T_co]): # __key: _KT_contra in SupportsGetItem seems to be causing variance issues, ie: # TypeVar "_KT_contra@SupportsGetItem" is contravariant # "tuple[int, int]" is incompatible with protocol "SupportsIndex" - # preventing [_T_co, _T] instead of [Any, _T] - def __call__(self, obj: SupportsGetItem[Any, _T]) -> _T: ... + # preventing [_T_co, ...] instead of [Any, ...] + # + # If we can't infer a literal key from __new__ (ie: `itemgetter[Literal[0]]` for `itemgetter(0)`), + # then we can't annotate __call__'s return type or it'll break on tuples + # + # These issues are best demonstrated by the `itertools.check_itertools_recipes.unique_justseen` test. + def __call__(self, obj: SupportsGetItem[Any, Any]) -> Any: ... @final class methodcaller: From 0bd80fcded13fe127a2cfb556c0179bf8ceb901c Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 30 Sep 2025 16:46:52 -0400 Subject: [PATCH 10/12] Comment out failing added test --- stdlib/@tests/test_cases/check_SupportsGetItem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/@tests/test_cases/check_SupportsGetItem.py b/stdlib/@tests/test_cases/check_SupportsGetItem.py index 7d808ce56d2e..feb140438b2c 100644 --- a/stdlib/@tests/test_cases/check_SupportsGetItem.py +++ b/stdlib/@tests/test_cases/check_SupportsGetItem.py @@ -23,7 +23,7 @@ def standalone_call(obj: SupportsGetItem[Any, _T]) -> _T: ... # Regression tests for https://github.com/python/mypy/issues/14032 -assert_type(itemgetter("")({"first": 1, "second": 2}), int) +# assert_type(itemgetter("first")({"first": 1, "second": 2}), int) # See comment on itemgetter.__call__ assert_type(min({"first": 1, "second": 2}, key=itemgetter(1)), str) assert_type(min({"first": 1, "second": 2}.items(), key=itemgetter(1)), tuple[str, int]) assert_type(standalone_call({"first": 1, "second": 2}), int) From f15fca8dbbcdb667527877ffea693f1c23349546 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 7 Oct 2025 11:15:51 -0400 Subject: [PATCH 11/12] forgot to revert a line --- stdlib/operator.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/operator.pyi b/stdlib/operator.pyi index c916b043f266..2f919514b0b8 100644 --- a/stdlib/operator.pyi +++ b/stdlib/operator.pyi @@ -195,7 +195,7 @@ class attrgetter(Generic[_T_co]): def __call__(self, obj: Any, /) -> _T_co: ... @final -class itemgetter(Generic[_T]): +class itemgetter(Generic[_T_co]): @overload def __new__(cls, item: _T, /) -> itemgetter[_T]: ... @overload From 5c56f60f790b8cdf6ec58079971ca469a9f87232 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 7 Oct 2025 11:19:42 -0400 Subject: [PATCH 12/12] Adapt comments in six --- stubs/six/@tests/stubtest_allowlist.txt | 2 ++ stubs/six/six/__init__.pyi | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/stubs/six/@tests/stubtest_allowlist.txt b/stubs/six/@tests/stubtest_allowlist.txt index 5c638491dd63..83d20bf7bb4f 100644 --- a/stubs/six/@tests/stubtest_allowlist.txt +++ b/stubs/six/@tests/stubtest_allowlist.txt @@ -14,6 +14,8 @@ six.get_method_self six.viewitems six.viewkeys six.viewvalues +# Should be `byte2int: operator.itemgetter[int]`. But `itemgetter.__call__` returns `Any` +six.byte2int # Utils six.Module_six_moves_urllib diff --git a/stubs/six/six/__init__.pyi b/stubs/six/six/__init__.pyi index b2a140f403b4..c59e91ccb3be 100644 --- a/stubs/six/six/__init__.pyi +++ b/stubs/six/six/__init__.pyi @@ -2,7 +2,7 @@ import builtins import operator import types import unittest -from _typeshed import IdentityFunction, Unused +from _typeshed import IdentityFunction, SupportsGetItem, Unused from builtins import next as next from collections.abc import Callable, ItemsView, Iterable, Iterator as _Iterator, KeysView, Mapping, ValuesView from functools import wraps as wraps @@ -61,7 +61,9 @@ unichr = chr def int2byte(i: int) -> bytes: ... -byte2int: operator.itemgetter[int] +# Should be `byte2int: operator.itemgetter[int]`. But `itemgetter.__call__` returns `Any` +def byte2int(obj: SupportsGetItem[int, _T]) -> _T: ... + indexbytes = operator.getitem iterbytes = iter