From 5fe063eaf81724c296752bfa7bae6cd839e658ef Mon Sep 17 00:00:00 2001 From: Ryan Heard Date: Fri, 9 Oct 2026 18:47:04 -0400 Subject: [PATCH] [mypyc] Reject calling a property through class object --- mypyc/irbuild/expression.py | 27 ++++++++++++++ mypyc/test-data/irbuild-classes.test | 56 ++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 8da4a38aa1ed..65486ff5c3ba 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -394,6 +394,32 @@ def check_instance_attribute_access_through_class( ) +def check_property_call_through_class( + builder: IRBuilder, expr: CallExpr, callee: MemberExpr +) -> None: + """Report error if calling a property through class object, as in "C.prop(obj)". + + This always fails at runtime, since "C.prop" is a property object (or a getset + descriptor in a native class), but mypy may not report it. Since the getter is + stored as a method, we'd otherwise compile this as a direct call to the getter. + """ + if isinstance(callee.expr, RefExpr): + node = callee.expr.node + typ = get_proper_type(builder.types.get(callee.expr)) + if isinstance(typ, TypeType) and isinstance(typ.item, Instance): + node = typ.item.type + if isinstance(node, TypeInfo): + class_ir = builder.mapper.type_to_ir.get(node) + if ( + class_ir is not None + and class_ir.has_method(callee.name) + and class_ir.method_decl(callee.name).is_prop_getter + ): + builder.error( + f'Cannot call property "{callee.name}" through class object', expr.line + ) + + def transform_super_expr(builder: IRBuilder, o: SuperExpr) -> Value: # warning(builder, 'can not optimize super() expression', o.line) sup_val = builder.load_module_attr_by_fullname("builtins.super", o.line) @@ -499,6 +525,7 @@ def translate_method_call(builder: IRBuilder, expr: CallExpr, callee: MemberExpr This can also deal with calls to module-level functions. """ + check_property_call_through_class(builder, expr, callee) if builder.is_native_ref_expr(callee): # Call to module-level native function or such return translate_call(builder, expr, callee) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 59ab6ced0391..631750284ba7 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -1388,6 +1388,62 @@ L0: r5 = unbox(int, r4) return r5 +[case testCallPropertyThroughClass] +from typing import Callable, Type + +from mypy_extensions import mypyc_attr, trait + +class C: + _f: Callable[[int], int] + + @property + def f(self) -> Callable[[int], int]: + return self._f + + @property + def x(self) -> int: + return 0 + + @x.setter + def x(self, value: int) -> None: + pass + + def m(self) -> int: + return 0 + + @classmethod + def g(cls, c): + cls.f(c) # E: Cannot call property "f" through class object + +class D(C): + pass + +@trait +class T: + @property + def p(self) -> int: + return 0 + +@mypyc_attr(native_class=False) +class N: + @property + def p(self) -> int: + return 0 + +def f(c, d, t, n): + # mypy doesn't type check unannotated functions, so only mypyc can catch these + C.f(c) # E: Cannot call property "f" through class object + C.x(c) # E: Cannot call property "x" through class object + D.f(d)(1) # E: Cannot call property "f" through class object + T.p(t) # E: Cannot call property "p" through class object + N.p(n) # E: Cannot call property "p" through class object + +def ok(c: C, tc: Type[C]) -> None: + C.f + C.m(c) + tc.m(c) + c.f(1) + [case testBorrowResultOfCustomGetItemInIfStatement_withgil] from typing import List