Skip to content

Commit 97d7722

Browse files
Fix weakref support in native classes (#11)
* Fix weakref support in native classes * Update util.py
1 parent d1d56ab commit 97d7722

File tree

4 files changed

+45
-14
lines changed

4 files changed

+45
-14
lines changed

mypyc/codegen/emitclass.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def emit_line() -> None:
331331
if emitter.capi_version < (3, 12):
332332
fields["tp_dictoffset"] = base_size
333333
fields["tp_weaklistoffset"] = weak_offset
334-
elif cl.supports_weakref:
334+
elif cl.supports_weakref and emitter.capi_version < (3, 12):
335335
# __weakref__ lives right after the struct
336336
# TODO: It should get a member in the struct instead of doing this nonsense.
337337
emitter.emit_lines(
@@ -340,10 +340,11 @@ def emit_line() -> None:
340340
"{0}",
341341
"};",
342342
)
343-
if emitter.capi_version < (3, 12):
344-
# versions >= 3.12 set Py_TPFLAGS_MANAGED_WEAKREF flag instead
345-
# https://docs.python.org/3.12/extending/newtypes.html#weak-reference-support
346-
fields["tp_weaklistoffset"] = base_size
343+
fields["tp_members"] = members_name
344+
fields["tp_basicsize"] = f"{base_size} + sizeof(PyObject *)"
345+
# versions >= 3.12 set Py_TPFLAGS_MANAGED_WEAKREF flag instead
346+
# https://docs.python.org/3.12/extending/newtypes.html#weak-reference-support
347+
fields["tp_weaklistoffset"] = base_size
347348
else:
348349
fields["tp_basicsize"] = base_size
349350

@@ -902,6 +903,12 @@ def generate_traverse_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -
902903
f"*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({struct_name})))",
903904
object_rprimitive,
904905
)
906+
elif cl.supports_weakref and emitter.capi_version < (3, 12):
907+
struct_name = cl.struct_name(emitter.names)
908+
# __weakref__ lives right after the struct
909+
emitter.emit_gc_visit(
910+
f"*((PyObject **)((char *)self + sizeof({struct_name})))", object_rprimitive
911+
)
905912
emitter.emit_line("return 0;")
906913
emitter.emit_line("}")
907914

@@ -925,6 +932,12 @@ def generate_clear_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> N
925932
f"*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({struct_name})))",
926933
object_rprimitive,
927934
)
935+
elif cl.supports_weakref and emitter.capi_version < (3, 12):
936+
struct_name = cl.struct_name(emitter.names)
937+
# __weakref__ lives right after the struct
938+
emitter.emit_gc_clear(
939+
f"*((PyObject **)((char *)self + sizeof({struct_name})))", object_rprimitive
940+
)
928941
emitter.emit_line("return 0;")
929942
emitter.emit_line("}")
930943

@@ -940,12 +953,7 @@ def generate_dealloc_for_class(
940953
emitter.emit_line(f"{dealloc_func_name}({cl.struct_name(emitter.names)} *self)")
941954
emitter.emit_line("{")
942955
if cl.supports_weakref:
943-
if emitter.capi_version < (3, 12):
944-
emitter.emit_line("if (self->weakreflist != NULL) {")
945-
emitter.emit_line("PyObject_ClearWeakRefs((PyObject *) self);")
946-
emitter.emit_line("}")
947-
else:
948-
emitter.emit_line("PyObject_ClearWeakRefs((PyObject *) self);")
956+
emitter.emit_line("PyObject_ClearWeakRefs((PyObject *) self);")
949957
if has_tp_finalize:
950958
emitter.emit_line("PyObject *type, *value, *traceback;")
951959
emitter.emit_line("PyErr_Fetch(&type, &value, &traceback);")

mypyc/ir/class_ir.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ def deserialize(cls, data: JsonDict, ctx: DeserMaps) -> ClassIR:
447447
ir.is_final_class = data["is_final_class"]
448448
ir.inherits_python = data["inherits_python"]
449449
ir.has_dict = data["has_dict"]
450-
ir.supports_weakref = data["supports_weakref"]
450+
ir.supports_weakref = data.get("supports_weakref", False)
451451
ir.allow_interpreted_subclasses = data["allow_interpreted_subclasses"]
452452
ir.needs_getseters = data["needs_getseters"]
453453
ir._serializable = data["_serializable"]

mypyc/irbuild/util.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,32 @@
3333
from mypyc.errors import Errors
3434

3535
MYPYC_ATTRS: Final[frozenset[MypycAttr]] = frozenset(
36-
["native_class", "allow_interpreted_subclasses", "serializable", "free_list_len"]
36+
[
37+
"native_class",
38+
"allow_interpreted_subclasses",
39+
"serializable",
40+
"supports_weakref",
41+
"free_list_len",
42+
]
3743
)
3844

3945
DATACLASS_DECORATORS: Final = frozenset(["dataclasses.dataclass", "attr.s", "attr.attrs"])
4046

4147

4248
MypycAttr = Literal[
43-
"native_class", "allow_interpreted_subclasses", "serializable", "free_list_len"
49+
"native_class",
50+
"allow_interpreted_subclasses",
51+
"serializable",
52+
"supports_weakref",
53+
"free_list_len",
4454
]
4555

4656

4757
class MypycAttrs(TypedDict):
4858
native_class: NotRequired[bool]
4959
allow_interpreted_subclasses: NotRequired[bool]
5060
serializable: NotRequired[bool]
61+
supports_weakref: NotRequired[bool]
5162
free_list_len: NotRequired[int]
5263

5364

mypyc/test-data/run-weakref.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ class Object:
1515

1616
_callback_called_cache = {"ref": False, "proxy": False}
1717

18+
@mypyc_attr(supports_weakref=True)
19+
class NativeObject:
20+
def some_meth(self) -> int:
21+
return 1
22+
1823
def test_weakref_ref() -> None:
1924
obj: Optional[Object] = Object()
2025
r = ref(obj)
@@ -50,3 +55,10 @@ def test_weakref_proxy_with_callback() -> None:
5055
with assertRaises(ReferenceError):
5156
p.some_meth()
5257
assert _callback_called_cache["proxy"] is True
58+
59+
def test_weakref_native_ref() -> None:
60+
obj: Optional[NativeObject] = NativeObject()
61+
r = ref(obj)
62+
assert r() is obj
63+
obj = None
64+
assert r() is None

0 commit comments

Comments
 (0)