83
83
dict_new_op ,
84
84
exact_dict_set_item_op ,
85
85
)
86
- from mypyc .primitives .generic_ops import generic_getattr , py_setattr_op
86
+ from mypyc .primitives .generic_ops import generic_getattr , generic_setattr , py_setattr_op
87
87
from mypyc .primitives .misc_ops import register_function
88
88
from mypyc .primitives .registry import builtin_names
89
89
from mypyc .sametype import is_same_method_signature , is_same_type
@@ -423,8 +423,10 @@ def generate_setattr_wrapper(builder: IRBuilder, cdef: ClassDef, setattr: FuncDe
423
423
Returns 0 on success and -1 on failure. Restrictions are similar to the __getattr__
424
424
wrapper above.
425
425
426
- This one is simpler because to match interpreted python semantics it's enough to always
427
- call the user-provided function, including for names matching regular attributes.
426
+ The wrapper calls the user-defined __setattr__ when the value to set is not NULL.
427
+ When it's NULL, this means that the call to tp_setattro comes from a del statement,
428
+ so it calls __delattr__ instead. If __delattr__ is not overridden in the native class,
429
+ this will call the base implementation in object which doesn't work without __dict__.
428
430
"""
429
431
name = setattr .name + "__wrapper"
430
432
ir = builder .mapper .type_to_ir [cdef .info ]
@@ -440,6 +442,27 @@ def generate_setattr_wrapper(builder: IRBuilder, cdef: ClassDef, setattr: FuncDe
440
442
attr_arg = builder .add_argument ("attr" , object_rprimitive )
441
443
value_arg = builder .add_argument ("value" , object_rprimitive )
442
444
445
+ call_delattr , call_setattr = BasicBlock (), BasicBlock ()
446
+ null = Integer (0 , object_rprimitive , line )
447
+ is_delattr = builder .add (ComparisonOp (value_arg , null , ComparisonOp .EQ , line ))
448
+ builder .add_bool_branch (is_delattr , call_delattr , call_setattr )
449
+
450
+ builder .activate_block (call_delattr )
451
+ delattr_symbol = cdef .info .get ("__delattr__" )
452
+ delattr = delattr_symbol .node if delattr_symbol else None
453
+ delattr_override = delattr is not None and not delattr .fullname .startswith ("builtins." )
454
+ if delattr_override :
455
+ builder .gen_method_call (builder .self (), "__delattr__" , [attr_arg ], None , line )
456
+ else :
457
+ # Call internal function that cpython normally calls when deleting an attribute.
458
+ # Cannot call object.__delattr__ here because it calls PyObject_SetAttr internally
459
+ # which in turn calls our wrapper and recurses infinitely.
460
+ # Note that since native classes don't have __dict__, this will raise AttributeError
461
+ # for dynamic attributes.
462
+ builder .call_c (generic_setattr , [builder .self (), attr_arg , null ], line )
463
+ builder .add (Return (Integer (0 , c_int_rprimitive ), line ))
464
+
465
+ builder .activate_block (call_setattr )
443
466
builder .gen_method_call (builder .self (), setattr .name , [attr_arg , value_arg ], None , line )
444
467
builder .add (Return (Integer (0 , c_int_rprimitive ), line ))
445
468
@@ -514,6 +537,14 @@ def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None
514
537
generate_getattr_wrapper (builder , cdef , fdef )
515
538
elif fdef .name == "__setattr__" :
516
539
generate_setattr_wrapper (builder , cdef , fdef )
540
+ elif fdef .name == "__delattr__" :
541
+ setattr = cdef .info .get ("__setattr__" )
542
+ if not setattr or not setattr .node or setattr .node .fullname .startswith ("builtins." ):
543
+ builder .error (
544
+ '"__delattr__" supported only in classes that also override "__setattr__", '
545
+ + "or inherit from a native class that overrides it." ,
546
+ fdef .line ,
547
+ )
517
548
518
549
519
550
def handle_non_ext_method (
0 commit comments