]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-148639: Implement PEP 800 (typing.disjoint_base) (#148640)
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Sat, 18 Apr 2026 02:20:41 +0000 (19:20 -0700)
committerGitHub <noreply@github.com>
Sat, 18 Apr 2026 02:20:41 +0000 (19:20 -0700)
Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com>
Co-authored-by: Petr Viktorin <encukou@gmail.com>
Doc/library/typing.rst
Doc/whatsnew/3.15.rst
Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2026-04-15-20-32-55.gh-issue-148639.-dwsjB.rst [new file with mode: 0644]

index 2ce868cf84da9d529a66cdf7dc7549e84176a379..9150385bd5888d3fe08501d9eaf0502d0ff70934 100644 (file)
@@ -3358,6 +3358,36 @@ Functions and decorators
 
    .. versionadded:: 3.12
 
+.. decorator:: disjoint_base
+
+   Decorator to mark a class as a disjoint base.
+
+   Type checkers do not allow child classes of a disjoint base ``C`` to
+   inherit from other disjoint bases that are not parent or child classes of ``C``.
+
+   For example::
+
+       @disjoint_base
+       class Disjoint1: pass
+
+       @disjoint_base
+       class Disjoint2: pass
+
+       class Disjoint3(Disjoint1, Disjoint2): pass  # Type checker error
+
+   Type checkers can use knowledge of disjoint bases to detect unreachable code
+   and determine when two types can overlap.
+
+   The corresponding runtime concept is a solid base (see :ref:`multiple-inheritance`).
+   Classes that are solid bases at runtime can be marked with ``@disjoint_base`` in stub files.
+   Users may also mark other classes as disjoint bases to indicate to type checkers that
+   multiple inheritance with other disjoint bases should not be allowed.
+
+   Note that the concept of a solid base is a CPython implementation
+   detail, and the exact set of standard library classes that are
+   disjoint bases at runtime may change in future versions of Python.
+
+   .. versionadded:: next
 
 .. decorator:: type_check_only
 
index 56cc71b40fce8253ead166e00b6fe60ed1267571..7cf4dc3701fa6e86dd394574e3b2e7db7d9f5f7c 100644 (file)
@@ -80,6 +80,7 @@ Summary -- Release highlights
 * :pep:`728`: ``TypedDict`` with typed extra items
 * :pep:`747`: :ref:`Annotating type forms with TypeForm
   <whatsnew315-typeform>`
+* :pep:`800`: Disjoint bases in the type system
 * :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object
   <whatsnew315-pybyteswriter>`
 * :pep:`803`: :ref:`Stable ABI for Free-Threaded Builds <whatsnew315-abi3t>`
@@ -1290,6 +1291,13 @@ typing
   as it was incorrectly inferred in runtime before.
   (Contributed by Nikita Sobolev in :gh:`137191`.)
 
+* :pep:`800`: Add :deco:`typing.disjoint_base`, a new decorator marking a class
+  as a disjoint base. This is an advanced feature primarily intended to allow
+  type checkers to faithfully reflect the runtime semantics of types defined
+  as builtins or in compiled extensions. If a class ``C`` is a disjoint base, then
+  child classes of that class cannot inherit from other disjoint bases that are
+  not parent or child classes of ``C``. (Contributed by Jelle Zijlstra in :gh:`148639`.)
+
 
 unicodedata
 -----------
index 9c0172f6ba7f23948343051113192cafb790c073..3fb974c517da7cb21269d863a62aeee3e02440a3 100644 (file)
@@ -29,7 +29,7 @@ from typing import Generic, ClassVar, Final, final, Protocol
 from typing import assert_type, cast, runtime_checkable
 from typing import get_type_hints
 from typing import get_origin, get_args, get_protocol_members
-from typing import override
+from typing import override, disjoint_base
 from typing import is_typeddict, is_protocol
 from typing import reveal_type
 from typing import dataclass_transform
@@ -10920,6 +10920,18 @@ class SpecialAttrsTests(BaseTestCase):
         self.assertNotIn('__magic__', dir_items)
 
 
+class DisjointBaseTests(BaseTestCase):
+    def test_disjoint_base_unmodified(self):
+        class C: ...
+        self.assertIs(C, disjoint_base(C))
+
+    def test_dunder_disjoint_base(self):
+        @disjoint_base
+        class C: ...
+
+        self.assertIs(C.__disjoint_base__, True)
+
+
 class RevealTypeTests(BaseTestCase):
     def test_reveal_type(self):
         obj = object()
index e78fb8b71a996c7bcb6c71b690cc536fd3562157..868fec9e088cb5f1127829ab220a7a93f7ba3660 100644 (file)
@@ -126,6 +126,7 @@ __all__ = [
     'cast',
     'clear_overloads',
     'dataclass_transform',
+    'disjoint_base',
     'evaluate_forward_ref',
     'final',
     'get_args',
@@ -2794,6 +2795,29 @@ def final(f):
     return f
 
 
+def disjoint_base(cls):
+    """This decorator marks a class as a disjoint base.
+
+    Child classes of a disjoint base cannot inherit from other disjoint bases that are
+    not parent or child classes of the disjoint base.
+
+    For example:
+
+        @disjoint_base
+        class Disjoint1: pass
+
+        @disjoint_base
+        class Disjoint2: pass
+
+        class Disjoint3(Disjoint1, Disjoint2): pass  # Type checker error
+
+    Type checkers can use knowledge of disjoint bases to detect unreachable code
+    and determine when two types can overlap.
+    """
+    cls.__disjoint_base__ = True
+    return cls
+
+
 # Some unconstrained type variables.  These were initially used by the container types.
 # They were never meant for export and are now unused, but we keep them around to
 # avoid breaking compatibility with users who import them.
diff --git a/Misc/NEWS.d/next/Library/2026-04-15-20-32-55.gh-issue-148639.-dwsjB.rst b/Misc/NEWS.d/next/Library/2026-04-15-20-32-55.gh-issue-148639.-dwsjB.rst
new file mode 100644 (file)
index 0000000..d7acdb0
--- /dev/null
@@ -0,0 +1,2 @@
+Implement :pep:`800`, adding the :deco:`typing.disjoint_base` decorator.
+Patch by Jelle Zijlstra.