diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index 7303f561d60c..0cf0edb32f86 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -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__": @@ -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): @@ -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__ 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__" + 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. diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 54e568a47768..c81425e98993 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -5774,3 +5774,45 @@ from native import Concrete c = Concrete() assert c.value() == 42 assert c.derived() == 42 + +[case testPropertyShadowVtableGlue] +# The setter's shadow glue overwrites the getter's (both keyed by fdef.name), +# misaligning subsequent vtable entries -> segfault on interpreted subclasses. +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"