From 1e70ae37a8d2c82408776a7cd9501435a9663b12 Mon Sep 17 00:00:00 2001 From: Pranav Manglik Date: Thu, 19 Mar 2026 21:39:03 +0530 Subject: [PATCH 1/5] Undefined names used as decorators on top of TypedDict are not reported --- mypy/semanal.py | 2 ++ test-data/unit/check-typeddict.test | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index d944c60cd16e4..f718a0b4f6a10 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2113,6 +2113,8 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: and not has_placeholder(defn.info.typeddict_type) ): # This is a valid TypedDict, and it is fully analyzed. + for decorator in defn.decorators: + decorator.accept(self) return True is_typeddict, info = self.typed_dict_analyzer.analyze_typeddict_classdef(defn) if is_typeddict: diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 43fcf13e7be17..1924af8a32199 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -233,6 +233,18 @@ class D(TypedDict): d = D() reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {})" [builtins fixtures/dict.pyi] +[case testTypedDictDecoratorUndefinedNames] +from typing import TypedDict + +@abc # E: Name "abc" is not defined +@efg.hij # E: Name "efg" is not defined +@klm[nop] # E: Name "klm" is not defined \ + # E: Name "nop" is not defined +@qrs.tuv[wxy] # E: Name "qrs" is not defined \ + # E: Name "wxy" is not defined +class A(TypedDict): + x: int +[builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] [case testTypedDictWithClassmethodAlternativeConstructorDoesNotCrash] From 3b85d2ce75329a5ddd4dfcc5e2a2a7e3b544b7d7 Mon Sep 17 00:00:00 2001 From: Pranav Manglik Date: Tue, 7 Apr 2026 00:56:53 +0530 Subject: [PATCH 2/5] fix CI fail --- test-data/unit/check-typeddict.test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 1924af8a32199..261a32e692a97 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -233,6 +233,8 @@ class D(TypedDict): d = D() reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {})" [builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testTypedDictDecoratorUndefinedNames] from typing import TypedDict From 64675189624a16358a515349183a4f787e6a5d47 Mon Sep 17 00:00:00 2001 From: Pranav Manglik Date: Fri, 17 Apr 2026 01:44:25 +0530 Subject: [PATCH 3/5] stubtest: fix false positive for property with deleter --- mypy/stubtest.py | 8 ++++++-- mypy/test/teststubtest.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index ac0378cc54689..bdfa451a859a9 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1851,8 +1851,12 @@ def is_probably_a_function(runtime: Any) -> bool: def is_read_only_property(runtime: object) -> bool: - return isinstance(runtime, property) and runtime.fset is None - + return ( + isinstance(runtime, property) + and runtime.fset is None + and runtime.fdel is None + ) + def safe_inspect_signature(runtime: Any) -> inspect.Signature | None: if ( diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index ada69fc6e47cf..d03fc253a6b4c 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -1061,6 +1061,25 @@ class FineAndDandy: """, error=None, ) + yield Case( + stub=""" + class spam: + @property + def eggs(self) -> int: ... + @eggs.deleter + def eggs(self) -> None: ... + """, + runtime=""" + class spam: + @property + def eggs(self): + return 42 + @eggs.deleter + def eggs(self): + print("eggs") + """, + error=None, + ) @collect_cases def test_cached_property(self) -> Iterator[Case]: From bad40d71ae908764d2815f5d6af3df4179e81131 Mon Sep 17 00:00:00 2001 From: Pranav Manglik Date: Fri, 17 Apr 2026 01:50:33 +0530 Subject: [PATCH 4/5] Remove unrelated changes --- mypy/semanal.py | 2 -- test-data/unit/check-typeddict.test | 14 -------------- 2 files changed, 16 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index d6e03b15b1230..8fd09f5381aa5 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2113,8 +2113,6 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: and not has_placeholder(defn.info.typeddict_type) ): # This is a valid TypedDict, and it is fully analyzed. - for decorator in defn.decorators: - decorator.accept(self) return True is_typeddict, info = self.typed_dict_analyzer.analyze_typeddict_classdef(defn) if is_typeddict: diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index c74ae96d7763e..622004758364b 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -235,20 +235,6 @@ reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {})" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] -[case testTypedDictDecoratorUndefinedNames] -from typing import TypedDict - -@abc # E: Name "abc" is not defined -@efg.hij # E: Name "efg" is not defined -@klm[nop] # E: Name "klm" is not defined \ - # E: Name "nop" is not defined -@qrs.tuv[wxy] # E: Name "qrs" is not defined \ - # E: Name "wxy" is not defined -class A(TypedDict): - x: int -[builtins fixtures/dict.pyi] -[typing fixtures/typing-typeddict.pyi] - [case testTypedDictWithClassmethodAlternativeConstructorDoesNotCrash] # https://github.com/python/mypy/issues/5653 from typing import TypedDict From 69e1c8f982c285bee864a6d142230e33cf385bc7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:28:26 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/stubtest.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index bdfa451a859a9..e63e109d504b8 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1851,12 +1851,8 @@ def is_probably_a_function(runtime: Any) -> bool: def is_read_only_property(runtime: object) -> bool: - return ( - isinstance(runtime, property) - and runtime.fset is None - and runtime.fdel is None - ) - + return isinstance(runtime, property) and runtime.fset is None and runtime.fdel is None + def safe_inspect_signature(runtime: Any) -> inspect.Signature | None: if (