Skip to content

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Jan 18, 2026

Summary by CodeRabbit

  • New Features

    • Enhanced traversal and cleanup capabilities for built-in collection types (dictionaries, lists, tuples).
    • Added explicit clearing support for frame objects and other core types.
    • Introduced new metadata flags for tracking traversal and clearing support across object types.
  • Refactor

    • Consolidated garbage collection traversal logic with improved semantics for memory management across the VM's object system.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 18, 2026

Warning

Rate limit exceeded

@youknowone has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 48 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 3009d91 and f2b3a06.

📒 Files selected for processing (13)
  • crates/derive-impl/src/pyclass.rs
  • crates/derive-impl/src/pystructseq.rs
  • crates/derive-impl/src/util.rs
  • crates/vm/src/builtins/dict.rs
  • crates/vm/src/builtins/function.rs
  • crates/vm/src/builtins/list.rs
  • crates/vm/src/builtins/str.rs
  • crates/vm/src/builtins/tuple.rs
  • crates/vm/src/dict_inner.rs
  • crates/vm/src/frame.rs
  • crates/vm/src/object/core.rs
  • crates/vm/src/object/traverse.rs
  • crates/vm/src/object/traverse_object.rs
📝 Walkthrough

Walkthrough

This PR systematically refactors the garbage collection traversal system across RustPython, transitioning from an older "trace" mechanism to a "traverse" model with explicit support for circular reference clearing via new HAS_TRAVERSE, HAS_CLEAR flags and optional clear methods on types.

Changes

Cohort / File(s) Summary
Derive macro infrastructure
crates/derive-impl/src/pyclass.rs, crates/derive-impl/src/pystructseq.rs, crates/derive-impl/src/util.rs
Updated code generation to emit MaybeTraverse with HAS_TRAVERSE/HAS_CLEAR constants and try_clear hooks. PyClass now supports conditional traverse derivation and optional clear paths. Added "clear" to allowed item attributes.
Core traversal abstractions
crates/vm/src/object/traverse.rs, crates/vm/src/object/core.rs, crates/vm/src/object/traverse_object.rs
Replaced IS_TRACE with HAS_TRAVERSE/HAS_CLEAR constants. Added clear hook to Traverse trait. Renamed try_trace_obj to try_traverse_obj and updated all references.
Builtin type traversal implementations
crates/vm/src/builtins/dict.rs, crates/vm/src/builtins/list.rs, crates/vm/src/builtins/tuple.rs, crates/vm/src/builtins/function.rs, crates/vm/src/builtins/str.rs
Added manual Traverse implementations with explicit traverse and clear methods. Updated pyclass declarations to use traverse = "manual". Function.rs traverses additional fields (type_params, annotations, module, doc) and implements clearing logic.
Supporting garbage collection utilities
crates/vm/src/dict_inner.rs, crates/vm/src/frame.rs
Added pop_all_entries method to Dict for circular reference resolution. Introduced Traverse implementations for FrameState and Frame with manual traversal support.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #6319: Modifies MaybeTraverse trait implementation and generated pystructseq/pyclass traversal wiring with HAS_TRAVERSE/HAS_CLEAR semantics.
  • #5959: Updates derive-impl traversal code generation to add Traverse bounds and trait implementations.
  • #6320: Updates PyObject payload traversal helper with PyPayload bounds that pairs with try_traverse_obj renaming and HAS_TRAVERSE semantics.

Suggested reviewers

  • ShaharNaveh
  • arihant2math
  • coolreader18

Poem

🐰 Through forests deep where references dance,
We trace each path, give cycles a chance,
With HAS_TRAVERSE flags held high and clear,
The garbage collector's work draws near,
And when the dust settles, all cleaned and bright,
Our heap hops forward, unburdened and light!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main changes: introducing traverse and clear semantics for garbage collection across multiple builtin types and the derive macro system.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

@youknowone youknowone marked this pull request as ready for review January 18, 2026 13:26
@github-actions
Copy link
Contributor

Code has been automatically formatted

The code in this PR has been formatted using:

  • cargo fmt --all
    Please pull the latest changes before pushing again:
git pull origin traverse

@youknowone youknowone marked this pull request as draft January 18, 2026 13:32
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/vm/src/object/core.rs (1)

95-100: Doc comment still says try_trace.

Please update the comment to try_traverse to match the new function name.

