From 383486c616d0e43ea68f9044ffc45b429e4c6ba5 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 20 Aug 2025 15:36:36 -0700 Subject: [PATCH 1/2] stubtest: do not require @disjoint_base if there are __slots__ --- mypy/stubtest.py | 4 +++- mypy/test/teststubtest.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 43da0518b3f9..e90e072615a5 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -510,7 +510,9 @@ def _verify_disjoint_base( if stub.is_final: return is_disjoint_runtime = _is_disjoint_base(runtime) - if is_disjoint_runtime and not stub.is_disjoint_base: + # Don't complain about missing @disjoint_base if there are __slots__, because + # in that case we can infer that it's a disjoint base. + if is_disjoint_runtime and not stub.is_disjoint_base and stub.slots is None: yield Error( object_path, "is a disjoint base at runtime, but isn't marked with @disjoint_base in the stub", diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 69e2abe62f85..5bcf4a36bbc5 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -1430,6 +1430,19 @@ class BytesEnum(bytes, enum.Enum): """, error=None, ) + yield Case( + stub=""" + class HasSlotsAndNothingElse: + __slots__ = ("x",) + x: int + """, + runtime=""" + class HasSlotsAndNothingElse: + __slots__ = ("x",) + x: int + """, + error=None, + ) @collect_cases def test_decorator(self) -> Iterator[Case]: From c4fbcbd7110d63dae9ad8de5a4dd27f1f310540e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 20 Aug 2025 17:37:43 -0700 Subject: [PATCH 2/2] fix with slots --- mypy/stubtest.py | 2 +- mypy/test/teststubtest.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index e90e072615a5..f560049f2ec8 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -512,7 +512,7 @@ def _verify_disjoint_base( is_disjoint_runtime = _is_disjoint_base(runtime) # Don't complain about missing @disjoint_base if there are __slots__, because # in that case we can infer that it's a disjoint base. - if is_disjoint_runtime and not stub.is_disjoint_base and stub.slots is None: + if is_disjoint_runtime and not stub.is_disjoint_base and not runtime.__dict__.get("__slots__"): yield Error( object_path, "is a disjoint base at runtime, but isn't marked with @disjoint_base in the stub", diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 5bcf4a36bbc5..e6bc2c818164 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -1435,11 +1435,23 @@ class BytesEnum(bytes, enum.Enum): class HasSlotsAndNothingElse: __slots__ = ("x",) x: int + + class HasInheritedSlots(HasSlotsAndNothingElse): + pass + + class HasEmptySlots: + __slots__ = () """, runtime=""" class HasSlotsAndNothingElse: __slots__ = ("x",) x: int + + class HasInheritedSlots(HasSlotsAndNothingElse): + pass + + class HasEmptySlots: + __slots__ = () """, error=None, )