@@ -284,8 +284,9 @@ def emit_line() -> None:
284284
285285 fields ["tp_members" ] = members_name
286286 fields ["tp_basicsize" ] = f"{ base_size } + 2*sizeof(PyObject *)"
287- fields ["tp_dictoffset" ] = base_size
288- fields ["tp_weaklistoffset" ] = weak_offset
287+ if emitter .capi_version < (3 , 12 ):
288+ fields ["tp_dictoffset" ] = base_size
289+ fields ["tp_weaklistoffset" ] = weak_offset
289290 else :
290291 fields ["tp_basicsize" ] = base_size
291292
@@ -341,6 +342,8 @@ def emit_line() -> None:
341342 # This is just a placeholder to please CPython. It will be
342343 # overridden during setup.
343344 fields ["tp_call" ] = "PyVectorcall_Call"
345+ if has_managed_dict (cl , emitter ):
346+ flags .append ("Py_TPFLAGS_MANAGED_DICT" )
344347 fields ["tp_flags" ] = " | " .join (flags )
345348
346349 emitter .emit_line (f"static PyTypeObject { emitter .type_struct_name (cl )} _template_ = {{" )
@@ -730,7 +733,9 @@ def generate_traverse_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -
730733 for base in reversed (cl .base_mro ):
731734 for attr , rtype in base .attributes .items ():
732735 emitter .emit_gc_visit (f"self->{ emitter .attr (attr )} " , rtype )
733- if cl .has_dict :
736+ if has_managed_dict (cl , emitter ):
737+ emitter .emit_line ("_PyObject_VisitManagedDict((PyObject *)self, visit, arg);" )
738+ elif cl .has_dict :
734739 struct_name = cl .struct_name (emitter .names )
735740 # __dict__ lives right after the struct and __weakref__ lives right after that
736741 emitter .emit_gc_visit (
@@ -751,7 +756,9 @@ def generate_clear_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> N
751756 for base in reversed (cl .base_mro ):
752757 for attr , rtype in base .attributes .items ():
753758 emitter .emit_gc_clear (f"self->{ emitter .attr (attr )} " , rtype )
754- if cl .has_dict :
759+ if has_managed_dict (cl , emitter ):
760+ emitter .emit_line ("_PyObject_ClearManagedDict((PyObject *)self);" )
761+ elif cl .has_dict :
755762 struct_name = cl .struct_name (emitter .names )
756763 # __dict__ lives right after the struct and __weakref__ lives right after that
757764 emitter .emit_gc_clear (
@@ -1040,3 +1047,15 @@ def generate_property_setter(
10401047 )
10411048 emitter .emit_line ("return 0;" )
10421049 emitter .emit_line ("}" )
1050+
1051+
1052+ def has_managed_dict (cl : ClassIR , emitter : Emitter ) -> bool :
1053+ """Should the class get the Py_TPFLAGS_MANAGED_DICT flag?"""
1054+ # On 3.11 and earlier the flag doesn't exist and we use
1055+ # tp_dictoffset instead. If a class inherits from Exception, the
1056+ # flag conflicts with tp_dictoffset set in the base class.
1057+ return (
1058+ emitter .capi_version >= (3 , 12 )
1059+ and cl .has_dict
1060+ and cl .builtin_base != "PyBaseExceptionObject"
1061+ )
0 commit comments