From 831c28219432e0e0371029ae61956f159c32e949 Mon Sep 17 00:00:00 2001 From: subotac <73706465+subotac@users.noreply.github.com> Date: Fri, 31 Jul 2026 21:17:38 +0300 Subject: [PATCH 1/5] Fix set xor return types for abstract sets --- stdlib/@tests/test_cases/builtins/check_set.py | 8 ++++++++ stdlib/builtins.pyi | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/stdlib/@tests/test_cases/builtins/check_set.py b/stdlib/@tests/test_cases/builtins/check_set.py index 89cb8683bfe1..0cd429299ea5 100644 --- a/stdlib/@tests/test_cases/builtins/check_set.py +++ b/stdlib/@tests/test_cases/builtins/check_set.py @@ -1,3 +1,4 @@ +from collections.abc import Set as AbstractSet from typing_extensions import Literal, assert_type @@ -55,3 +56,10 @@ def test_frozenset_interface(s: frozenset[Literal["foo", "bar"]], y: frozenset[s assert_type(s & y, frozenset[Literal["foo", "bar"]]) assert_type(s | y, frozenset[str]) assert_type(s ^ y, frozenset[str]) + + +def test_xor_with_abstract_set( + s: set[Literal["foo", "bar"]], frozen: frozenset[Literal["foo", "bar"]], other: AbstractSet[str] +) -> None: + assert_type(s ^ other, AbstractSet[str]) + assert_type(frozen ^ other, AbstractSet[str]) diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index 3c549d97316c..16c4a06062ba 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -1460,7 +1460,12 @@ class set(MutableSet[_T]): def __ior__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] def __sub__(self, value: AbstractSet[object], /) -> set[_T]: ... def __isub__(self, value: AbstractSet[object], /) -> Self: ... - def __xor__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... + + @overload + def __xor__(self, value: set[_S] | frozenset[_S], /) -> set[_T | _S]: ... + @overload + def __xor__(self, value: AbstractSet[_S], /) -> AbstractSet[_T | _S]: ... + def __ixor__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] def __le__(self, value: AbstractSet[object], /) -> bool: ... def __lt__(self, value: AbstractSet[object], /) -> bool: ... @@ -1491,7 +1496,12 @@ class frozenset(AbstractSet[_T_co]): def __and__(self, value: AbstractSet[object], /) -> frozenset[_T_co]: ... def __or__(self, value: AbstractSet[_S], /) -> frozenset[_T_co | _S]: ... def __sub__(self, value: AbstractSet[object], /) -> frozenset[_T_co]: ... - def __xor__(self, value: AbstractSet[_S], /) -> frozenset[_T_co | _S]: ... + + @overload + def __xor__(self, value: set[_S] | frozenset[_S], /) -> frozenset[_T_co | _S]: ... + @overload + def __xor__(self, value: AbstractSet[_S], /) -> AbstractSet[_T_co | _S]: ... + def __le__(self, value: AbstractSet[object], /) -> bool: ... def __lt__(self, value: AbstractSet[object], /) -> bool: ... def __ge__(self, value: AbstractSet[object], /) -> bool: ... From 39e1136d035a6b715a453fda15b708bf46c2b779 Mon Sep 17 00:00:00 2001 From: subotac <73706465+subotac@users.noreply.github.com> Date: Sun, 13 Sep 2026 18:24:04 +0300 Subject: [PATCH 2/5] Correct set xor signatures for abstract sets --- stdlib/@tests/test_cases/builtins/check_set.py | 4 ++++ stdlib/builtins.pyi | 12 ++++-------- stdlib/typing.pyi | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/stdlib/@tests/test_cases/builtins/check_set.py b/stdlib/@tests/test_cases/builtins/check_set.py index 0cd429299ea5..246a4ac95295 100644 --- a/stdlib/@tests/test_cases/builtins/check_set.py +++ b/stdlib/@tests/test_cases/builtins/check_set.py @@ -61,5 +61,9 @@ def test_frozenset_interface(s: frozenset[Literal["foo", "bar"]], y: frozenset[s def test_xor_with_abstract_set( s: set[Literal["foo", "bar"]], frozen: frozenset[Literal["foo", "bar"]], other: AbstractSet[str] ) -> None: + s.__xor__(other) # type: ignore + frozen.__xor__(other) # type: ignore + assert_type(other.__rxor__(s), AbstractSet[str]) + assert_type(other.__rxor__(frozen), AbstractSet[str]) assert_type(s ^ other, AbstractSet[str]) assert_type(frozen ^ other, AbstractSet[str]) diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index 16c4a06062ba..12dd72f90e05 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -1461,10 +1461,8 @@ class set(MutableSet[_T]): def __sub__(self, value: AbstractSet[object], /) -> set[_T]: ... def __isub__(self, value: AbstractSet[object], /) -> Self: ... - @overload - def __xor__(self, value: set[_S] | frozenset[_S], /) -> set[_T | _S]: ... - @overload - def __xor__(self, value: AbstractSet[_S], /) -> AbstractSet[_T | _S]: ... + def __xor__(self, value: set[_S] | frozenset[_S], /) -> set[_T | _S]: ... # type: ignore[override] + def __rxor__(self, value: set[_S] | frozenset[_S], /) -> set[_T | _S]: ... # type: ignore[override,misc] def __ixor__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] def __le__(self, value: AbstractSet[object], /) -> bool: ... @@ -1497,10 +1495,8 @@ class frozenset(AbstractSet[_T_co]): def __or__(self, value: AbstractSet[_S], /) -> frozenset[_T_co | _S]: ... def __sub__(self, value: AbstractSet[object], /) -> frozenset[_T_co]: ... - @overload - def __xor__(self, value: set[_S] | frozenset[_S], /) -> frozenset[_T_co | _S]: ... - @overload - def __xor__(self, value: AbstractSet[_S], /) -> AbstractSet[_T_co | _S]: ... + def __xor__(self, value: set[_S] | frozenset[_S], /) -> frozenset[_T_co | _S]: ... # type: ignore[override] + def __rxor__(self, value: set[_S] | frozenset[_S], /) -> frozenset[_T_co | _S]: ... # type: ignore[override,misc] def __le__(self, value: AbstractSet[object], /) -> bool: ... def __lt__(self, value: AbstractSet[object], /) -> bool: ... diff --git a/stdlib/typing.pyi b/stdlib/typing.pyi index 35031a27445b..88f18250dcfe 100644 --- a/stdlib/typing.pyi +++ b/stdlib/typing.pyi @@ -742,6 +742,7 @@ class AbstractSet(Collection[_T_co]): def __or__(self, other: AbstractSet[_T], /) -> AbstractSet[_T_co | _T]: ... def __sub__(self, other: AbstractSet[Any], /) -> AbstractSet[_T_co]: ... def __xor__(self, other: AbstractSet[_T], /) -> AbstractSet[_T_co | _T]: ... + def __rxor__(self, other: AbstractSet[_T], /) -> AbstractSet[_T_co | _T]: ... def __eq__(self, other: object, /) -> bool: ... def isdisjoint(self, other: Iterable[Any], /) -> bool: ... From 94a32b325beac49263daf837fa933ec96de90c7b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:31:32 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/builtins.pyi | 4 ---- 1 file changed, 4 deletions(-) diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index 12dd72f90e05..cd2051710b60 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -1460,10 +1460,8 @@ class set(MutableSet[_T]): def __ior__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] def __sub__(self, value: AbstractSet[object], /) -> set[_T]: ... def __isub__(self, value: AbstractSet[object], /) -> Self: ... - def __xor__(self, value: set[_S] | frozenset[_S], /) -> set[_T | _S]: ... # type: ignore[override] def __rxor__(self, value: set[_S] | frozenset[_S], /) -> set[_T | _S]: ... # type: ignore[override,misc] - def __ixor__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] def __le__(self, value: AbstractSet[object], /) -> bool: ... def __lt__(self, value: AbstractSet[object], /) -> bool: ... @@ -1494,10 +1492,8 @@ class frozenset(AbstractSet[_T_co]): def __and__(self, value: AbstractSet[object], /) -> frozenset[_T_co]: ... def __or__(self, value: AbstractSet[_S], /) -> frozenset[_T_co | _S]: ... def __sub__(self, value: AbstractSet[object], /) -> frozenset[_T_co]: ... - def __xor__(self, value: set[_S] | frozenset[_S], /) -> frozenset[_T_co | _S]: ... # type: ignore[override] def __rxor__(self, value: set[_S] | frozenset[_S], /) -> frozenset[_T_co | _S]: ... # type: ignore[override,misc] - def __le__(self, value: AbstractSet[object], /) -> bool: ... def __lt__(self, value: AbstractSet[object], /) -> bool: ... def __ge__(self, value: AbstractSet[object], /) -> bool: ... From a3225c169f288d854c3ec2f98c769af1cce9bd3f Mon Sep 17 00:00:00 2001 From: subotac <73706465+subotac@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:18:37 +0200 Subject: [PATCH 4/5] Accept iterable operands in AbstractSet reflected xor --- stdlib/@tests/test_cases/builtins/check_set.py | 3 +++ stdlib/typing.pyi | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/stdlib/@tests/test_cases/builtins/check_set.py b/stdlib/@tests/test_cases/builtins/check_set.py index 246a4ac95295..545177169889 100644 --- a/stdlib/@tests/test_cases/builtins/check_set.py +++ b/stdlib/@tests/test_cases/builtins/check_set.py @@ -65,5 +65,8 @@ def test_xor_with_abstract_set( frozen.__xor__(other) # type: ignore assert_type(other.__rxor__(s), AbstractSet[str]) assert_type(other.__rxor__(frozen), AbstractSet[str]) + assert_type(other.__rxor__([1]), AbstractSet[str | int]) + assert_type([1] ^ other, AbstractSet[str | int]) + assert_type(("foo", "baz") ^ other, AbstractSet[str]) assert_type(s ^ other, AbstractSet[str]) assert_type(frozen ^ other, AbstractSet[str]) diff --git a/stdlib/typing.pyi b/stdlib/typing.pyi index fd6440a3b9a9..b8b90a0934e5 100644 --- a/stdlib/typing.pyi +++ b/stdlib/typing.pyi @@ -744,7 +744,7 @@ class AbstractSet(Collection[_T_co]): def __or__(self, other: AbstractSet[_T], /) -> AbstractSet[_T_co | _T]: ... def __sub__(self, other: AbstractSet[Any], /) -> AbstractSet[_T_co]: ... def __xor__(self, other: AbstractSet[_T], /) -> AbstractSet[_T_co | _T]: ... - def __rxor__(self, other: AbstractSet[_T], /) -> AbstractSet[_T_co | _T]: ... + def __rxor__(self, other: Iterable[_T], /) -> AbstractSet[_T_co | _T]: ... def __eq__(self, other: object, /) -> bool: ... def isdisjoint(self, other: Iterable[Any], /) -> bool: ... From 60b9638414d820e074d477c7e282238cced9422d Mon Sep 17 00:00:00 2001 From: subotac <73706465+subotac@users.noreply.github.com> Date: Wed, 28 Oct 2026 11:04:07 +0200 Subject: [PATCH 5/5] Preserve narrower reflected xor overrides --- stdlib/multiprocessing/managers.pyi | 2 +- stubs/boltons/boltons/setutils.pyi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/multiprocessing/managers.pyi b/stdlib/multiprocessing/managers.pyi index 0f1d4e87b0dc..0727f60f1080 100644 --- a/stdlib/multiprocessing/managers.pyi +++ b/stdlib/multiprocessing/managers.pyi @@ -195,7 +195,7 @@ if sys.version_info >= (3, 14): def __rand__(self, value: AbstractSet[object], /) -> set[_T]: ... def __ror__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... # type: ignore[misc] def __rsub__(self, value: AbstractSet[_T], /) -> set[_T]: ... - def __rxor__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... # type: ignore[misc] + def __rxor__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... # type: ignore[override,misc] def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... class SetProxy(_BaseSetProxy[_T]): ... diff --git a/stubs/boltons/boltons/setutils.pyi b/stubs/boltons/boltons/setutils.pyi index d94de1967fc0..9dd1edee1a73 100644 --- a/stubs/boltons/boltons/setutils.pyi +++ b/stubs/boltons/boltons/setutils.pyi @@ -39,7 +39,7 @@ class IndexedSet(MutableSet[Any]): __rand__ = intersection # __sub__ = difference # __xor__ = symmetric_difference - __rxor__ = symmetric_difference + __rxor__ = symmetric_difference # type: ignore[assignment] # pyright: ignore[reportAssignmentType] def __rsub__(self, other: _RSub[_T_co]) -> _RSub[_T_co]: ... def update(self, *others: Iterable[Any]) -> None: ... def intersection_update(self, *others: Iterable[Any]) -> None: ...