From c22171ccc826adc6568f842c6aea7b83a258e5f7 Mon Sep 17 00:00:00 2001 From: Vedant Bhayani Date: Fri, 16 Oct 2026 16:38:20 +0530 Subject: [PATCH] Fix descriptor __get__/__set__ access on named tuple attributes Named tuples are represented as TupleType, but analyze_descriptor_access() returned early for any non-Instance type, so a __get__ or __set__ override defined on a named tuple class was ignored when the named tuple was used as a class attribute descriptor. Map TupleType to its fallback instance before the Instance check, matching what regular member access already does. Fixes #22193. --- mypy/checkmember.py | 5 ++++ test-data/unit/check-classes.test | 39 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 08bcdab8d0a2..488e4e90ed08 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -689,6 +689,11 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type: return make_simplified_union( [analyze_descriptor_access(typ, mx) for typ in descriptor_type.items] ) + elif isinstance(descriptor_type, TupleType): + # Named tuples (and other tuple subclasses) are represented as TupleType. + # As with regular member access, use the fallback instance to look up + # descriptor methods such as __get__ and __set__. + descriptor_type = tuple_fallback(descriptor_type) elif not isinstance(descriptor_type, Instance): return orig_descriptor_type diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 9f0d5462cba3..20ea9a1a3a9a 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2262,6 +2262,45 @@ def foo(x: Type[Union[X1, X2]]) -> None: reveal_type(x.prop) # N: Revealed type is "__main__.getter" +[case testDescriptorGetNamedTuple] +from typing import Any, NamedTuple + +class D(NamedTuple): + def __get__(self, instance: Any, owner: Any) -> str: return 's' + +class A: + f = D() + +reveal_type(A.f) # N: Revealed type is "builtins.str" +reveal_type(A().f) # N: Revealed type is "builtins.str" + +class P(NamedTuple): + x: int + +class B: + p = P(1) + +reveal_type(B.p) # N: Revealed type is "tuple[builtins.int, fallback=__main__.P]" +reveal_type(B().p) # N: Revealed type is "tuple[builtins.int, fallback=__main__.P]" +[builtins fixtures/tuple.pyi] + +[case testDescriptorSetNamedTuple] +from typing import Any, NamedTuple + +class D(NamedTuple): + def __get__(self, instance: Any, owner: Any) -> str: return 's' + def __set__(self, instance: Any, value: int) -> None: pass + +class A: + f = D() + +a = A() +a.f = 1 +reveal_type(a.f) # N: Revealed type is "builtins.str" +a.f = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[builtins fixtures/tuple.pyi] + + -- _promote decorators -- -------------------