Traverse references held by operator callables (#9044)

Extract attrgetter, itemgetter and methodcaller GC traversal from #8954. Add four bounded reference-cycle cases to the existing operator snippet, including a non-dotted str subclass retained by attrgetter.

Assisted-by: OpenAI Codex:model identifier unavailable
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-10 19:34:41 +09:00
1 parent 9c8b745bfe
commit 7c9a0e32b3
2 files changed
+31 -3

No files matched your search

+3 -3
View File
@@ -351,7 +351,7 @@ mod _operator {
}
#[pyattr]
#[pyclass(name = "attrgetter", module = "operator")]
#[pyclass(name = "attrgetter", module = "operator", traverse)]
#[derive(Debug, PyPayload)]
struct PyAttrGetter {
attrs: Vec<PyStrRef>,
@@ -470,7 +470,7 @@ mod _operator {
}
#[pyattr]
#[pyclass(name = "itemgetter", module = "operator")]
#[pyclass(name = "itemgetter", module = "operator", traverse)]
#[derive(Debug, PyPayload)]
struct PyItemGetter {
items: Vec<PyObjectRef>,
@@ -556,7 +556,7 @@ mod _operator {
}
#[pyattr]
#[pyclass(name = "methodcaller", module = "operator")]
#[pyclass(name = "methodcaller", module = "operator", traverse)]
#[derive(Debug, PyPayload)]
struct PyMethodCaller {
name: PyUtf8StrRef,
+28
View File
@@ -3,3 +3,31 @@ import _operator
assert _operator._compare_digest("abcdef", "abcdef")
assert not _operator._compare_digest("abcdef", "abc")
assert not _operator._compare_digest("abc", "abcdef")
def check_operator_reference_cycles():
import gc
import weakref
class Holder:
pass
class Name(str):
pass
for make, make_payload in (
(_operator.itemgetter, Holder),
(lambda value: _operator.methodcaller("method", value), Holder),
(lambda value: _operator.methodcaller("method", value=value), Holder),
(_operator.attrgetter, lambda: Name("attribute")),
):
payload = make_payload()
wrapper = make(payload)
payload.wrapper = wrapper
reference = weakref.ref(payload)
del wrapper, payload
gc.collect()
assert reference() is None
check_operator_reference_cycles()