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 -- -------------------