Skip to content

Commit c2dc3ff

Browse files
authored
[mypyc] Fix classes with __dict__ on 3.12 (#15471)
Got this working with a little trial and error. Work on mypyc/mypyc#995.
1 parent c239369 commit c2dc3ff

File tree

1 file changed

+23
-4
lines changed

1 file changed

+23
-4
lines changed

mypyc/codegen/emitclass.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)