Skip to content

Commit

Permalink
Fix inference for properties with __call__ (#15926)
Browse files Browse the repository at this point in the history
Fixes #5858
  • Loading branch information
hauntsaninja authored Aug 30, 2023
1 parent 2298829 commit 5783af4
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 8 deletions.
21 changes: 13 additions & 8 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Callable, Sequence, cast
from typing import TYPE_CHECKING, Callable, Optional, Sequence, cast

from mypy import meet, message_registry, subtypes
from mypy.erasetype import erase_typevars
Expand Down Expand Up @@ -776,12 +776,17 @@ def analyze_var(
freeze_all_type_vars(t)
result: Type = t
typ = get_proper_type(typ)
if (
var.is_initialized_in_class
and (not is_instance_var(var) or mx.is_operator)
and isinstance(typ, FunctionLike)
and not typ.is_type_obj()
):

call_type: Optional[ProperType] = None
if var.is_initialized_in_class and (not is_instance_var(var) or mx.is_operator):
if isinstance(typ, FunctionLike) and not typ.is_type_obj():
call_type = typ
elif var.is_property:
call_type = get_proper_type(_analyze_member_access("__call__", typ, mx))
else:
call_type = typ

if isinstance(call_type, FunctionLike) and not call_type.is_type_obj():
if mx.is_lvalue:
if var.is_property:
if not var.is_settable_property:
Expand All @@ -792,7 +797,7 @@ def analyze_var(
if not var.is_staticmethod:
# Class-level function objects and classmethods become bound methods:
# the former to the instance, the latter to the class.
functype = typ
functype: FunctionLike = call_type
# Use meet to narrow original_type to the dispatched type.
# For example, assume
# * A.f: Callable[[A1], None] where A1 <: A (maybe A1 == A)
Expand Down
17 changes: 17 additions & 0 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -3158,3 +3158,20 @@ class C(A, B):
class D(A, B):
def f(self, z: int) -> str: pass # E: Method "f" is not using @override but is overriding a method in class "__main__.A"
[typing fixtures/typing-override.pyi]

[case testCallableProperty]
from typing import Callable

class something_callable:
def __call__(self, fn) -> str: ...

def decorator(fn: Callable[..., int]) -> something_callable: ...

class A:
@property
@decorator
def f(self) -> int: ...

reveal_type(A.f) # N: Revealed type is "__main__.something_callable"
reveal_type(A().f) # N: Revealed type is "builtins.str"
[builtins fixtures/property.pyi]

0 comments on commit 5783af4

Please sign in to comment.