Skip to content

Commit 5783af4

Browse files
authored
Fix inference for properties with __call__ (#15926)
Fixes #5858
1 parent 2298829 commit 5783af4

File tree

2 files changed

+30
-8
lines changed

2 files changed

+30
-8
lines changed

mypy/checkmember.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from typing import TYPE_CHECKING, Callable, Sequence, cast
5+
from typing import TYPE_CHECKING, Callable, Optional, Sequence, cast
66

77
from mypy import meet, message_registry, subtypes
88
from mypy.erasetype import erase_typevars
@@ -776,12 +776,17 @@ def analyze_var(
776776
freeze_all_type_vars(t)
777777
result: Type = t
778778
typ = get_proper_type(typ)
779-
if (
780-
var.is_initialized_in_class
781-
and (not is_instance_var(var) or mx.is_operator)
782-
and isinstance(typ, FunctionLike)
783-
and not typ.is_type_obj()
784-
):
779+
780+
call_type: Optional[ProperType] = None
781+
if var.is_initialized_in_class and (not is_instance_var(var) or mx.is_operator):
782+
if isinstance(typ, FunctionLike) and not typ.is_type_obj():
783+
call_type = typ
784+
elif var.is_property:
785+
call_type = get_proper_type(_analyze_member_access("__call__", typ, mx))
786+
else:
787+
call_type = typ
788+
789+
if isinstance(call_type, FunctionLike) and not call_type.is_type_obj():
785790
if mx.is_lvalue:
786791
if var.is_property:
787792
if not var.is_settable_property:
@@ -792,7 +797,7 @@ def analyze_var(
792797
if not var.is_staticmethod:
793798
# Class-level function objects and classmethods become bound methods:
794799
# the former to the instance, the latter to the class.
795-
functype = typ
800+
functype: FunctionLike = call_type
796801
# Use meet to narrow original_type to the dispatched type.
797802
# For example, assume
798803
# * A.f: Callable[[A1], None] where A1 <: A (maybe A1 == A)

test-data/unit/check-functions.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3158,3 +3158,20 @@ class C(A, B):
31583158
class D(A, B):
31593159
def f(self, z: int) -> str: pass # E: Method "f" is not using @override but is overriding a method in class "__main__.A"
31603160
[typing fixtures/typing-override.pyi]
3161+
3162+
[case testCallableProperty]
3163+
from typing import Callable
3164+
3165+
class something_callable:
3166+
def __call__(self, fn) -> str: ...
3167+
3168+
def decorator(fn: Callable[..., int]) -> something_callable: ...
3169+
3170+
class A:
3171+
@property
3172+
@decorator
3173+
def f(self) -> int: ...
3174+
3175+
reveal_type(A.f) # N: Revealed type is "__main__.something_callable"
3176+
reveal_type(A().f) # N: Revealed type is "builtins.str"
3177+
[builtins fixtures/property.pyi]

0 commit comments

Comments
 (0)