Skip to content
Merged
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
61 changes: 58 additions & 3 deletions mypyc/irbuild/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,11 @@ def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None
# children.
if class_ir.allow_interpreted_subclasses:
f = gen_glue(builder, func_ir.sig, func_ir, class_ir, class_ir, fdef, do_py_ops=True)
class_ir.glue_methods[(class_ir, name)] = f
# Use func_ir.decl.name (unique) rather than fdef.name, because for properties
# the getter and setter share the same fdef.name but have distinct decl names
# (e.g. "prop" vs "__mypyc_setter__prop"). Using fdef.name would cause the
# setter's glue to overwrite the getter's glue in the shadow vtable.
class_ir.glue_methods[(class_ir, func_ir.decl.name)] = f
builder.functions.append(f)

if fdef.name == "__getattr__":
Expand Down Expand Up @@ -653,8 +657,9 @@ def gen_glue(
"""
if fdef.is_property:
return gen_glue_property(builder, base_sig, target, cls, base, fdef.line, do_py_ops)
else:
return gen_glue_method(builder, base_sig, target, cls, base, fdef.line, do_py_ops)
if do_py_ops and target.name.startswith(PROPSET_PREFIX):
return gen_glue_property_setter(builder, base_sig, target, cls, base, fdef.line)
return gen_glue_method(builder, base_sig, target, cls, base, fdef.line, do_py_ops)


class ArgInfo(NamedTuple):
Expand Down Expand Up @@ -846,6 +851,56 @@ def gen_glue_property(
)


def gen_glue_property_setter(
builder: IRBuilder, sig: FuncSignature, target: FuncIR, cls: ClassIR, base: ClassIR, line: int
) -> FuncIR:
"""Generate a shadow glue method for a property setter.

For interpreted subclasses, property setters can't be called via the
internal __mypyc_setter__<name> method. Instead, use Python's setattr
to set the property via the standard descriptor protocol.
"""
builder.enter()
builder.ret_types[-1] = sig.ret_type

rt_args = list(sig.args)
rt_args[0] = RuntimeArg(sig.args[0].name, RInstance(cls))

arg_info = get_args(builder, rt_args, line)
args = arg_info.args

self_arg = args[0]
value_arg = args[1]

# Extract the property name from "__mypyc_setter__<name>"
assert target.name.startswith(PROPSET_PREFIX)
prop_name = target.name[len(PROPSET_PREFIX) :]

builder.primitive_op(
py_setattr_op,
[
self_arg,
builder.load_str(prop_name),
builder.coerce(value_arg, object_rprimitive, line),
],
line,
)
retval = builder.coerce(builder.none(), sig.ret_type, line)
builder.add(Return(retval))

arg_regs, _, blocks, return_type, _ = builder.leave()
return FuncIR(
FuncDecl(
target.name + "__" + base.name + "_glue",
cls.name,
builder.module_name,
FuncSignature(rt_args, return_type),
),
arg_regs,
blocks,
)


def get_func_target(builder: IRBuilder, fdef: FuncDef) -> AssignmentTarget:
"""Given a FuncDef, return the target for the instance of its callable class.

Expand Down
56 changes: 56 additions & 0 deletions mypyc/test-data/irbuild-glue-methods.test
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,59 @@ class EE(E):
main:7: error: An argument with type "i64" cannot be given a default value in a method override
main:13: error: Incompatible argument type "i64" (base class has type "int")
main:18: error: Incompatible argument type "int" (base class has type "i64")

[case testPropertySetterShadowGlue]
import mypy_extensions

@mypy_extensions.mypyc_attr(allow_interpreted_subclasses=True)
class Base:
_x: str = ""

@property
def x(self) -> str:
return self._x

@x.setter
def x(self, v: str) -> None:
self._x = v
[out]
def Base.x(self):
self :: __main__.Base
r0 :: str
L0:
r0 = self._x
return r0
def Base.x__Base_glue(__mypyc_self__):
__mypyc_self__ :: __main__.Base
r0 :: str
r1 :: object
r2 :: str
L0:
r0 = 'x'
r1 = CPyObject_GetAttr(__mypyc_self__, r0)
r2 = cast(str, r1)
return r2
def Base.__mypyc_setter__x(self, v):
self :: __main__.Base
v :: str
r0 :: bool
L0:
self._x = v; r0 = is_error
return 1
def Base.__mypyc_setter__x__Base_glue(self, v):
self :: __main__.Base
v, r0 :: str
r1 :: i32
r2 :: bit
L0:
r0 = 'x'
r1 = PyObject_SetAttr(self, r0, v)
r2 = r1 >= 0 :: signed
return 1
def Base.__mypyc_defaults_setup(__mypyc_self__):
__mypyc_self__ :: __main__.Base
r0 :: str
L0:
r0 = ''
__mypyc_self__._x = r0
return 1
40 changes: 40 additions & 0 deletions mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -5774,3 +5774,43 @@ from native import Concrete
c = Concrete()
assert c.value() == 42
assert c.derived() == 42

[case testPropertyShadowVtableGlue]
from typing import List
import mypy_extensions

@mypy_extensions.mypyc_attr(allow_interpreted_subclasses=True)
class Base:
_x: str
_y: List[int]
def __init__(self) -> None:
self._x = ""
self._y = []

@property
def x(self) -> str:
return self._x

@x.setter
def x(self, v: str) -> None:
self._x = v

@property
def y(self) -> List[int]:
return self._y

@y.setter
def y(self, v: List[int]) -> None:
self._y = v

def method(self) -> str:
self.x = "a"
self.y = [1]
return self.x + str(len(self.y))

[file driver.py]
from native import Base

Sub = type("Sub", (Base,), {})
s = Sub()
assert s.method() == "a1"
Loading