@@ -429,7 +429,7 @@ def generate_getattr_wrapper(builder: IRBuilder, cdef: ClassDef, getattr: FuncDe
429429 builder .add (Return (getattr_result , line ))
430430
431431
432- def generate_setattr_wrapper (builder : IRBuilder , cdef : ClassDef , setattr : FuncDef ) -> None :
432+ def generate_setattr_wrapper (builder : IRBuilder , cdef : ClassDef , fdef : FuncDef ) -> None :
433433 """
434434 Generate a wrapper function for __setattr__ that can be put into the tp_setattro slot.
435435 The wrapper takes two arguments besides self - attribute name and the new value.
@@ -440,16 +440,21 @@ def generate_setattr_wrapper(builder: IRBuilder, cdef: ClassDef, setattr: FuncDe
440440 When it's NULL, this means that the call to tp_setattro comes from a del statement,
441441 so it calls __delattr__ instead. If __delattr__ is not overridden in the native class,
442442 this will call the base implementation in object which doesn't work without __dict__.
443+
444+ The wrapper is generated for a class that defines __setattr__, and for a class that
445+ defines only __delattr__ and inherits __setattr__ from a native class. The argument
446+ fdef is the definition of __setattr__ in the first case and of __delattr__ in the second.
443447 """
444- name = setattr . name + "__wrapper "
448+ name = "__setattr____wrapper "
445449 ir = builder .mapper .type_to_ir [cdef .info ]
446- line = setattr .line
450+ line = fdef .line
447451
448- error_base = f'"__setattr__" not supported in class "{ cdef .name } " because '
449- if ir .allow_interpreted_subclasses :
450- builder .error (error_base + "it allows interpreted subclasses" , line )
451- if ir .inherits_python :
452- builder .error (error_base + "it inherits from a non-native class" , line )
452+ if fdef .name == "__setattr__" :
453+ error_base = f'"__setattr__" not supported in class "{ cdef .name } " because '
454+ if ir .allow_interpreted_subclasses :
455+ builder .error (error_base + "it allows interpreted subclasses" , line )
456+ if ir .inherits_python :
457+ builder .error (error_base + "it inherits from a non-native class" , line )
453458
454459 with builder .enter_method (ir , name , c_int_rprimitive , internal = True ):
455460 attr_arg = builder .add_argument ("attr" , object_rprimitive )
@@ -476,7 +481,7 @@ def generate_setattr_wrapper(builder: IRBuilder, cdef: ClassDef, setattr: FuncDe
476481 builder .add (Return (Integer (0 , c_int_rprimitive ), line ))
477482
478483 builder .activate_block (call_setattr )
479- builder .gen_method_call (builder .self (), setattr . name , [attr_arg , value_arg ], None , line )
484+ builder .gen_method_call (builder .self (), "__setattr__" , [attr_arg , value_arg ], None , line )
480485 builder .add (Return (Integer (0 , c_int_rprimitive ), line ))
481486
482487
@@ -578,6 +583,15 @@ def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None
578583 + "or inherit from a native class that overrides it." ,
579584 fdef .line ,
580585 )
586+ elif "__setattr__" not in class_ir .method_decls :
587+ # The class inherits __setattr__. If that comes from a native class, the class
588+ # would also inherit its tp_setattro wrapper, which was generated without this
589+ # __delattr__ and may not call it. So generate a wrapper for this class as well.
590+ setattr_cls = next (
591+ (base for base in class_ir .mro if "__setattr__" in base .method_decls ), None
592+ )
593+ if setattr_cls is not None and setattr_cls .is_ext_class :
594+ generate_setattr_wrapper (builder , cdef , fdef )
581595
582596
583597def handle_non_ext_method (
0 commit comments