@@ -196,6 +196,7 @@ def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
196
196
197
197
setup_name = f"{ name_prefix } _setup"
198
198
new_name = f"{ name_prefix } _new"
199
+ finalize_name = f"{ name_prefix } _finalize"
199
200
members_name = f"{ name_prefix } _members"
200
201
getseters_name = f"{ name_prefix } _getseters"
201
202
vtable_name = f"{ name_prefix } _vtable"
@@ -217,6 +218,10 @@ def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
217
218
fields ["tp_dealloc" ] = f"(destructor){ name_prefix } _dealloc"
218
219
fields ["tp_traverse" ] = f"(traverseproc){ name_prefix } _traverse"
219
220
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 } "
220
225
if needs_getseters :
221
226
fields ["tp_getset" ] = getseters_name
222
227
fields ["tp_methods" ] = methods_name
@@ -297,8 +302,11 @@ def emit_line() -> None:
297
302
emit_line ()
298
303
generate_clear_for_class (cl , clear_name , emitter )
299
304
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 )
301
306
emit_line ()
307
+ if del_method :
308
+ generate_finalize_for_class (del_method , finalize_name , emitter )
309
+ emit_line ()
302
310
303
311
if cl .allow_interpreted_subclasses :
304
312
shadow_vtable_name : str | None = generate_vtables (
@@ -765,11 +773,19 @@ def generate_clear_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> N
765
773
766
774
767
775
def 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 ,
769
781
) -> None :
770
782
emitter .emit_line ("static void" )
771
783
emitter .emit_line (f"{ dealloc_func_name } ({ cl .struct_name (emitter .names )} *self)" )
772
784
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 ("}" )
773
789
emitter .emit_line ("PyObject_GC_UnTrack(self);" )
774
790
# The trashcan is needed to handle deep recursive deallocations
775
791
emitter .emit_line (f"CPy_TRASHCAN_BEGIN(self, { dealloc_func_name } )" )
@@ -779,6 +795,39 @@ def generate_dealloc_for_class(
779
795
emitter .emit_line ("}" )
780
796
781
797
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
+
782
831
def generate_methods_table (cl : ClassIR , name : str , emitter : Emitter ) -> None :
783
832
emitter .emit_line (f"static PyMethodDef { name } [] = {{" )
784
833
for fn in cl .methods .values ():
0 commit comments