✏️ Suggested doc fix
-/// Call `try_trace` on payload
+/// Call `try_traverse` on payload
🤖 Fix all issues with AI agents
In `@crates/derive-impl/src/pyclass.rs`:
- Around line 588-595: The code currently allows `clear` to be true while
`traverse` is absent which leaves `HAS_TRAVERSE` false and makes GC traversal a
no-op; update the logic around `has_traverse` and `has_clear` in the block using
`class_meta.inner()._has_key` and `class_meta.inner()._bool` to validate that if
`clear` is present and true then `traverse` must also be present (or return an
error) — e.g. after computing `has_clear` check `if has_clear && !has_traverse {
return Err(...) }` referencing the same symbols (`has_traverse`, `has_clear`,
`class_meta.inner()._has_key`, `_bool`, `HAS_TRAVERSE`) so the parser fails the
derive when `clear` is enabled without `traverse`.
- Around line 643-644: The generated signature for try_traverse is using
::rustpython_vm::object::TraverseFn without its required lifetime; update the
generated code so all uses of TraverseFn include the lifetime parameter (e.g.
TraverseFn<'a>) and propagate that 'a into the impl/generics for try_traverse
and any surrounding impl blocks where tracer_fn is referenced (look for the
try_traverse function and references to ::rustpython_vm::object::TraverseFn and
add the 'a lifetime consistently).

In `@crates/vm/src/builtins/function.rs`:
- Around line 54-58: The traversal currently grabs blocking locks (e.g.
self.type_params.lock().traverse(...), self.annotations.lock().traverse(...),
self.module.lock().traverse(...), self.doc.lock().traverse(...)) which can block
the GC and deadlock; instead call the Traverse implementation on the PyMutex
fields directly (use the PyMutex's non-blocking traverse method or
try_lock-based impl) — replace usages of .lock().traverse(tracer_fn) with the
PyMutex/field's traverse(tracer_fn) so traversal uses the non-blocking path for
type_params, annotations, module and doc.
🧹 Nitpick comments (1)
crates/vm/src/object/traverse_object.rs (1)

63-75: Stale comments reference old "trace" terminology.

The doc comment on line 64 and inline comment on line 67 still reference try_trace while the actual code calls T::try_traverse. Consider updating for consistency with the refactor.

📝 Suggested comment updates
 unsafe impl<T: MaybeTraverse> Traverse for PyInner<T> {
-    /// Type is known, so we can call `try_trace` directly instead of using erased type vtable
+    /// Type is known, so we can call `try_traverse` directly instead of using erased type vtable
     fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
         // 1. trace `dict` and `slots` field(`typ` can't trace for it's a AtomicRef while is leaked by design)
-        // 2. call corresponding `try_trace` function to trace payload
+        // 2. call corresponding `try_traverse` function to traverse payload
         // (No need to call vtable's trace function because we already know the type)
         // self.typ.trace(tracer_fn);
         self.dict.traverse(tracer_fn);

Comment on lines +588 to +595
let has_traverse = class_meta.inner()._has_key("traverse")?;
let has_clear = if class_meta.inner()._has_key("clear")? {
// If clear attribute is present, use its value
class_meta.inner()._bool("clear")?
} else {
// If clear attribute is absent, default to same as traverse
has_traverse
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Require traverse when clear is enabled.
If clear is set without traverse, HAS_TRAVERSE stays false and GC traversal becomes a no-op.

🔧 Proposed fix
 let has_traverse = class_meta.inner()._has_key("traverse")?;
 let has_clear = if class_meta.inner()._has_key("clear")? {
     // If clear attribute is present, use its value
     class_meta.inner()._bool("clear")?
 } else {
     // If clear attribute is absent, default to same as traverse
     has_traverse
 };
+if has_clear && !has_traverse {
+    bail_span!(
+        item,
+        "`clear` requires `traverse` (use `traverse` or `traverse = \"manual\"`)"
+    );
+}
🤖 Prompt for AI Agents
In `@crates/derive-impl/src/pyclass.rs` around lines 588 - 595, The code currently
allows `clear` to be true while `traverse` is absent which leaves `HAS_TRAVERSE`
false and makes GC traversal a no-op; update the logic around `has_traverse` and
`has_clear` in the block using `class_meta.inner()._has_key` and
`class_meta.inner()._bool` to validate that if `clear` is present and true then
`traverse` must also be present (or return an error) — e.g. after computing
`has_clear` check `if has_clear && !has_traverse { return Err(...) }`
referencing the same symbols (`has_traverse`, `has_clear`,
`class_meta.inner()._has_key`, `_bool`, `HAS_TRAVERSE`) so the parser fails the
derive when `clear` is enabled without `traverse`.

Comment on lines +643 to +644
fn try_traverse(&self, tracer_fn: &mut ::rustpython_vm::object::TraverseFn) {
#try_traverse_body
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Search for TraverseFn definition
rg -n "type TraverseFn|struct TraverseFn|trait TraverseFn" crates/vm/src/object -A 3 -B 1

Repository: RustPython/RustPython

Length of output: 186


🏁 Script executed:

# Check current code at lines 643-644 in the file
sed -n '640,650p' crates/derive-impl/src/pyclass.rs

Repository: RustPython/RustPython

Length of output: 186


🏁 Script executed:

# Search for other uses of TraverseFn in the codebase to see patterns
rg -n "TraverseFn" crates/ --type rs | head -20

Repository: RustPython/RustPython

Length of output: 186


🌐 Web query:

RustPython TraverseFn lifetime generic type definition

💡 Result:

Definition (from rustpython-vm crate):
pub type TraverseFn<'a> = dyn FnMut(&PyObject) + 'a. [1][2]

Sources:

  • rustpython-vm API docs — TraverseFn type alias. [1]
  • rustpython-vm source (object/mod.rs) showing the type alias. [2]

🌐 Web query:

RustPython vm object TraverseFn signature

💡 Result:

I can look that up in the RustPython source (to give the exact current Rust signature). Do you want me to fetch the signature from the repository (GitHub/docs.rs) now?


Fix the missing lifetime on TraverseFn in generated signatures.
The TraverseFn type is defined as dyn FnMut(&PyObject) + 'a and requires a lifetime parameter. Generated code must include it for type-checking.

🔧 Proposed fix
-            fn try_traverse(&self, tracer_fn: &mut ::rustpython_vm::object::TraverseFn) {
+            fn try_traverse(&self, tracer_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) {
                 `#try_traverse_body`
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn try_traverse(&self, tracer_fn: &mut ::rustpython_vm::object::TraverseFn) {
#try_traverse_body
fn try_traverse(&self, tracer_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) {
`#try_traverse_body`
🤖 Prompt for AI Agents
In `@crates/derive-impl/src/pyclass.rs` around lines 643 - 644, The generated
signature for try_traverse is using ::rustpython_vm::object::TraverseFn without
its required lifetime; update the generated code so all uses of TraverseFn
include the lifetime parameter (e.g. TraverseFn<'a>) and propagate that 'a into
the impl/generics for try_traverse and any surrounding impl blocks where
tracer_fn is referenced (look for the try_traverse function and references to
::rustpython_vm::object::TraverseFn and add the 'a lifetime consistently).

Comment on lines +54 to +58
// Traverse additional fields that may contain references
self.type_params.lock().traverse(tracer_fn);
self.annotations.lock().traverse(tracer_fn);
self.module.lock().traverse(tracer_fn);
self.doc.lock().traverse(tracer_fn);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid blocking locks in traverse to prevent GC deadlocks.

PyMutex::lock() can block if the lock is held when GC runs, which can deadlock traversal. Prefer the Traverse impl for PyMutex (uses try_lock) instead of manual locking.

✅ Safer traversal (non-blocking)
-        self.type_params.lock().traverse(tracer_fn);
-        self.annotations.lock().traverse(tracer_fn);
-        self.module.lock().traverse(tracer_fn);
-        self.doc.lock().traverse(tracer_fn);
+        self.type_params.traverse(tracer_fn);
+        self.annotations.traverse(tracer_fn);
+        self.module.traverse(tracer_fn);
+        self.doc.traverse(tracer_fn);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Traverse additional fields that may contain references
self.type_params.lock().traverse(tracer_fn);
self.annotations.lock().traverse(tracer_fn);
self.module.lock().traverse(tracer_fn);
self.doc.lock().traverse(tracer_fn);
// Traverse additional fields that may contain references
self.type_params.traverse(tracer_fn);
self.annotations.traverse(tracer_fn);
self.module.traverse(tracer_fn);
self.doc.traverse(tracer_fn);
🤖 Prompt for AI Agents
In `@crates/vm/src/builtins/function.rs` around lines 54 - 58, The traversal
currently grabs blocking locks (e.g. self.type_params.lock().traverse(...),
self.annotations.lock().traverse(...), self.module.lock().traverse(...),
self.doc.lock().traverse(...)) which can block the GC and deadlock; instead call
the Traverse implementation on the PyMutex fields directly (use the PyMutex's
non-blocking traverse method or try_lock-based impl) — replace usages of
.lock().traverse(tracer_fn) with the PyMutex/field's traverse(tracer_fn) so
traversal uses the non-blocking path for type_params, annotations, module and
doc.

@youknowone youknowone marked this pull request as ready for review January 18, 2026 13:38
@youknowone youknowone merged commit 9301ae2 into RustPython:main Jan 18, 2026
13 checks passed
@youknowone youknowone deleted the traverse branch January 18, 2026 14:23
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