@@ -262,7 +262,8 @@ def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
262262 if not cl .builtin_base :
263263 fields ["tp_new" ] = new_name
264264
265- if generate_full :
265+ managed_dict = has_managed_dict (cl , emitter )
266+ if generate_full or managed_dict :
266267 fields ["tp_dealloc" ] = f"(destructor){ name_prefix } _dealloc"
267268 if not cl .is_acyclic :
268269 fields ["tp_traverse" ] = f"(traverseproc){ name_prefix } _traverse"
@@ -335,6 +336,14 @@ def emit_line() -> None:
335336 else :
336337 fields ["tp_basicsize" ] = base_size
337338
339+ if generate_full or managed_dict :
340+ if not cl .is_acyclic :
341+ generate_traverse_for_class (cl , traverse_name , emitter )
342+ emit_line ()
343+ generate_clear_for_class (cl , clear_name , emitter )
344+ emit_line ()
345+ generate_dealloc_for_class (cl , dealloc_name , clear_name , bool (del_method ), emitter )
346+ emit_line ()
338347 if generate_full :
339348 assert cl .setup is not None
340349 emitter .emit_line (native_function_header (cl .setup , emitter ) + ";" )
@@ -345,13 +354,6 @@ def emit_line() -> None:
345354 init_fn = cl .get_method ("__init__" )
346355 generate_new_for_class (cl , new_name , vtable_name , setup_name , init_fn , emitter )
347356 emit_line ()
348- if not cl .is_acyclic :
349- generate_traverse_for_class (cl , traverse_name , emitter )
350- emit_line ()
351- generate_clear_for_class (cl , clear_name , emitter )
352- emit_line ()
353- generate_dealloc_for_class (cl , dealloc_name , clear_name , bool (del_method ), emitter )
354- emit_line ()
355357
356358 if cl .allow_interpreted_subclasses :
357359 shadow_vtable_name : str | None = generate_vtables (
@@ -380,7 +382,7 @@ def emit_line() -> None:
380382 emit_line ()
381383
382384 flags = ["Py_TPFLAGS_DEFAULT" , "Py_TPFLAGS_HEAPTYPE" , "Py_TPFLAGS_BASETYPE" ]
383- if generate_full and not cl .is_acyclic :
385+ if ( generate_full or managed_dict ) and not cl .is_acyclic :
384386 flags .append ("Py_TPFLAGS_HAVE_GC" )
385387 if cl .has_method ("__call__" ):
386388 fields ["tp_vectorcall_offset" ] = "offsetof({}, vectorcall)" .format (
@@ -391,7 +393,7 @@ def emit_line() -> None:
391393 # This is just a placeholder to please CPython. It will be
392394 # overridden during setup.
393395 fields ["tp_call" ] = "PyVectorcall_Call"
394- if has_managed_dict ( cl , emitter ) :
396+ if managed_dict :
395397 flags .append ("Py_TPFLAGS_MANAGED_DICT" )
396398 fields ["tp_flags" ] = " | " .join (flags )
397399
@@ -869,7 +871,8 @@ def generate_traverse_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -
869871 for attr , rtype in base .attributes .items ():
870872 emitter .emit_gc_visit (f"self->{ emitter .attr (attr )} " , rtype )
871873 if has_managed_dict (cl , emitter ):
872- emitter .emit_line ("PyObject_VisitManagedDict((PyObject *)self, visit, arg);" )
874+ emitter .emit_line ("int rv = PyObject_VisitManagedDict((PyObject *)self, visit, arg);" )
875+ emitter .emit_line ("if (rv < 0) return rv;" )
873876 elif cl .has_dict :
874877 struct_name = cl .struct_name (emitter .names )
875878 # __dict__ lives right after the struct and __weakref__ lives right after that
@@ -934,6 +937,12 @@ def generate_dealloc_for_class(
934937 emitter .emit_line ("if (res < 0) {" )
935938 emitter .emit_line ("goto done;" )
936939 emitter .emit_line ("}" )
940+ if cl .builtin_base :
941+ # For native subclasses of builtins such as dict, the base deallocator
942+ # is responsible for tearing down base-owned storage and freeing memory.
943+ emitter .emit_line (f"{ clear_func_name } (self);" )
944+ emitter .emit_line ("Py_TYPE(self)->tp_base->tp_dealloc((PyObject *)self);" )
945+ emitter .emit_line ("goto done;" )
937946 if not cl .is_acyclic :
938947 emitter .emit_line ("PyObject_GC_UnTrack(self);" )
939948 if cl .reuse_freed_instance :
0 commit comments