diff --git a/CHANGELOG.md b/CHANGELOG.md index cc2122f6..0c6a941a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Add the `@typing_extensions.disjoint_base` decorator, as specified + in PEP 800. Patch by Jelle Zijlstra. - Add `typing_extensions.type_repr`, a backport of [`annotationlib.type_repr`](https://docs.python.org/3.14/library/annotationlib.html#annotationlib.type_repr), introduced in Python 3.14 (CPython PR [#124551](https://github.com/python/cpython/pull/124551), diff --git a/doc/index.rst b/doc/index.rst index 29572a50..6aa95f5b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -705,6 +705,17 @@ Decorators Inheriting from a deprecated class now also raises a runtime :py:exc:`DeprecationWarning`. +.. decorator:: disjoint_base + + See :pep:`800`. A class decorator that marks a class as a "disjoint base", meaning that + child classes of the decorated class cannot inherit from other disjoint bases that are not + parent classes of the decorated class. + + This helps type checkers to detect unreachable code and to understand when two types + can overlap. + + .. versionadded:: 4.15.0 + .. decorator:: final See :py:func:`typing.final` and :pep:`591`. In ``typing`` since 3.8. diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 16370bc0..1ef9f013 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -84,6 +84,7 @@ clear_overloads, dataclass_transform, deprecated, + disjoint_base, evaluate_forward_ref, final, get_annotations, @@ -6670,6 +6671,18 @@ def cached(self): ... self.assertIs(True, Methods.cached.__final__) +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/src/typing_extensions.py b/src/typing_extensions.py index bd424da9..77f33e16 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -71,6 +71,7 @@ 'clear_overloads', 'dataclass_transform', 'deprecated', + 'disjoint_base', 'Doc', 'evaluate_forward_ref', 'get_overloads', @@ -321,6 +322,33 @@ class Other(Leaf): # Error reported by type checker return f +if hasattr(typing, "disjoint_base"): # 3.15 + disjoint_base = typing.disjoint_base +else: + 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. + + See PEP 800.""" + cls.__disjoint_base__ = True + return cls + + def IntVar(name): return typing.TypeVar(name)