diff --git a/CHANGELOG.md b/CHANGELOG.md index 8855595e..62918bc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Unreleased + +- Add the `@typing_extensions.solid_base` decorator, as specified + in PEP 800. Patch by Jelle Zijlstra. + # Release 4.14.1 (July 4, 2025) - Fix usage of `typing_extensions.TypedDict` nested inside other types diff --git a/doc/index.rst b/doc/index.rst index 21d6fa60..6569a39e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -748,6 +748,17 @@ Decorators improved, and ``typing_extensions`` backports these performance improvements. +.. decorator:: solid_base + + See :pep:`800`. A class decorator that marks a class as a "solid base", meaning that + child classes of the decorated class cannot inherit from other solid 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 + Functions ~~~~~~~~~ diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index cb3b462b..c493c9a3 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -101,6 +101,7 @@ reveal_type, runtime, runtime_checkable, + solid_base, ) NoneType = type(None) @@ -6669,6 +6670,18 @@ def cached(self): ... self.assertIs(True, Methods.cached.__final__) +class SolidBaseTests(BaseTestCase): + def test_solid_base_unmodified(self): + class C: ... + self.assertIs(C, solid_base(C)) + + def test_dunder_solid_base(self): + @solid_base + class C: ... + + self.assertIs(C.__solid_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 efa09d55..8e2c5332 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -93,6 +93,7 @@ 'reveal_type', 'runtime', 'runtime_checkable', + 'solid_base', 'Text', 'TypeAlias', 'TypeAliasType', @@ -315,6 +316,33 @@ class Other(Leaf): # Error reported by type checker return f +if hasattr(typing, "solid_base"): # 3.15 + solid_base = typing.solid_base +else: + def solid_base(cls): + """This decorator marks a class a solid base. + + Child classes of a solid base cannot inherit from other solid bases that are + not parent classes of the solid base. + + For example: + + @solid_base + class Solid1: pass + + @solid_base + class Solid2: pass + + class Solid3(Solid1, Solid2): pass # Type checker error + + Type checkers can use knowledge of solid bases to detect unreachable code + and determine when two types can overlap. + + See PEP 800.""" + cls.__solid_base__ = True + return cls + + def IntVar(name): return typing.TypeVar(name)