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
27 changes: 27 additions & 0 deletions mypyc/irbuild/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
56 changes: 56 additions & 0 deletions mypyc/test-data/irbuild-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading