From 57c542c922adb0d562a76d9618127bb2791a5eaa Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Sun, 21 Dec 2025 19:22:54 +0100 Subject: [PATCH 1/2] allow arbitrary types in set difference --- stdlib/@tests/test_cases/builtins/check_set.py | 7 +++++++ stdlib/builtins.pyi | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 stdlib/@tests/test_cases/builtins/check_set.py diff --git a/stdlib/@tests/test_cases/builtins/check_set.py b/stdlib/@tests/test_cases/builtins/check_set.py new file mode 100644 index 000000000000..8f89b351f95b --- /dev/null +++ b/stdlib/@tests/test_cases/builtins/check_set.py @@ -0,0 +1,7 @@ +from typing_extensions import assert_type + +# Note: type checkers / linters are free to point out that the set difference +# below is redundant. But typeshed should allow it, as its job is to describe +# what is legal in Python, not what is sensible. +x: set[str] = {"foo", "bar"} +assert_type(x - {123}, set[str]) diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index 416d793de61c..81690da5f89a 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -1282,7 +1282,7 @@ class set(MutableSet[_T]): def __iand__(self, value: AbstractSet[object], /) -> Self: ... def __or__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... def __ior__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] - def __sub__(self, value: AbstractSet[_T | None], /) -> set[_T]: ... + def __sub__(self, value: AbstractSet[object], /) -> set[_T]: ... def __isub__(self, value: AbstractSet[object], /) -> Self: ... def __xor__(self, value: AbstractSet[_S], /) -> set[_T | _S]: ... def __ixor__(self, value: AbstractSet[_T], /) -> Self: ... # type: ignore[override,misc] @@ -1313,7 +1313,7 @@ class frozenset(AbstractSet[_T_co]): def __iter__(self) -> Iterator[_T_co]: ... def __and__(self, value: AbstractSet[_T_co], /) -> frozenset[_T_co]: ... def __or__(self, value: AbstractSet[_S], /) -> frozenset[_T_co | _S]: ... - def __sub__(self, value: AbstractSet[_T_co], /) -> frozenset[_T_co]: ... + def __sub__(self, value: AbstractSet[object], /) -> frozenset[_T_co]: ... def __xor__(self, value: AbstractSet[_S], /) -> frozenset[_T_co | _S]: ... def __le__(self, value: AbstractSet[object], /) -> bool: ... def __lt__(self, value: AbstractSet[object], /) -> bool: ... From f7af3f7d067ac4e22ca912be9b6ea33dd9db8595 Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Sat, 10 Jan 2026 10:56:56 +0100 Subject: [PATCH 2/2] updated test --- stdlib/@tests/test_cases/builtins/check_set.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/stdlib/@tests/test_cases/builtins/check_set.py b/stdlib/@tests/test_cases/builtins/check_set.py index 8f89b351f95b..604251b0bb67 100644 --- a/stdlib/@tests/test_cases/builtins/check_set.py +++ b/stdlib/@tests/test_cases/builtins/check_set.py @@ -1,7 +1,14 @@ -from typing_extensions import assert_type +from typing_extensions import Literal, assert_type + # Note: type checkers / linters are free to point out that the set difference # below is redundant. But typeshed should allow it, as its job is to describe # what is legal in Python, not what is sensible. -x: set[str] = {"foo", "bar"} -assert_type(x - {123}, set[str]) +# For instance, set[Literal] - set[str] should be legal. +def test_set_difference(x: set[Literal["foo", "bar"]], y: set[str], z: set[int]) -> None: + assert_type(x - y, set[Literal["foo", "bar"]]) + assert_type(y - x, set[str]) + assert_type(x - z, set[Literal["foo", "bar"]]) + assert_type(z - x, set[int]) + assert_type(y - z, set[str]) + assert_type(z - y, set[int])