@@ -196,6 +196,7 @@ def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
196196
197197 setup_name = f"{ name_prefix } _setup"
198198 new_name = f"{ name_prefix } _new"
199+ finalize_name = f"{ name_prefix } _finalize"
199200 members_name = f"{ name_prefix } _members"
200201 getseters_name = f"{ name_prefix } _getseters"
201202 vtable_name = f"{ name_prefix } _vtable"
@@ -217,6 +218,10 @@ def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
217218 fields ["tp_dealloc" ] = f"(destructor){ name_prefix } _dealloc"
218219 fields ["tp_traverse" ] = f"(traverseproc){ name_prefix } _traverse"
219220 fields ["tp_clear" ] = f"(inquiry){ name_prefix } _clear"
221+ # Populate .tp_finalize and generate a finalize method only if __del__ is defined for this class.
222+ del_method = next ((e .method for e in cl .vtable_entries if e .name == "__del__" ), None )
223+ if del_method :
224+ fields ["tp_finalize" ] = f"(destructor){ finalize_name } "
220225 if needs_getseters :
221226 fields ["tp_getset" ] = getseters_name
222227 fields ["tp_methods" ] = methods_name
@@ -297,8 +302,11 @@ def emit_line() -> None:
297302 emit_line ()
298303 generate_clear_for_class (cl , clear_name , emitter )
299304 emit_line ()
300- generate_dealloc_for_class (cl , dealloc_name , clear_name , emitter )
305+ generate_dealloc_for_class (cl , dealloc_name , clear_name , bool ( del_method ), emitter )
301306 emit_line ()
307+ if del_method :
308+ generate_finalize_for_class (del_method , finalize_name , emitter )
309+ emit_line ()
302310
303311 if cl .allow_interpreted_subclasses :
304312 shadow_vtable_name : str | None = generate_vtables (
@@ -765,11 +773,19 @@ def generate_clear_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> N
765773
766774
767775def generate_dealloc_for_class (
768- cl : ClassIR , dealloc_func_name : str , clear_func_name : str , emitter : Emitter
776+ cl : ClassIR ,
777+ dealloc_func_name : str ,
778+ clear_func_name : str ,
779+ has_tp_finalize : bool ,
780+ emitter : Emitter ,
769781) -> None :
770782 emitter .emit_line ("static void" )
771783 emitter .emit_line (f"{ dealloc_func_name } ({ cl .struct_name (emitter .names )} *self)" )
772784 emitter .emit_line ("{" )
785+ if has_tp_finalize :
786+ emitter .emit_line ("if (!PyObject_GC_IsFinalized((PyObject *)self)) {" )
787+ emitter .emit_line ("Py_TYPE(self)->tp_finalize((PyObject *)self);" )
788+ emitter .emit_line ("}" )
773789 emitter .emit_line ("PyObject_GC_UnTrack(self);" )
774790 # The trashcan is needed to handle deep recursive deallocations
775791 emitter .emit_line (f"CPy_TRASHCAN_BEGIN(self, { dealloc_func_name } )" )
@@ -779,6 +795,39 @@ def generate_dealloc_for_class(
779795 emitter .emit_line ("}" )
780796
781797
798+ def generate_finalize_for_class (
799+ del_method : FuncIR , finalize_func_name : str , emitter : Emitter
800+ ) -> None :
801+ emitter .emit_line ("static void" )
802+ emitter .emit_line (f"{ finalize_func_name } (PyObject *self)" )
803+ emitter .emit_line ("{" )
804+ emitter .emit_line ("PyObject *type, *value, *traceback;" )
805+ emitter .emit_line ("PyErr_Fetch(&type, &value, &traceback);" )
806+ emitter .emit_line (
807+ "{}{}{}(self);" .format (
808+ emitter .get_group_prefix (del_method .decl ),
809+ NATIVE_PREFIX ,
810+ del_method .cname (emitter .names ),
811+ )
812+ )
813+ emitter .emit_line ("if (PyErr_Occurred() != NULL) {" )
814+ emitter .emit_line ('PyObject *del_str = PyUnicode_FromString("__del__");' )
815+ emitter .emit_line (
816+ "PyObject *del_method = (del_str == NULL) ? NULL : _PyType_Lookup(Py_TYPE(self), del_str);"
817+ )
818+ # CPython interpreter uses PyErr_WriteUnraisable: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_WriteUnraisable
819+ # However, the message is slightly different due to the way mypyc compiles classes.
820+ # CPython interpreter prints: Exception ignored in: <function F.__del__ at 0x100aed940>
821+ # mypyc prints: Exception ignored in: <slot wrapper '__del__' of 'F' objects>
822+ emitter .emit_line ("PyErr_WriteUnraisable(del_method);" )
823+ emitter .emit_line ("Py_XDECREF(del_method);" )
824+ emitter .emit_line ("Py_XDECREF(del_str);" )
825+ emitter .emit_line ("}" )
826+ # PyErr_Restore also clears exception raised in __del__.
827+ emitter .emit_line ("PyErr_Restore(type, value, traceback);" )
828+ emitter .emit_line ("}" )
829+
830+
782831def generate_methods_table (cl : ClassIR , name : str , emitter : Emitter ) -> None :
783832 emitter .emit_line (f"static PyMethodDef { name } [] = {{" )
784833 for fn in cl .methods .values ():
0 commit comments