Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mypyc/irbuild/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,10 @@ def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: Supe
return translate_call(builder, expr, callee)

decl = base.method_decl(callee.name)
if decl.is_prop_getter:
# super().prop(...) calls the property's value, so get it through super()
return translate_call(builder, expr, callee)

arg_values = [builder.accept(arg) for arg in expr.args]
arg_kinds, arg_names = expr.arg_kinds.copy(), expr.arg_names.copy()

Expand Down
7 changes: 6 additions & 1 deletion mypyc/irbuild/ll_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1489,7 +1489,12 @@ def gen_method_call(
and not base.type.class_ir.builtin_base
):
name = name if base.type.class_ir.is_ext_class else fast_name
if base.type.class_ir.has_method(name):
# Property getters are also stored as methods, but obj.prop(...) calls the
# property's value, so handle properties like attributes below.
if (
base.type.class_ir.has_method(name)
and not base.type.class_ir.method_decl(name).is_prop_getter
):
decl = base.type.class_ir.method_decl(name)
if arg_kinds is None:
assert arg_names is None, "arg_kinds not present but arg_names is"
Expand Down
36 changes: 36 additions & 0 deletions mypyc/test-data/irbuild-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,42 @@ L0:
r2 = r1.x
return r2

[case testPropertyReturningCallable]
from typing import Callable

class C:
_f: Callable[[int], int]

@property
def f(self) -> Callable[[int], int]:
return self._f

def g(c: C) -> int:
return c.f(1)
[out]
def C.f(self):
self :: __main__.C
r0 :: object
L0:
r0 = self._f
return r0
def g(c):
c :: __main__.C
r0, r1 :: object
r2 :: object[1]
r3 :: object_ptr
r4 :: object
r5 :: int
L0:
r0 = c.f
r1 = object 1
r2 = [r1]
r3 = load_address r2
r4 = PyObject_Vectorcall(r0, r3, 1, 0)
keep_alive r1
r5 = unbox(int, r4)
return r5

[case testBorrowResultOfCustomGetItemInIfStatement_withgil]
from typing import List

Expand Down
83 changes: 83 additions & 0 deletions mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -2129,6 +2129,89 @@ Represents a sequence of values. Updates itself by next, which is a new value.
3
3

[case testPropertyReturningCallable]
from typing import Any, Callable
from mypy_extensions import trait

class Config:
def __init__(self) -> None:
self._sorting_function: Callable[..., list[str]] | None = None

@property
def sorting_function(self) -> Callable[..., list[str]]:
if self._sorting_function is not None:
return self._sorting_function
self._sorting_function = sorted
return self._sorting_function

def sort(self, items: list[str]) -> list[str]:
return self.sorting_function(items)

class Item:
def __init__(self, n: int) -> None:
self.n = n

class C:
@property
def no_args(self) -> Callable[[], str]:
return lambda: "called"

@property
def any_value(self) -> Any:
return lambda x: x * 2

@property
def item_type(self) -> type[Item]:
return Item

class Base:
@property
def f(self) -> Callable[[int], int]:
return lambda x: x + 1

class Derived(Base):
def call_super(self, x: int) -> int:
return super().f(x)

class AttrOverride(Base):
# A builtin, so that it isn't bound as a method when interpreted
f: Callable[[int], int] = abs

@trait
class T:
@property
def f(self) -> Callable[[int], int]:
return lambda x: x - 1

class UsesTrait(T):
pass

def call_f(b: Base, x: int) -> int:
return b.f(x)

def call_trait_f(t: T, x: int) -> int:
return t.f(x)

def test_property_returning_callable() -> None:
config = Config()
assert config.sorting_function(["b2", "b10", "b1"]) == ["b1", "b10", "b2"]
assert config.sorting_function(["a", "b"], reverse=True) == ["b", "a"]
assert config.sort(["b", "a"]) == ["a", "b"]

def test_property_returning_other_callables() -> None:
c = C()
assert c.no_args() == "called"
assert c.any_value(21) == 42
assert c.item_type(5).n == 5

def test_property_returning_callable_inherited() -> None:
assert call_f(Base(), 1) == 2
assert call_f(Derived(), 1) == 2
assert Derived().call_super(2) == 3
assert call_f(AttrOverride(), -5) == 5
assert AttrOverride().f(-6) == 6
assert call_trait_f(UsesTrait(), 1) == 0

[case testPropertySetters]

from mypy_extensions import trait
Expand Down
Loading