From 3f7202c59ae628850c4b038b53c984c505cb883b Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 15 Apr 2026 20:07:22 -0700 Subject: [PATCH 1/5] gh-148639: Implement PEP 800 (typing.disjoint_base) --- Doc/library/typing.rst | 24 ++++++++++++++++++++++++ Doc/reference/compound_stmts.rst | 2 ++ Doc/whatsnew/3.15.rst | 8 ++++++++ Lib/test/test_typing.py | 14 +++++++++++++- Lib/typing.py | 24 ++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 2ce868cf84da9d..bd6b1761859b1d 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -3358,6 +3358,30 @@ 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. .. decorator:: type_check_only diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 0cf0a41bfb400c..f92d4cc8def15f 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -1565,6 +1565,8 @@ child class. It must be a metaclass that is a subclass of all other candidate metaclasses. If no such metaclass exists among the candidates, the class cannot be created, as explained in :ref:`metaclass-determination`. +.. _solid-bases: + Finally, the instance layouts of the bases must be compatible. This means that it must be possible to compute a *solid base* for the class. Exactly which classes are solid bases depends on the Python implementation. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index ed07afd2277cc0..4dec5d9760a4d5 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -80,6 +80,7 @@ Summary -- Release highlights * :pep:`728`: ``TypedDict`` with typed extra items * :pep:`747`: :ref:`Annotating type forms with TypeForm ` +* :pep:`800`: Disjoint bases in the type system * :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object ` * :ref:`The JIT compiler has been significantly upgraded ` @@ -1240,6 +1241,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 ----------- diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 9c0172f6ba7f23..3fb974c517da7c 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -29,7 +29,7 @@ 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 @@ def bar(self): 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() diff --git a/Lib/typing.py b/Lib/typing.py index e78fb8b71a996c..9ea1cb14a8e591 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -126,6 +126,7 @@ 'cast', 'clear_overloads', 'dataclass_transform', + 'disjoint_base', 'evaluate_forward_ref', 'final', 'get_args', @@ -2794,6 +2795,29 @@ class Other(Leaf): # Error reported by type checker 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 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. From 27d989a3ea12c702ffecc0229265e1547aa3ae04 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 15 Apr 2026 20:10:15 -0700 Subject: [PATCH 2/5] did not end up using that --- Doc/reference/compound_stmts.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index f92d4cc8def15f..0cf0a41bfb400c 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -1565,8 +1565,6 @@ child class. It must be a metaclass that is a subclass of all other candidate metaclasses. If no such metaclass exists among the candidates, the class cannot be created, as explained in :ref:`metaclass-determination`. -.. _solid-bases: - Finally, the instance layouts of the bases must be compatible. This means that it must be possible to compute a *solid base* for the class. Exactly which classes are solid bases depends on the Python implementation. From 0cd8736d482788e4ccf3ed4e97cb0105e8e1d5e8 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 15 Apr 2026 20:32:58 -0700 Subject: [PATCH 3/5] blurb --- .../next/Library/2026-04-15-20-32-55.gh-issue-148639.-dwsjB.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-04-15-20-32-55.gh-issue-148639.-dwsjB.rst 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 index 00000000000000..d7acdb0983837a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-15-20-32-55.gh-issue-148639.-dwsjB.rst @@ -0,0 +1,2 @@ +Implement :pep:`800`, adding the :deco:`typing.disjoint_base` decorator. +Patch by Jelle Zijlstra. From adb6834fc7cf3725dcc7b5cd5260459e5f41a760 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 16 Apr 2026 05:51:50 -0700 Subject: [PATCH 4/5] Apply suggestion from @Viicos Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com> --- Doc/library/typing.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index bd6b1761859b1d..28ef0bf376619b 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -3383,6 +3383,8 @@ Functions and decorators 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. + .. versionadded:: next + .. decorator:: type_check_only Decorator to mark a class or function as unavailable at runtime. From 90b4418e6eaf5cbfb411e151f6c01e6bdb0c7080 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 16 Apr 2026 05:55:31 -0700 Subject: [PATCH 5/5] Update Doc/library/typing.rst Co-authored-by: Petr Viktorin --- Doc/library/typing.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 28ef0bf376619b..4aba2b43a1ebdc 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -3383,6 +3383,10 @@ Functions and decorators 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