Skip to content

memoryview: traverse the view for the collector, and name types as tp_name does - #8608

Merged
youknowone merged 2 commits into
RustPython:mainfrom
youknowone:RustPython-3
Aug 29, 2026
Merged

youknowone merged 2 commits into
RustPython:mainfrom
youknowone:RustPython-3

Conversation

@youknowone

@youknowone youknowone commented Aug 29, 2026 •

Copy link
Copy Markdown
Member

Two defects, both found while working through what was left after #8553. Comparisons are against CPython 3.14.6.

A cycle running through a view was never collected

Neither memoryview nor the _buffer_wrapper a __buffer__ produces reported what it held while being traversed, so the object a view looks at was invisible to the collector and the cycle was classified as reachable:

class Src:
    def __buffer__(self, flags):
        return memoryview(b'abcdef')

o = Src()
o.keep = memoryview(o)      # o -> memoryview -> wrapper -> o
del o
gc.collect()                # CPython: freed. before: never freed

The same held for a plain view over an exporter that refers back to it. Both types now report what they hold, as memory_traverse and mbuf_traverse do.

This is what test_gc in test_memoryview was covering; the three expectedFailure decorators are removed, and with them the overrides that only existed to carry the decorator.

An unhashable-type message dropped the module from the name

The message took the short name where CPython uses tp_name:

CPython before
hash(array.array('B', b'a')) unhashable type: 'array.array' unhashable type: 'array'
hash(collections.deque()) unhashable type: 'collections.deque' unhashable type: 'deque'
hash(Foo()) where Foo.__hash__ = None unhashable type: 'Foo' same

slot_name() already holds what tp_name holds — name() strips everything before the last dot — so this is that accessor at the two sites that build the message. A class defined in Python is still reported by its own name.

Only this message is changed. name() is read in 321 places, and which of them CPython builds from tp_name rather than __name__ is a separate audit.

Tests

extra_tests/snippets/stdlib_gc.py gains five cycle shapes through a view, and builtin_hash.py five type-name cases. Both files pass under CPython 3.14.6 as well.

16 suites run clean locally — memoryview, buffer, gc, weakref, array, bytes, dict, set, collections, io, re, types, descr, exceptions, pickle, struct.

Left out

  • pickle.PickleBuffer is still missing. It is reachable only through _pickle, and test_pickle.py reads a successful import _pickle as "the C accelerator is present", then binds dump, dumps, load, loads, Pickler and Unpickler from it in a class body. A _pickle carrying only PickleBuffer therefore breaks that file at import, so the accelerator has to come first.
  • Argument errors carry no function name — readinto() argument must be read-write bytes-like object is reported as buffer is not a read-write bytes-like object. ArgumentError::into_exception never receives the name, which is why the arity messages read expected at least 1 arguments, got 0 too. Threading it through is a change to the argument-binding path rather than to any one converter.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P9HewXGX8qcGSccUxGdSPV

Summary by CodeRabbit

  • Bug Fixes

    • Improved garbage collection for memoryview objects and custom buffer exporters, helping cyclic references be collected reliably.
    • Corrected unhashable-type error messages so they display the type name accurately.
  • Tests

    • Added coverage for garbage collection across sliced, cast, nested, and custom-exported memory views.
    • Added tests verifying unhashable-type message formatting.

Neither `memoryview` nor the `_buffer_wrapper` a `__buffer__` produces was
traversed, so the object a view looks at was never reported and a cycle
running through one was classified as reachable:

    class Src:
        def __buffer__(self, flags): return memoryview(b'abcdef')
    o = Src(); o.keep = memoryview(o)   # o is never freed

Both now report what they hold, as `memory_traverse` and `mbuf_traverse` do.

Assisted-by: Claude
The message took the short name, so a type defined in a module lost the
module: `hash(array.array('B', b'a'))` said `unhashable type: 'array'`
where the name is `array.array`, and `collections.deque` said `'deque'`.
`slot_name()` is what `tp_name` holds, and it leaves a class defined in
Python reported by its own name as before.

Assisted-by: Claude
@coderabbitai

coderabbitai Bot commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2bc7f495-8195-4045-8a36-779bc069c321

📥 Commits

Reviewing files that changed from the base of the PR and between 27d13d2 and 7344458.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_memoryview.py is excluded by !Lib/**
📒 Files selected for processing (5)
  • crates/vm/src/builtins/memory.rs
  • crates/vm/src/protocol/object.rs
  • crates/vm/src/types/slot.rs
  • extra_tests/snippets/builtin_hash.py
  • extra_tests/snippets/stdlib_gc.py

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds cycle-collector traversal metadata to memoryview-related classes and tests cycle collection. It also changes unhashable type errors to use slot names and adds coverage for built-in and user-defined types.

Changes

Memoryview garbage collection

Layer / File(s) Summary
Memoryview traversal and cycle tests
crates/vm/src/builtins/memory.rs, extra_tests/snippets/stdlib_gc.py
Memoryview-related classes now mark Python-reference fields for GC traversal. Tests cover direct, sliced, cast, nested, and exporter-backed memoryview cycles.

Unhashable type error names

Layer / File(s) Summary
Hash error formatting and coverage
crates/vm/src/protocol/object.rs, crates/vm/src/types/slot.rs, extra_tests/snippets/builtin_hash.py
Unhashable type errors now use slot_name(). Tests verify names for built-in, module-qualified, and user-defined types.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 73444

The changes improve collection of memoryview-related cycles and correct module-qualified unhashable-type messages, with focused tests covering both behaviors. No actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: shaharnaveh

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes both main changes: garbage-collector traversal for memory views and tp_name-style unhashable type names.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] test: cpython/Lib/test/test_memoryview.py (TODO: 1)

dependencies:

dependent tests: (no tests depend on memoryview)

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

@youknowone
youknowone marked this pull request as ready for review August 29, 2026 13:19
@youknowone
youknowone merged commit 371bb82 into RustPython:main Aug 29, 2026
29 checks passed
@youknowone
youknowone deleted the RustPython-3 branch August 29, 2026 14:00
youknowone added a commit that referenced this pull request Sep 16, 2026
…p_name` does (#8608)

* memoryview: walk the view and the buffer wrapper for the collector

Neither `memoryview` nor the `_buffer_wrapper` a `__buffer__` produces was
traversed, so the object a view looks at was never reported and a cycle
running through one was classified as reachable:

    class Src:
        def __buffer__(self, flags): return memoryview(b'abcdef')
    o = Src(); o.keep = memoryview(o)   # o is never freed

Both now report what they hold, as `memory_traverse` and `mbuf_traverse` do.

Assisted-by: Claude

* Name the type in an unhashable-type message the way `tp_name` does

The message took the short name, so a type defined in a module lost the
module: `hash(array.array('B', b'a'))` said `unhashable type: 'array'`
where the name is `array.array`, and `collections.deque` said `'deque'`.
`slot_name()` is what `tp_name` holds, and it leaves a class defined in
Python reported by its own name as before.

Assisted-by: Claude
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant