diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 5ef8d8594c15..c7f3748e8f22 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -1553,8 +1553,7 @@ def is_native_attr_ref(self, expr: MemberExpr) -> bool: return ( isinstance(obj_rtype, RInstance) and obj_rtype.class_ir.is_ext_class - and obj_rtype.class_ir.has_attr(expr.name) - and not obj_rtype.class_ir.get_method(expr.name) + and any(expr.name in ir.attributes for ir in obj_rtype.class_ir.mro) ) def mark_block_unreachable(self) -> None: diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 877c40699cda..44f952916f52 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -5925,3 +5925,47 @@ assert NonExtDict.BASE == {"x": 1} assert NonExtDict.EXTENDED == {"x": 1, "y": 2} assert NonExtChained.Z == {10, 20, 30} + +[case testPropertyGetterLeak] +class Bar: + pass + +class Foo: + def __init__(self) -> None: + self.obj: object = Bar() + + @property + def val(self) -> object: + return self.obj + +[file other.py] +import gc +from native import Foo, Bar + +def check(foo: Foo) -> bool: + return isinstance(foo.val, Bar) + +def test_property_getter_no_leak() -> None: + gc.collect() + before = gc.get_objects() + for _ in range(100): + foo = Foo() + check(foo) + gc.collect() + after = gc.get_objects() + diff = len(after) - len(before) + assert diff <= 2, diff + +test_property_getter_no_leak() + +[file driver.py] +import sys +from other import check +from native import Foo + +foo = Foo() +init = sys.getrefcount(foo.obj) +for _ in range(100): + check(foo) +after = sys.getrefcount(foo.obj) +assert after - init == 0, f"Leaked {after - init} refs"