From f5287b50251c0342079ba9af3fdc413449ad33f3 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Mon, 16 Mar 2026 17:45:39 -0400 Subject: [PATCH] Warn when `@disjoint_base` is used on protocols or TypedDicts --- mypy/semanal.py | 7 ++++++- test-data/unit/check-protocols.test | 10 ++++++++++ test-data/unit/check-typeddict.test | 11 +++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index bf21e057345f..c88c93eda3a0 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2259,7 +2259,12 @@ def analyze_class_decorator_common( if refers_to_fullname(decorator, FINAL_DECORATOR_NAMES): info.is_final = True elif refers_to_fullname(decorator, DISJOINT_BASE_DECORATOR_NAMES): - info.is_disjoint_base = True + if info.is_protocol: + self.fail("@disjoint_base cannot be used with protocol class", decorator) + elif info.typeddict_type is not None: + self.fail("@disjoint_base cannot be used with TypedDict", decorator) + else: + info.is_disjoint_base = True elif refers_to_fullname(decorator, TYPE_CHECK_ONLY_NAMES): info.is_type_check_only = True elif (deprecated := self.get_deprecated(decorator)) is not None: diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index a51445ef0b5b..511461894db7 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -4746,3 +4746,13 @@ tmp/a.py:8: note: Expected: tmp/a.py:8: note: def f(self) -> PNested tmp/a.py:8: note: Got: tmp/a.py:8: note: def f(self) -> CNested + +[case testProtocolCannotBeDisjointBase] +from typing import Protocol +from typing_extensions import disjoint_base + +@disjoint_base # E: @disjoint_base cannot be used with protocol class +class A(Protocol): + pass + +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 69985e095124..43fcf13e7be1 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -2515,6 +2515,17 @@ class ForwardDeclared: pass [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] +[case testTypedDictCannotBeDisjointBase] +from typing import TypedDict +from typing_extensions import disjoint_base + +@disjoint_base # E: @disjoint_base cannot be used with TypedDict +class A(TypedDict): + pass + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testTypedDictTypeNarrowingWithFinalKey] from typing import Final, Optional, TypedDict