From 4ed9c5413563c004c725859a839e6a2e45cb7098 Mon Sep 17 00:00:00 2001 From: Piotr Sawicki Date: Thu, 25 Sep 2025 18:44:13 +0200 Subject: [PATCH 1/3] [mypyc] Generate __setattr__ wrapper --- mypyc/codegen/emitclass.py | 1 + mypyc/irbuild/builder.py | 12 +- mypyc/irbuild/expression.py | 7 + mypyc/irbuild/function.py | 31 ++ mypyc/irbuild/specialize.py | 54 ++- mypyc/lib-rt/CPy.h | 3 + mypyc/primitives/generic_ops.py | 7 + mypyc/test-data/fixtures/ir.py | 1 + mypyc/test-data/irbuild-classes.test | 271 ++++++++++++++ mypyc/test-data/run-classes.test | 516 +++++++++++++++++++++++++++ 10 files changed, 892 insertions(+), 11 deletions(-) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index 9e8f9c74bc6d..122f62a0d582 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -62,6 +62,7 @@ def dunder_attr_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str: "__hash__": ("tp_hash", generate_hash_wrapper), "__get__": ("tp_descr_get", generate_get_wrapper), "__getattr__": ("tp_getattro", dunder_attr_slot), + "__setattr__": ("tp_setattro", dunder_attr_slot), } AS_MAPPING_SLOT_DEFS: SlotTable = { diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index f4ee4371b9bf..d2f0c552126e 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -76,6 +76,7 @@ Integer, IntOp, LoadStatic, + MethodCall, Op, PrimitiveDescription, RaiseStandardError, @@ -735,8 +736,15 @@ def assign(self, target: Register | AssignmentTarget, rvalue_reg: Value, line: i self.add(Assign(target.register, rvalue_reg)) elif isinstance(target, AssignmentTargetAttr): if isinstance(target.obj_type, RInstance): - rvalue_reg = self.coerce_rvalue(rvalue_reg, target.type, line) - self.add(SetAttr(target.obj, target.attr, rvalue_reg, line)) + setattr = target.obj_type.class_ir.get_method("__setattr__") + if setattr: + key = self.load_str(target.attr) + boxed_reg = self.builder.box(rvalue_reg) + call = MethodCall(target.obj, setattr.name, [key, boxed_reg], line) + self.add(call) + else: + rvalue_reg = self.coerce_rvalue(rvalue_reg, target.type, line) + self.add(SetAttr(target.obj, target.attr, rvalue_reg, line)) else: key = self.load_str(target.attr) boxed_reg = self.builder.box(rvalue_reg) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 1f39b09c0995..e58a3bf41bf6 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -101,6 +101,7 @@ apply_function_specialization, apply_method_specialization, translate_object_new, + translate_object_setattr, ) from mypyc.primitives.bytes_ops import bytes_slice_op from mypyc.primitives.dict_ops import dict_get_item_op, dict_new_op, exact_dict_set_item_op @@ -485,6 +486,12 @@ def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: Supe # Call translates to object.__init__(self), which is a # no-op, so omit the call. return builder.none() + elif callee.name == "__setattr__": + result = translate_object_setattr( + builder, expr, MemberExpr(callee.call, "__setattr__") + ) + if result: + return result return translate_call(builder, expr, callee) decl = base.method_decl(callee.name) diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index a9a098d25dde..51bdc76495f2 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -57,6 +57,7 @@ from mypyc.ir.rtypes import ( RInstance, bool_rprimitive, + c_int_rprimitive, dict_rprimitive, int_rprimitive, object_rprimitive, @@ -415,6 +416,34 @@ def generate_getattr_wrapper(builder: IRBuilder, cdef: ClassDef, getattr: FuncDe builder.add(Return(getattr_result, line)) +def generate_setattr_wrapper(builder: IRBuilder, cdef: ClassDef, setattr: FuncDef) -> None: + """ + Generate a wrapper function for __setattr__ that can be put into the tp_setattro slot. + The wrapper takes two arguments besides self - attribute name and the new value. + Returns 0 on success and -1 on failure. Restrictions are similar to the __getattr__ + wrapper above. + + This one is simpler because to match interpreted python semantics it's enough to always + call the user-provided function, including for names matching regular attributes. + """ + name = setattr.name + "__wrapper" + ir = builder.mapper.type_to_ir[cdef.info] + line = setattr.line + + error_base = f'"__setattr__" not supported in class "{cdef.name}" because ' + if ir.allow_interpreted_subclasses: + builder.error(error_base + "it allows interpreted subclasses", line) + if ir.inherits_python: + builder.error(error_base + "it inherits from a non-native class", line) + + with builder.enter_method(ir, name, c_int_rprimitive, internal=True): + attr_arg = builder.add_argument("attr", object_rprimitive) + value_arg = builder.add_argument("value", object_rprimitive) + + builder.gen_method_call(builder.self(), setattr.name, [attr_arg, value_arg], None, line) + builder.add(Return(Integer(0, c_int_rprimitive), line)) + + def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None: # Perform the function of visit_method for methods inside extension classes. name = fdef.name @@ -483,6 +512,8 @@ def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None if fdef.name == "__getattr__": generate_getattr_wrapper(builder, cdef, fdef) + elif fdef.name == "__setattr__": + generate_setattr_wrapper(builder, cdef, fdef) def handle_non_ext_method( diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 29820787d10c..17d0081a444c 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -42,6 +42,7 @@ Integer, RaiseStandardError, Register, + SetAttr, Truncate, Unreachable, Value, @@ -97,6 +98,7 @@ isinstance_dict, ) from mypyc.primitives.float_ops import isinstance_float +from mypyc.primitives.generic_ops import generic_setattr from mypyc.primitives.int_ops import isinstance_int from mypyc.primitives.list_ops import isinstance_list, new_list_set_item_op from mypyc.primitives.misc_ops import isinstance_bool @@ -1007,19 +1009,28 @@ def translate_ord(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value return None -@specialize_function("__new__", object_rprimitive) -def translate_object_new(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: - fn = builder.fn_info - if fn.name != "__new__": - return None - - is_super_new = isinstance(expr.callee, SuperExpr) - is_object_new = ( +def is_object(callee: RefExpr) -> bool: + """ + Returns True for object. calls + """ + return ( isinstance(callee, MemberExpr) and isinstance(callee.expr, NameExpr) and callee.expr.fullname == "builtins.object" ) - if not (is_super_new or is_object_new): + + +def is_super_or_object(expr: CallExpr, callee: RefExpr) -> bool: + """ + Returns True for super(). or object. calls. + """ + return isinstance(expr.callee, SuperExpr) or is_object(callee) + + +@specialize_function("__new__", object_rprimitive) +def translate_object_new(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: + fn = builder.fn_info + if fn.name != "__new__" or not is_super_or_object(expr, callee): return None ir = builder.get_current_class_ir() @@ -1046,3 +1057,28 @@ def translate_object_new(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> return builder.add(Call(ir.setup, [subtype], expr.line)) return None + + +@specialize_function("__setattr__", object_rprimitive) +def translate_object_setattr(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: + is_super = isinstance(expr.callee, SuperExpr) + is_object_callee = is_object(callee) + if not ((is_super and len(expr.args) >= 2) or (is_object_callee and len(expr.args) >= 3)): + return None + + self_reg = builder.accept(expr.args[0]) if is_object_callee else builder.self() + ir = builder.get_current_class_ir() + # Need to offset by 1 for super().__setattr__ calls because there is no self arg in this case. + name_idx = 0 if is_super else 1 + value_idx = 1 if is_super else 2 + attr_name = expr.args[name_idx] + attr_value = expr.args[value_idx] + value = builder.accept(attr_value) + + if isinstance(attr_name, StrExpr) and ir and ir.has_attr(attr_name.value): + name = attr_name.value + value = builder.coerce(value, ir.attributes[name], expr.line) + return builder.add(SetAttr(self_reg, name, value, expr.line)) + + name_reg = builder.accept(attr_name) + return builder.call_c(generic_setattr, [self_reg, name_reg, value], expr.line) diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index b9cecb9280f3..e9dfd8de3683 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -952,6 +952,9 @@ void CPyTrace_LogEvent(const char *location, const char *line, const char *op, c static inline PyObject *CPyObject_GenericGetAttr(PyObject *self, PyObject *name) { return _PyObject_GenericGetAttrWithDict(self, name, NULL, 1); } +static inline int CPyObject_GenericSetAttr(PyObject *self, PyObject *name, PyObject *value) { + return _PyObject_GenericSetAttrWithDict(self, name, value, NULL); +} #if CPY_3_11_FEATURES PyObject *CPy_GetName(PyObject *obj); diff --git a/mypyc/primitives/generic_ops.py b/mypyc/primitives/generic_ops.py index ff978b7c8c3b..16bd074396d2 100644 --- a/mypyc/primitives/generic_ops.py +++ b/mypyc/primitives/generic_ops.py @@ -410,3 +410,10 @@ error_kind=ERR_NEVER, returns_null=True, ) + +generic_setattr = custom_op( + arg_types=[object_rprimitive, object_rprimitive, object_rprimitive], + return_type=c_int_rprimitive, + c_function_name="CPyObject_GenericSetAttr", + error_kind=ERR_NEG_INT, +) diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index a4b4f3ce2b1f..22a6a5986cbd 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -45,6 +45,7 @@ def __init__(self) -> None: pass def __eq__(self, x: object) -> bool: pass def __ne__(self, x: object) -> bool: pass def __str__(self) -> str: pass + def __setattr__(self, k: str, v: object) -> None: pass class type: def __init__(self, o: object) -> None: ... diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 76e28711c5e3..68b15a99c005 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -2213,3 +2213,274 @@ L0: buf_init_item r13, 4, r12 keep_alive r9 return r9 + +[case testUnsupportedSetAttr] +from mypy_extensions import mypyc_attr + +@mypyc_attr(allow_interpreted_subclasses=True) +class AllowsInterpreted: + def __setattr__(self, attr: str, val: object) -> None: # E: "__setattr__" not supported in class "AllowsInterpreted" because it allows interpreted subclasses + pass + +class InheritsInterpreted(dict): + def __setattr__(self, attr: str, val: object) -> None: # E: "__setattr__" not supported in class "InheritsInterpreted" because it inherits from a non-native class + pass + +@mypyc_attr(native_class=False) +class NonNative: + pass + +class InheritsNonNative(NonNative): + def __setattr__(self, attr: str, val: object) -> None: # E: "__setattr__" not supported in class "InheritsNonNative" because it inherits from a non-native class + pass + +[case testSetAttr] +from typing import ClassVar +class SetAttr: + _attributes: dict[str, object] + regular_attr: int + class_var: ClassVar[str] = "x" + + def __init__(self, regular_attr: int, extra_attrs: dict[str, object], new_attr: str, new_val: object) -> None: + super().__setattr__("_attributes", extra_attrs) + object.__setattr__(self, "regular_attr", regular_attr) + + super().__setattr__(new_attr, new_val) + object.__setattr__(self, new_attr, new_val) + + def __setattr__(self, key: str, val: object) -> None: + if key == "regular_attr": + super().__setattr__("regular_attr", val) + elif key == "class_var": + raise AttributeError() + else: + self._attributes[key] = val + +def test(attr: str, val: object) -> None: + i = SetAttr(99, {}, attr, val) + i.regular_attr = 100 + i.new_attr = 101 + + object.__setattr__(i, "regular_attr", 11) + object.__setattr__(i, attr, val) + +[typing fixtures/typing-full.pyi] +[out] +def SetAttr.__init__(self, regular_attr, extra_attrs, new_attr, new_val): + self :: __main__.SetAttr + regular_attr :: int + extra_attrs :: dict + new_attr :: str + new_val :: object + r0 :: i32 + r1 :: bit + r2 :: i32 + r3 :: bit +L0: + self._attributes = extra_attrs + self.regular_attr = regular_attr + r0 = CPyObject_GenericSetAttr(self, new_attr, new_val) + r1 = r0 >= 0 :: signed + r2 = CPyObject_GenericSetAttr(self, new_attr, new_val) + r3 = r2 >= 0 :: signed + return 1 +def SetAttr.__setattr__(self, key, val): + self :: __main__.SetAttr + key :: str + val :: object + r0 :: str + r1 :: bool + r2 :: int + r3 :: bool + r4 :: str + r5 :: bool + r6 :: object + r7 :: str + r8, r9 :: object + r10 :: dict + r11 :: i32 + r12 :: bit +L0: + r0 = 'regular_attr' + r1 = CPyStr_Equal(key, r0) + if r1 goto L1 else goto L2 :: bool +L1: + r2 = unbox(int, val) + self.regular_attr = r2; r3 = is_error + goto L6 +L2: + r4 = 'class_var' + r5 = CPyStr_Equal(key, r4) + if r5 goto L3 else goto L4 :: bool +L3: + r6 = builtins :: module + r7 = 'AttributeError' + r8 = CPyObject_GetAttr(r6, r7) + r9 = PyObject_Vectorcall(r8, 0, 0, 0) + CPy_Raise(r9) + unreachable +L4: + r10 = self._attributes + r11 = CPyDict_SetItem(r10, key, val) + r12 = r11 >= 0 :: signed +L5: +L6: + return 1 +def SetAttr.__setattr____wrapper(__mypyc_self__, attr, value): + __mypyc_self__ :: __main__.SetAttr + attr, value :: object + r0 :: str + r1 :: None +L0: + r0 = cast(str, attr) + r1 = __mypyc_self__.__setattr__(r0, value) + return 0 +def test(attr, val): + attr :: str + val :: object + r0 :: dict + r1, i :: __main__.SetAttr + r2 :: str + r3 :: object + r4 :: None + r5 :: str + r6 :: object + r7 :: i32 + r8 :: bit + r9 :: str + r10 :: object + r11 :: i32 + r12 :: bit + r13 :: i32 + r14 :: bit +L0: + r0 = PyDict_New() + r1 = SetAttr(198, r0, attr, val) + i = r1 + r2 = 'regular_attr' + r3 = object 100 + r4 = i.__setattr__(r2, r3) + r5 = 'new_attr' + r6 = object 101 + r7 = PyObject_SetAttr(i, r5, r6) + r8 = r7 >= 0 :: signed + r9 = 'regular_attr' + r10 = object 11 + r11 = CPyObject_GenericSetAttr(i, r9, r10) + r12 = r11 >= 0 :: signed + r13 = CPyObject_GenericSetAttr(i, attr, val) + r14 = r13 >= 0 :: signed + return 1 + +[case testUntransformedSetAttr_64bit] +class SetAttr: + def super_missing_args(self): + super().__setattr__() + super().__setattr__("attr") + + def object_missing_args(self): + object.__setattr__() + object.__setattr__(self) + object.__setattr__(self, "attr") + +[typing fixtures/typing-full.pyi] +[out] +def SetAttr.super_missing_args(self): + self :: __main__.SetAttr + r0 :: object + r1 :: str + r2, r3 :: object + r4 :: object[2] + r5 :: object_ptr + r6 :: object + r7 :: str + r8, r9, r10 :: object + r11 :: str + r12, r13 :: object + r14 :: object[2] + r15 :: object_ptr + r16 :: object + r17 :: str + r18 :: object + r19 :: str + r20 :: object[1] + r21 :: object_ptr + r22, r23 :: object +L0: + r0 = builtins :: module + r1 = 'super' + r2 = CPyObject_GetAttr(r0, r1) + r3 = __main__.SetAttr :: type + r4 = [r3, self] + r5 = load_address r4 + r6 = PyObject_Vectorcall(r2, r5, 2, 0) + keep_alive r3, self + r7 = '__setattr__' + r8 = CPyObject_GetAttr(r6, r7) + r9 = PyObject_Vectorcall(r8, 0, 0, 0) + r10 = builtins :: module + r11 = 'super' + r12 = CPyObject_GetAttr(r10, r11) + r13 = __main__.SetAttr :: type + r14 = [r13, self] + r15 = load_address r14 + r16 = PyObject_Vectorcall(r12, r15, 2, 0) + keep_alive r13, self + r17 = '__setattr__' + r18 = CPyObject_GetAttr(r16, r17) + r19 = 'attr' + r20 = [r19] + r21 = load_address r20 + r22 = PyObject_Vectorcall(r18, r21, 1, 0) + keep_alive r19 + r23 = box(None, 1) + return r23 +def SetAttr.object_missing_args(self): + self :: __main__.SetAttr + r0 :: object + r1 :: str + r2 :: object + r3 :: str + r4 :: object[1] + r5 :: object_ptr + r6, r7 :: object + r8 :: str + r9 :: object + r10 :: str + r11 :: object[2] + r12 :: object_ptr + r13, r14 :: object + r15 :: str + r16 :: object + r17, r18 :: str + r19 :: object[3] + r20 :: object_ptr + r21, r22 :: object +L0: + r0 = builtins :: module + r1 = 'object' + r2 = CPyObject_GetAttr(r0, r1) + r3 = '__setattr__' + r4 = [r2] + r5 = load_address r4 + r6 = PyObject_VectorcallMethod(r3, r5, 9223372036854775809, 0) + keep_alive r2 + r7 = builtins :: module + r8 = 'object' + r9 = CPyObject_GetAttr(r7, r8) + r10 = '__setattr__' + r11 = [r9, self] + r12 = load_address r11 + r13 = PyObject_VectorcallMethod(r10, r12, 9223372036854775810, 0) + keep_alive r9, self + r14 = builtins :: module + r15 = 'object' + r16 = CPyObject_GetAttr(r14, r15) + r17 = 'attr' + r18 = '__setattr__' + r19 = [r16, self, r17] + r20 = load_address r19 + r21 = PyObject_VectorcallMethod(r18, r20, 9223372036854775811, 0) + keep_alive r16, self, r17 + r22 = box(None, 1) + return r22 diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index b2f1a088585d..cbd8ff188643 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -4518,5 +4518,521 @@ test_getattr_inherited() test_getattr_overridden() test_getattr_nonnative() +[typing fixtures/typing-full.pyi] + +[case testDunderSetAttr] +from mypy_extensions import mypyc_attr +from testutil import assertRaises +from typing import ClassVar + +class SetAttr: + _attributes: dict[str, object] + regular_attr: int + class_var: ClassVar[str] = "x" + const: int = 42 + + def __init__(self, regular_attr: int, extra_attrs: dict[str, object]) -> None: + super().__setattr__("_attributes", extra_attrs) + super().__setattr__("regular_attr", regular_attr) + + def __setattr__(self, key: str, val: object) -> None: + if key == "regular_attr": + super().__setattr__("regular_attr", val) + elif key == "class_var" or key == "const": + raise AttributeError() + else: + self._attributes[key] = val + + def __getattr__(self, key: str) -> object: + return self._attributes.get(key) + +class SetAttrInherited(SetAttr): + def __init__(self, regular_attr: int, extra_attrs: dict[str, object]) -> None: + super().__init__(regular_attr, extra_attrs) + +class SetAttrOverridden(SetAttr): + sub_attr: int + subclass_var: ClassVar[str] = "y" + + def __init__(self, regular_attr: int, sub_attr: int, extra_attrs: dict[str, object]) -> None: + super().__init__(regular_attr, extra_attrs) + object.__setattr__(self, "sub_attr", sub_attr) + + def __setattr__(self, key: str, val: object) -> None: + if key == "sub_attr": + object.__setattr__(self, "sub_attr", val) + elif key == "subclass_var": + raise AttributeError() + else: + super().__setattr__(key, val) + +@mypyc_attr(native_class=False) +class SetAttrNonNative: + _attributes: dict[str, object] + regular_attr: int + class_var: ClassVar[str] = "x" + const: int = 42 + + def __init__(self, regular_attr: int, extra_attrs: dict[str, object]) -> None: + super().__setattr__("_attributes", extra_attrs) + super().__setattr__("regular_attr", regular_attr) + + def __setattr__(self, key: str, val: object) -> None: + if key == "regular_attr": + super().__setattr__("regular_attr", val) + elif key == "class_var" or key == "const": + raise AttributeError() + else: + self._attributes[key] = val + + def __getattr__(self, key: str) -> object: + return self._attributes.get(key) + +class NoSetAttr: + def __init__(self, attr: int) -> None: + self.attr = attr + + def object_setattr(self, attr: str, val: object) -> None: + object.__setattr__(self, attr, val) + + def super_setattr(self, attr: str, val: object) -> None: + super().__setattr__(attr, val) + +def test_setattr() -> None: + i = SetAttr(99, {"one": 1}) + assert i.class_var == "x" + assert i.regular_attr == 99 + assert i.one == 1 + assert i.two == None + assert i.const == 42 + + i.__setattr__("two", "2") + assert i.two == "2" + i.__setattr__("regular_attr", 101) + assert i.regular_attr == 101 + with assertRaises(AttributeError): + i.__setattr__("class_var", "y") + with assertRaises(AttributeError): + i.__setattr__("const", 43) + + setattr(i, "three", (3,3,3)) + assert i.three == (3,3,3) + setattr(i, "regular_attr", 102) + assert i.regular_attr == 102 + with assertRaises(AttributeError): + setattr(i, "class_var", "z") + with assertRaises(AttributeError): + setattr(i, "const", 44) + + i.four = [4,4] + assert i.four == [4,4] + i.regular_attr = 103 + assert i.regular_attr == 103 + with assertRaises(AttributeError): + i.const = 45 + +def test_setattr_inherited() -> None: + i = SetAttrInherited(99, {"one": 1}) + assert i.class_var == "x" + assert i.regular_attr == 99 + assert i.one == 1 + assert i.two == None + assert i.const == 42 + + i.__setattr__("two", "2") + assert i.two == "2" + i.__setattr__("regular_attr", 101) + assert i.regular_attr == 101 + with assertRaises(AttributeError): + i.__setattr__("class_var", "y") + with assertRaises(AttributeError): + i.__setattr__("const", 43) + + setattr(i, "three", (3,3,3)) + assert i.three == (3,3,3) + setattr(i, "regular_attr", 102) + assert i.regular_attr == 102 + with assertRaises(AttributeError): + setattr(i, "class_var", "z") + with assertRaises(AttributeError): + setattr(i, "const", 44) + + i.four = [4,4] + assert i.four == [4,4] + i.regular_attr = 103 + assert i.regular_attr == 103 + with assertRaises(AttributeError): + i.const = 45 + +def test_setattr_overridden() -> None: + i = SetAttrOverridden(99, 1, {"one": 1}) + assert i.class_var == "x" + assert i.subclass_var == "y" + assert i.regular_attr == 99 + assert i.sub_attr == 1 + assert i.one == 1 + assert i.two == None + assert i.const == 42 + + i.__setattr__("two", "2") + assert i.two == "2" + i.__setattr__("regular_attr", 101) + assert i.regular_attr == 101 + i.__setattr__("sub_attr", 2) + assert i.sub_attr == 2 + with assertRaises(AttributeError): + i.__setattr__("class_var", "y") + with assertRaises(AttributeError): + i.__setattr__("subclass_var", "a") + with assertRaises(AttributeError): + i.__setattr__("const", 43) + + setattr(i, "three", (3,3,3)) + assert i.three == (3,3,3) + setattr(i, "regular_attr", 102) + assert i.regular_attr == 102 + setattr(i, "sub_attr", 3) + assert i.sub_attr == 3 + with assertRaises(AttributeError): + setattr(i, "class_var", "z") + with assertRaises(AttributeError): + setattr(i, "subclass_var", "b") + with assertRaises(AttributeError): + setattr(i, "const", 44) + + i.four = [4,4] + assert i.four == [4,4] + i.regular_attr = 103 + assert i.regular_attr == 103 + i.sub_attr = 4 + assert i.sub_attr == 4 + with assertRaises(AttributeError): + i.const = 45 + + base_ref: SetAttr = i + setattr(base_ref, "sub_attr", 5) + assert base_ref.sub_attr == 5 + + base_ref.sub_attr = 6 + assert base_ref.sub_attr == 6 + + with assertRaises(AttributeError): + setattr(base_ref, "subclass_var", "c") + +def test_setattr_nonnative() -> None: + i = SetAttrNonNative(99, {"one": 1}) + assert i.class_var == "x" + assert i.regular_attr == 99 + assert i.one == 1 + assert i.two == None + assert i.const == 42 + + i.__setattr__("two", "2") + assert i.two == "2" + i.__setattr__("regular_attr", 101) + assert i.regular_attr == 101 + with assertRaises(AttributeError): + i.__setattr__("class_var", "y") + with assertRaises(AttributeError): + i.__setattr__("const", 43) + + setattr(i, "three", (3,3,3)) + assert i.three == (3,3,3) + setattr(i, "regular_attr", 102) + assert i.regular_attr == 102 + with assertRaises(AttributeError): + setattr(i, "class_var", "z") + with assertRaises(AttributeError): + setattr(i, "const", 44) + + i.four = [4,4] + assert i.four == [4,4] + i.regular_attr = 103 + assert i.regular_attr == 103 + with assertRaises(AttributeError): + i.const = 45 + +def test_no_setattr() -> None: + i = NoSetAttr(99) + i.super_setattr("attr", 100) + assert i.attr == 100 + + i.object_setattr("attr", 101) + assert i.attr == 101 + + object.__setattr__(i, "attr", 102) + assert i.attr == 102 + + with assertRaises(AttributeError): + i.super_setattr("not_attr", 100) + + with assertRaises(AttributeError): + i.object_setattr("not_attr", 101) + + with assertRaises(AttributeError): + object.__setattr__(i, "not_attr", 102) + +[typing fixtures/typing-full.pyi] + +[case testDunderSetAttrInterpreted] +from mypy_extensions import mypyc_attr +from typing import ClassVar + +class SetAttr: + _attributes: dict[str, object] + regular_attr: int + class_var: ClassVar[str] = "x" + const: int = 42 + + def __init__(self, regular_attr: int, extra_attrs: dict[str, object]) -> None: + super().__setattr__("_attributes", extra_attrs) + super().__setattr__("regular_attr", regular_attr) + + def __setattr__(self, key: str, val: object) -> None: + if key == "regular_attr": + super().__setattr__("regular_attr", val) + elif key == "class_var" or key == "const": + raise AttributeError() + else: + self._attributes[key] = val + + def __getattr__(self, key: str) -> object: + return self._attributes.get(key) + +class SetAttrInherited(SetAttr): + def __init__(self, regular_attr: int, extra_attrs: dict[str, object]) -> None: + super().__init__(regular_attr, extra_attrs) + +class SetAttrOverridden(SetAttr): + sub_attr: int + subclass_var: ClassVar[str] = "y" + + def __init__(self, regular_attr: int, sub_attr: int, extra_attrs: dict[str, object]) -> None: + super().__init__(regular_attr, extra_attrs) + object.__setattr__(self, "sub_attr", sub_attr) + + def __setattr__(self, key: str, val: object) -> None: + if key == "sub_attr": + object.__setattr__(self, "sub_attr", val) + elif key == "subclass_var": + raise AttributeError() + else: + super().__setattr__(key, val) + +@mypyc_attr(native_class=False) +class SetAttrNonNative: + _attributes: dict[str, object] + regular_attr: int + class_var: ClassVar[str] = "x" + const: int = 42 + + def __init__(self, regular_attr: int, extra_attrs: dict[str, object]) -> None: + super().__setattr__("_attributes", extra_attrs) + super().__setattr__("regular_attr", regular_attr) + + def __setattr__(self, key: str, val: object) -> None: + if key == "regular_attr": + super().__setattr__("regular_attr", val) + elif key == "class_var" or key == "const": + raise AttributeError() + else: + self._attributes[key] = val + + def __getattr__(self, key: str) -> object: + return self._attributes.get(key) + +class NoSetAttr: + def __init__(self, attr: int) -> None: + self.attr = attr + + def object_setattr(self, attr: str, val: object) -> None: + object.__setattr__(self, attr, val) + + def super_setattr(self, attr: str, val: object) -> None: + super().__setattr__(attr, val) + +[file driver.py] +from native import SetAttr, SetAttrInherited, SetAttrOverridden, SetAttrNonNative, NoSetAttr +from testutil import assertRaises + +def test_setattr() -> None: + i = SetAttr(99, {"one": 1}) + assert i.class_var == "x" + assert i.regular_attr == 99 + assert i.one == 1 + assert i.two == None + assert i.const == 42 + + i.__setattr__("two", "2") + assert i.two == "2" + i.__setattr__("regular_attr", 101) + assert i.regular_attr == 101 + with assertRaises(AttributeError): + i.__setattr__("class_var", "y") + with assertRaises(AttributeError): + i.__setattr__("const", 43) + + setattr(i, "three", (3,3,3)) + assert i.three == (3,3,3) + setattr(i, "regular_attr", 102) + assert i.regular_attr == 102 + with assertRaises(AttributeError): + setattr(i, "class_var", "z") + with assertRaises(AttributeError): + setattr(i, "const", 44) + + i.four = [4,4] + assert i.four == [4,4] + i.regular_attr = 103 + assert i.regular_attr == 103 + with assertRaises(AttributeError): + i.const = 45 + +def test_setattr_inherited() -> None: + i = SetAttrInherited(99, {"one": 1}) + assert i.class_var == "x" + assert i.regular_attr == 99 + assert i.one == 1 + assert i.two == None + assert i.const == 42 + + i.__setattr__("two", "2") + assert i.two == "2" + i.__setattr__("regular_attr", 101) + assert i.regular_attr == 101 + with assertRaises(AttributeError): + i.__setattr__("class_var", "y") + with assertRaises(AttributeError): + i.__setattr__("const", 43) + + setattr(i, "three", (3,3,3)) + assert i.three == (3,3,3) + setattr(i, "regular_attr", 102) + assert i.regular_attr == 102 + with assertRaises(AttributeError): + setattr(i, "class_var", "z") + with assertRaises(AttributeError): + setattr(i, "const", 44) + + i.four = [4,4] + assert i.four == [4,4] + i.regular_attr = 103 + assert i.regular_attr == 103 + with assertRaises(AttributeError): + i.const = 45 + +def test_setattr_overridden() -> None: + i = SetAttrOverridden(99, 1, {"one": 1}) + assert i.class_var == "x" + assert i.subclass_var == "y" + assert i.regular_attr == 99 + assert i.sub_attr == 1 + assert i.one == 1 + assert i.two == None + assert i.const == 42 + + i.__setattr__("two", "2") + assert i.two == "2" + i.__setattr__("regular_attr", 101) + assert i.regular_attr == 101 + i.__setattr__("sub_attr", 2) + assert i.sub_attr == 2 + with assertRaises(AttributeError): + i.__setattr__("class_var", "y") + with assertRaises(AttributeError): + i.__setattr__("subclass_var", "a") + with assertRaises(AttributeError): + i.__setattr__("const", 43) + + setattr(i, "three", (3,3,3)) + assert i.three == (3,3,3) + setattr(i, "regular_attr", 102) + assert i.regular_attr == 102 + setattr(i, "sub_attr", 3) + assert i.sub_attr == 3 + with assertRaises(AttributeError): + setattr(i, "class_var", "z") + with assertRaises(AttributeError): + setattr(i, "subclass_var", "b") + with assertRaises(AttributeError): + setattr(i, "const", 44) + + i.four = [4,4] + assert i.four == [4,4] + i.regular_attr = 103 + assert i.regular_attr == 103 + i.sub_attr = 4 + assert i.sub_attr == 4 + with assertRaises(AttributeError): + i.const = 45 + + base_ref: SetAttr = i + setattr(base_ref, "sub_attr", 5) + assert base_ref.sub_attr == 5 + + base_ref.sub_attr = 6 + assert base_ref.sub_attr == 6 + + with assertRaises(AttributeError): + setattr(base_ref, "subclass_var", "c") + +def test_setattr_nonnative() -> None: + i = SetAttrNonNative(99, {"one": 1}) + assert i.class_var == "x" + assert i.regular_attr == 99 + assert i.one == 1 + assert i.two == None + assert i.const == 42 + + i.__setattr__("two", "2") + assert i.two == "2" + i.__setattr__("regular_attr", 101) + assert i.regular_attr == 101 + with assertRaises(AttributeError): + i.__setattr__("class_var", "y") + with assertRaises(AttributeError): + i.__setattr__("const", 43) + + setattr(i, "three", (3,3,3)) + assert i.three == (3,3,3) + setattr(i, "regular_attr", 102) + assert i.regular_attr == 102 + with assertRaises(AttributeError): + setattr(i, "class_var", "z") + with assertRaises(AttributeError): + setattr(i, "const", 44) + + i.four = [4,4] + assert i.four == [4,4] + i.regular_attr = 103 + assert i.regular_attr == 103 + with assertRaises(AttributeError): + i.const = 45 + +def test_no_setattr() -> None: + i = NoSetAttr(99) + i.super_setattr("attr", 100) + assert i.attr == 100 + + i.object_setattr("attr", 101) + assert i.attr == 101 + + object.__setattr__(i, "attr", 102) + assert i.attr == 102 + + with assertRaises(AttributeError): + i.super_setattr("not_attr", 100) + + with assertRaises(AttributeError): + i.object_setattr("not_attr", 101) + + with assertRaises(AttributeError): + object.__setattr__(i, "not_attr", 102) + +test_setattr() +test_setattr_inherited() +test_setattr_overridden() +test_setattr_nonnative() +test_no_setattr() [typing fixtures/typing-full.pyi] From 914a77e3a8595910328c56193ccb335eefec9f53 Mon Sep 17 00:00:00 2001 From: Piotr Sawicki Date: Mon, 29 Sep 2025 15:09:44 +0200 Subject: [PATCH 2/3] Address review comments --- mypyc/irbuild/expression.py | 12 ++--- mypyc/irbuild/specialize.py | 8 +-- mypyc/test-data/irbuild-classes.test | 74 ++++++++++++++++++++++++++++ mypyc/test-data/run-classes.test | 71 +++++++++++++++++++++++++- 4 files changed, 152 insertions(+), 13 deletions(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index e58a3bf41bf6..acd18e6f951d 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -481,17 +481,17 @@ def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: Supe result = translate_object_new(builder, expr, MemberExpr(callee.call, "__new__")) if result: return result + elif callee.name == "__setattr__" and ir.builtin_base is None and not ir.inherits_python: + result = translate_object_setattr( + builder, expr, MemberExpr(callee.call, "__setattr__") + ) + if result: + return result if ir.is_ext_class and ir.builtin_base is None and not ir.inherits_python: if callee.name == "__init__" and len(expr.args) == 0: # Call translates to object.__init__(self), which is a # no-op, so omit the call. return builder.none() - elif callee.name == "__setattr__": - result = translate_object_setattr( - builder, expr, MemberExpr(callee.call, "__setattr__") - ) - if result: - return result return translate_call(builder, expr, callee) decl = base.method_decl(callee.name) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 17d0081a444c..65b4c3d6b390 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -1010,9 +1010,7 @@ def translate_ord(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value def is_object(callee: RefExpr) -> bool: - """ - Returns True for object. calls - """ + """Returns True for object. calls.""" return ( isinstance(callee, MemberExpr) and isinstance(callee.expr, NameExpr) @@ -1021,9 +1019,7 @@ def is_object(callee: RefExpr) -> bool: def is_super_or_object(expr: CallExpr, callee: RefExpr) -> bool: - """ - Returns True for super(). or object. calls. - """ + """Returns True for super(). or object. calls.""" return isinstance(expr.callee, SuperExpr) or is_object(callee) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 68b15a99c005..565ffc3e90ce 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -2373,6 +2373,8 @@ L0: return 1 [case testUntransformedSetAttr_64bit] +from mypy_extensions import mypyc_attr + class SetAttr: def super_missing_args(self): super().__setattr__() @@ -2383,6 +2385,18 @@ class SetAttr: object.__setattr__(self) object.__setattr__(self, "attr") +@mypyc_attr(native_class=False) +class NonNative: + pass + +class InheritsPython(NonNative): + def super_setattr(self, key: str, val: object) -> None: + super().__setattr__(key, val) + +class BuiltInBase(dict): + def super_setattr(self, key: str, val: object) -> None: + super().__setattr__(key, val) + [typing fixtures/typing-full.pyi] [out] def SetAttr.super_missing_args(self): @@ -2484,3 +2498,63 @@ L0: keep_alive r16, self, r17 r22 = box(None, 1) return r22 +def InheritsPython.super_setattr(self, key, val): + self :: __main__.InheritsPython + key :: str + val, r0 :: object + r1 :: str + r2, r3 :: object + r4 :: object[2] + r5 :: object_ptr + r6 :: object + r7 :: str + r8 :: object + r9 :: object[2] + r10 :: object_ptr + r11 :: object +L0: + r0 = builtins :: module + r1 = 'super' + r2 = CPyObject_GetAttr(r0, r1) + r3 = __main__.InheritsPython :: type + r4 = [r3, self] + r5 = load_address r4 + r6 = PyObject_Vectorcall(r2, r5, 2, 0) + keep_alive r3, self + r7 = '__setattr__' + r8 = CPyObject_GetAttr(r6, r7) + r9 = [key, val] + r10 = load_address r9 + r11 = PyObject_Vectorcall(r8, r10, 2, 0) + keep_alive key, val + return 1 +def BuiltInBase.super_setattr(self, key, val): + self :: dict + key :: str + val, r0 :: object + r1 :: str + r2, r3 :: object + r4 :: object[2] + r5 :: object_ptr + r6 :: object + r7 :: str + r8 :: object + r9 :: object[2] + r10 :: object_ptr + r11 :: object +L0: + r0 = builtins :: module + r1 = 'super' + r2 = CPyObject_GetAttr(r0, r1) + r3 = __main__.BuiltInBase :: type + r4 = [r3, self] + r5 = load_address r4 + r6 = PyObject_Vectorcall(r2, r5, 2, 0) + keep_alive r3, self + r7 = '__setattr__' + r8 = CPyObject_GetAttr(r6, r7) + r9 = [key, val] + r10 = load_address r9 + r11 = PyObject_Vectorcall(r8, r10, 2, 0) + keep_alive key, val + return 1 diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index cbd8ff188643..d10f7b19067c 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -4598,6 +4598,20 @@ class NoSetAttr: def super_setattr(self, attr: str, val: object) -> None: super().__setattr__(attr, val) +@mypyc_attr(native_class=False) +class NoSetAttrNonNative: + def __init__(self, attr: int) -> None: + self.attr = attr + + def object_setattr(self, attr: str, val: object) -> None: + object.__setattr__(self, attr, val) + + def super_setattr(self, attr: str, val: object) -> None: + super().__setattr__(attr, val) + + def __getattr__(self, attr: str) -> object: + pass + def test_setattr() -> None: i = SetAttr(99, {"one": 1}) assert i.class_var == "x" @@ -4772,6 +4786,26 @@ def test_no_setattr() -> None: with assertRaises(AttributeError): object.__setattr__(i, "not_attr", 102) +def test_no_setattr_nonnative() -> None: + i = NoSetAttrNonNative(99) + i.super_setattr("attr", 100) + assert i.attr == 100 + + i.object_setattr("attr", 101) + assert i.attr == 101 + + object.__setattr__(i, "attr", 102) + assert i.attr == 102 + + i.super_setattr("one", 100) + assert i.one == 100 + + i.object_setattr("two", 101) + assert i.two == 101 + + object.__setattr__(i, "three", 102) + assert i.three == 102 + [typing fixtures/typing-full.pyi] [case testDunderSetAttrInterpreted] @@ -4851,8 +4885,22 @@ class NoSetAttr: def super_setattr(self, attr: str, val: object) -> None: super().__setattr__(attr, val) +@mypyc_attr(native_class=False) +class NoSetAttrNonNative: + def __init__(self, attr: int) -> None: + self.attr = attr + + def object_setattr(self, attr: str, val: object) -> None: + object.__setattr__(self, attr, val) + + def super_setattr(self, attr: str, val: object) -> None: + super().__setattr__(attr, val) + + def __getattr__(self, attr: str) -> object: + pass + [file driver.py] -from native import SetAttr, SetAttrInherited, SetAttrOverridden, SetAttrNonNative, NoSetAttr +from native import SetAttr, SetAttrInherited, SetAttrOverridden, SetAttrNonNative, NoSetAttr, NoSetAttrNonNative from testutil import assertRaises def test_setattr() -> None: @@ -5029,10 +5077,31 @@ def test_no_setattr() -> None: with assertRaises(AttributeError): object.__setattr__(i, "not_attr", 102) +def test_no_setattr_nonnative() -> None: + i = NoSetAttrNonNative(99) + i.super_setattr("attr", 100) + assert i.attr == 100 + + i.object_setattr("attr", 101) + assert i.attr == 101 + + object.__setattr__(i, "attr", 102) + assert i.attr == 102 + + i.super_setattr("one", 100) + assert i.one == 100 + + i.object_setattr("two", 101) + assert i.two == 101 + + object.__setattr__(i, "three", 102) + assert i.three == 102 + test_setattr() test_setattr_inherited() test_setattr_overridden() test_setattr_nonnative() test_no_setattr() +test_no_setattr_nonnative() [typing fixtures/typing-full.pyi] From 1c2ae1a557ad2aeca8a6df2e96cb6e0f6a9fcab3 Mon Sep 17 00:00:00 2001 From: Piotr Sawicki Date: Mon, 29 Sep 2025 15:52:28 +0200 Subject: [PATCH 3/3] Disable translation for non-native classes --- mypyc/irbuild/expression.py | 2 +- mypyc/irbuild/specialize.py | 2 + mypyc/test-data/irbuild-classes.test | 130 ++++++++++++++++++++++++++- 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index acd18e6f951d..54a101bc4961 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -481,7 +481,7 @@ def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: Supe result = translate_object_new(builder, expr, MemberExpr(callee.call, "__new__")) if result: return result - elif callee.name == "__setattr__" and ir.builtin_base is None and not ir.inherits_python: + elif callee.name == "__setattr__": result = translate_object_setattr( builder, expr, MemberExpr(callee.call, "__setattr__") ) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 65b4c3d6b390..42b7710c98da 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -1064,6 +1064,8 @@ def translate_object_setattr(builder: IRBuilder, expr: CallExpr, callee: RefExpr self_reg = builder.accept(expr.args[0]) if is_object_callee else builder.self() ir = builder.get_current_class_ir() + if ir and (not ir.is_ext_class or ir.builtin_base or ir.inherits_python): + return None # Need to offset by 1 for super().__setattr__ calls because there is no self arg in this case. name_idx = 0 if is_super else 1 value_idx = 1 if is_super else 2 diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 565ffc3e90ce..a98b3a7d3dcf 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -2387,16 +2387,26 @@ class SetAttr: @mypyc_attr(native_class=False) class NonNative: - pass + def super_setattr(self, key: str, val: object) -> None: + super().__setattr__(key, val) + + def object_setattr(self, key: str, val: object) -> None: + object.__setattr__(self, key, val) class InheritsPython(NonNative): def super_setattr(self, key: str, val: object) -> None: super().__setattr__(key, val) + def object_setattr(self, key: str, val: object) -> None: + object.__setattr__(self, key, val) + class BuiltInBase(dict): def super_setattr(self, key: str, val: object) -> None: super().__setattr__(key, val) + def object_setattr(self, key: str, val: object) -> None: + object.__setattr__(self, key, val) + [typing fixtures/typing-full.pyi] [out] def SetAttr.super_missing_args(self): @@ -2498,6 +2508,84 @@ L0: keep_alive r16, self, r17 r22 = box(None, 1) return r22 +def super_setattr_NonNative_obj.__get__(__mypyc_self__, instance, owner): + __mypyc_self__, instance, owner, r0 :: object + r1 :: bit + r2 :: object +L0: + r0 = load_address _Py_NoneStruct + r1 = instance == r0 + if r1 goto L1 else goto L2 :: bool +L1: + return __mypyc_self__ +L2: + r2 = PyMethod_New(__mypyc_self__, instance) + return r2 +def super_setattr_NonNative_obj.__call__(__mypyc_self__, self, key, val): + __mypyc_self__ :: __main__.super_setattr_NonNative_obj + self :: __main__.NonNative + key :: str + val, r0 :: object + r1 :: str + r2, r3 :: object + r4 :: object[2] + r5 :: object_ptr + r6 :: object + r7 :: str + r8 :: object + r9 :: object[2] + r10 :: object_ptr + r11 :: object +L0: + r0 = builtins :: module + r1 = 'super' + r2 = CPyObject_GetAttr(r0, r1) + r3 = __main__.NonNative :: type + r4 = [r3, self] + r5 = load_address r4 + r6 = PyObject_Vectorcall(r2, r5, 2, 0) + keep_alive r3, self + r7 = '__setattr__' + r8 = CPyObject_GetAttr(r6, r7) + r9 = [key, val] + r10 = load_address r9 + r11 = PyObject_Vectorcall(r8, r10, 2, 0) + keep_alive key, val + return 1 +def object_setattr_NonNative_obj.__get__(__mypyc_self__, instance, owner): + __mypyc_self__, instance, owner, r0 :: object + r1 :: bit + r2 :: object +L0: + r0 = load_address _Py_NoneStruct + r1 = instance == r0 + if r1 goto L1 else goto L2 :: bool +L1: + return __mypyc_self__ +L2: + r2 = PyMethod_New(__mypyc_self__, instance) + return r2 +def object_setattr_NonNative_obj.__call__(__mypyc_self__, self, key, val): + __mypyc_self__ :: __main__.object_setattr_NonNative_obj + self :: __main__.NonNative + key :: str + val, r0 :: object + r1 :: str + r2 :: object + r3 :: str + r4 :: object[4] + r5 :: object_ptr + r6 :: object +L0: + r0 = builtins :: module + r1 = 'object' + r2 = CPyObject_GetAttr(r0, r1) + r3 = '__setattr__' + r4 = [r2, self, key, val] + r5 = load_address r4 + r6 = PyObject_VectorcallMethod(r3, r5, 9223372036854775812, 0) + keep_alive r2, self, key, val + return 1 def InheritsPython.super_setattr(self, key, val): self :: __main__.InheritsPython key :: str @@ -2528,6 +2616,26 @@ L0: r11 = PyObject_Vectorcall(r8, r10, 2, 0) keep_alive key, val return 1 +def InheritsPython.object_setattr(self, key, val): + self :: __main__.InheritsPython + key :: str + val, r0 :: object + r1 :: str + r2 :: object + r3 :: str + r4 :: object[4] + r5 :: object_ptr + r6 :: object +L0: + r0 = builtins :: module + r1 = 'object' + r2 = CPyObject_GetAttr(r0, r1) + r3 = '__setattr__' + r4 = [r2, self, key, val] + r5 = load_address r4 + r6 = PyObject_VectorcallMethod(r3, r5, 9223372036854775812, 0) + keep_alive r2, self, key, val + return 1 def BuiltInBase.super_setattr(self, key, val): self :: dict key :: str @@ -2558,3 +2666,23 @@ L0: r11 = PyObject_Vectorcall(r8, r10, 2, 0) keep_alive key, val return 1 +def BuiltInBase.object_setattr(self, key, val): + self :: dict + key :: str + val, r0 :: object + r1 :: str + r2 :: object + r3 :: str + r4 :: object[4] + r5 :: object_ptr + r6 :: object +L0: + r0 = builtins :: module + r1 = 'object' + r2 = CPyObject_GetAttr(r0, r1) + r3 = '__setattr__' + r4 = [r2, self, key, val] + r5 = load_address r4 + r6 = PyObject_VectorcallMethod(r3, r5, 9223372036854775812, 0) + keep_alive r2, self, key, val + return 1