Skip to content

Commit 92164dc

Browse files
JelleZijlstraViicosencukou
authored
gh-148639: Implement PEP 800 (typing.disjoint_base) (#148640)
Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com> Co-authored-by: Petr Viktorin <encukou@gmail.com>
1 parent 634568d commit 92164dc

File tree

5 files changed

+77
-1
lines changed

5 files changed

+77
-1
lines changed

Doc/library/typing.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3358,6 +3358,36 @@ Functions and decorators
33583358

33593359
.. versionadded:: 3.12
33603360

3361+
.. decorator:: disjoint_base
3362+
3363+
Decorator to mark a class as a disjoint base.
3364+
3365+
Type checkers do not allow child classes of a disjoint base ``C`` to
3366+
inherit from other disjoint bases that are not parent or child classes of ``C``.
3367+
3368+
For example::
3369+
3370+
@disjoint_base
3371+
class Disjoint1: pass
3372+
3373+
@disjoint_base
3374+
class Disjoint2: pass
3375+
3376+
class Disjoint3(Disjoint1, Disjoint2): pass # Type checker error
3377+
3378+
Type checkers can use knowledge of disjoint bases to detect unreachable code
3379+
and determine when two types can overlap.
3380+
3381+
The corresponding runtime concept is a solid base (see :ref:`multiple-inheritance`).
3382+
Classes that are solid bases at runtime can be marked with ``@disjoint_base`` in stub files.
3383+
Users may also mark other classes as disjoint bases to indicate to type checkers that
3384+
multiple inheritance with other disjoint bases should not be allowed.
3385+
3386+
Note that the concept of a solid base is a CPython implementation
3387+
detail, and the exact set of standard library classes that are
3388+
disjoint bases at runtime may change in future versions of Python.
3389+
3390+
.. versionadded:: next
33613391

33623392
.. decorator:: type_check_only
33633393

Doc/whatsnew/3.15.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ Summary -- Release highlights
8080
* :pep:`728`: ``TypedDict`` with typed extra items
8181
* :pep:`747`: :ref:`Annotating type forms with TypeForm
8282
<whatsnew315-typeform>`
83+
* :pep:`800`: Disjoint bases in the type system
8384
* :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object
8485
<whatsnew315-pybyteswriter>`
8586
* :pep:`803`: :ref:`Stable ABI for Free-Threaded Builds <whatsnew315-abi3t>`
@@ -1290,6 +1291,13 @@ typing
12901291
as it was incorrectly inferred in runtime before.
12911292
(Contributed by Nikita Sobolev in :gh:`137191`.)
12921293

1294+
* :pep:`800`: Add :deco:`typing.disjoint_base`, a new decorator marking a class
1295+
as a disjoint base. This is an advanced feature primarily intended to allow
1296+
type checkers to faithfully reflect the runtime semantics of types defined
1297+
as builtins or in compiled extensions. If a class ``C`` is a disjoint base, then
1298+
child classes of that class cannot inherit from other disjoint bases that are
1299+
not parent or child classes of ``C``. (Contributed by Jelle Zijlstra in :gh:`148639`.)
1300+
12931301

12941302
unicodedata
12951303
-----------

Lib/test/test_typing.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from typing import assert_type, cast, runtime_checkable
3030
from typing import get_type_hints
3131
from typing import get_origin, get_args, get_protocol_members
32-
from typing import override
32+
from typing import override, disjoint_base
3333
from typing import is_typeddict, is_protocol
3434
from typing import reveal_type
3535
from typing import dataclass_transform
@@ -10920,6 +10920,18 @@ def bar(self):
1092010920
self.assertNotIn('__magic__', dir_items)
1092110921

1092210922

10923+
class DisjointBaseTests(BaseTestCase):
10924+
def test_disjoint_base_unmodified(self):
10925+
class C: ...
10926+
self.assertIs(C, disjoint_base(C))
10927+
10928+
def test_dunder_disjoint_base(self):
10929+
@disjoint_base
10930+
class C: ...
10931+
10932+
self.assertIs(C.__disjoint_base__, True)
10933+
10934+
1092310935
class RevealTypeTests(BaseTestCase):
1092410936
def test_reveal_type(self):
1092510937
obj = object()

Lib/typing.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
'cast',
127127
'clear_overloads',
128128
'dataclass_transform',
129+
'disjoint_base',
129130
'evaluate_forward_ref',
130131
'final',
131132
'get_args',
@@ -2794,6 +2795,29 @@ class Other(Leaf): # Error reported by type checker
27942795
return f
27952796

27962797

2798+
def disjoint_base(cls):
2799+
"""This decorator marks a class as a disjoint base.
2800+
2801+
Child classes of a disjoint base cannot inherit from other disjoint bases that are
2802+
not parent or child classes of the disjoint base.
2803+
2804+
For example:
2805+
2806+
@disjoint_base
2807+
class Disjoint1: pass
2808+
2809+
@disjoint_base
2810+
class Disjoint2: pass
2811+
2812+
class Disjoint3(Disjoint1, Disjoint2): pass # Type checker error
2813+
2814+
Type checkers can use knowledge of disjoint bases to detect unreachable code
2815+
and determine when two types can overlap.
2816+
"""
2817+
cls.__disjoint_base__ = True
2818+
return cls
2819+
2820+
27972821
# Some unconstrained type variables. These were initially used by the container types.
27982822
# They were never meant for export and are now unused, but we keep them around to
27992823
# avoid breaking compatibility with users who import them.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Implement :pep:`800`, adding the :deco:`typing.disjoint_base` decorator.
2+
Patch by Jelle Zijlstra.

0 commit comments

Comments
 (0)