Skip to content

Commit

Permalink
[mypyc] Fix classes with __dict__ on 3.12 (#15471)
Browse files Browse the repository at this point in the history
Got this working with a little trial and error.

Work on mypyc/mypyc#995.
  • Loading branch information
JukkaL authored Jun 23, 2023
1 parent c239369 commit c2dc3ff
Showing 1 changed file with 23 additions and 4 deletions.
27 changes: 23 additions & 4 deletions mypyc/codegen/emitclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,9 @@ def emit_line() -> None:

fields["tp_members"] = members_name
fields["tp_basicsize"] = f"{base_size} + 2*sizeof(PyObject *)"
fields["tp_dictoffset"] = base_size
fields["tp_weaklistoffset"] = weak_offset
if emitter.capi_version < (3, 12):
fields["tp_dictoffset"] = base_size
fields["tp_weaklistoffset"] = weak_offset
else:
fields["tp_basicsize"] = base_size

Expand Down Expand Up @@ -341,6 +342,8 @@ def emit_line() -> None:
# This is just a placeholder to please CPython. It will be
# overridden during setup.
fields["tp_call"] = "PyVectorcall_Call"
if has_managed_dict(cl, emitter):
flags.append("Py_TPFLAGS_MANAGED_DICT")
fields["tp_flags"] = " | ".join(flags)

emitter.emit_line(f"static PyTypeObject {emitter.type_struct_name(cl)}_template_ = {{")
Expand Down Expand Up @@ -730,7 +733,9 @@ def generate_traverse_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -
for base in reversed(cl.base_mro):
for attr, rtype in base.attributes.items():
emitter.emit_gc_visit(f"self->{emitter.attr(attr)}", rtype)
if cl.has_dict:
if has_managed_dict(cl, emitter):
emitter.emit_line("_PyObject_VisitManagedDict((PyObject *)self, visit, arg);")
elif cl.has_dict:
struct_name = cl.struct_name(emitter.names)
# __dict__ lives right after the struct and __weakref__ lives right after that
emitter.emit_gc_visit(
Expand All @@ -751,7 +756,9 @@ def generate_clear_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> N
for base in reversed(cl.base_mro):
for attr, rtype in base.attributes.items():
emitter.emit_gc_clear(f"self->{emitter.attr(attr)}", rtype)
if cl.has_dict:
if has_managed_dict(cl, emitter):
emitter.emit_line("_PyObject_ClearManagedDict((PyObject *)self);")
elif cl.has_dict:
struct_name = cl.struct_name(emitter.names)
# __dict__ lives right after the struct and __weakref__ lives right after that
emitter.emit_gc_clear(
Expand Down Expand Up @@ -1040,3 +1047,15 @@ def generate_property_setter(
)
emitter.emit_line("return 0;")
emitter.emit_line("}")


def has_managed_dict(cl: ClassIR, emitter: Emitter) -> bool:
"""Should the class get the Py_TPFLAGS_MANAGED_DICT flag?"""
# On 3.11 and earlier the flag doesn't exist and we use
# tp_dictoffset instead. If a class inherits from Exception, the
# flag conflicts with tp_dictoffset set in the base class.
return (
emitter.capi_version >= (3, 12)
and cl.has_dict
and cl.builtin_base != "PyBaseExceptionObject"
)

0 comments on commit c2dc3ff

Please sign in to comment.