From 7c9a0e32b3c52aa2fa7e2e3daa248be7ab3e13a7 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Sat, 10 Oct 2026 03:34:41 -0700 Subject: [PATCH] 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 --- crates/vm/src/stdlib/_operator.rs | 6 +++--- extra_tests/snippets/stdlib_operator.py | 28 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/crates/vm/src/stdlib/_operator.rs b/crates/vm/src/stdlib/_operator.rs index 975521af1..61548a859 100644 --- a/crates/vm/src/stdlib/_operator.rs +++ b/crates/vm/src/stdlib/_operator.rs @@ -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, @@ -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, @@ -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, diff --git a/extra_tests/snippets/stdlib_operator.py b/extra_tests/snippets/stdlib_operator.py index d0954b122..1fa1b1770 100644 --- a/extra_tests/snippets/stdlib_operator.py +++ b/extra_tests/snippets/stdlib_operator.py @@ -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()