Skip to content

Read tuple, bytes, int, float and complex payloads through views on Py<T> - #8819

Merged
youknowone merged 12 commits into
RustPython:mainfrom
youknowone:explicit-payload
Sep 26, 2026
Merged

youknowone merged 12 commits into
RustPython:mainfrom
youknowone:explicit-payload

Conversation

@youknowone

@youknowone youknowone commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #8812.

Payload reads for tuple, bytes, int, float and complex go through explicit views on Py<T> instead of Deref<Target = T>. Deref itself is unchanged here.

  • Views on Py<T>:
    • Py<PyTuple<R>>::as_slice(), generic over R so typed tuples share it
    • Py<PyBytes>::as_bytes()
    • Py<PyInt>::as_bigint(), Py<PyFloat>::to_f64(), Py<PyComplex>::as_complex()
  • PyInt helpers that only read the integer value (try_to_primitive(), try_to_primitive_in_range(), as_u32_mask(), as_u64_mask(), to_str_radix_10(), try_to_i64_fast()) and the operators __xor__ / __or__ / __and__, general_op() and modpow() are defined on Py<PyInt> instead of the payload. PyBytes operators (__add__, __contains__, __getitem__, __mod__) move to Py<PyBytes> the same way.
  • try_to_primitive() and try_to_primitive_in_range() (renamed from try_to_primitive_raw()) share one conversion and differ only in the error for a negative value to an unsigned type: ValueError (PyLong_AsUInt32 and the unsigned argument converters) or OverflowError (PyLong_AsUnsignedLong, PyLong_AsSize_t). Behavior is unchanged.
  • Operations with their own logic (PyBytesInner in bytes.rs, PyBytes::contains_nuls, PyTuple::set_marshal_item, the types' own sequence/mapping slots, ...) are called through the payload field inside rustpython-vm and through payload() outside it.
  • Py<T>::payload is pub(crate).
  • DictKey for [u8] falls back to the PyObject key_eq. Its fallback previously resolved to its own key_eq through Deref.
  • ast constants build float and complex objects with .into(); they previously went through Deref and allocated a second object.
  • range's sequence/mapping length converts the BigInt length directly instead of building a PyInt payload.

🤖 Generated with Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: RustPython/RustPython/.coderabbit.yml

Review profile: CHILL

Plan: Advanced

Run ID: 801e5715-10e8-4ee9-bd40-e94abd148e46

📥 Commits

Reviewing files that changed from the base of the PR and between f4f68a6 and 18d4764.

📒 Files selected for processing (1)
  • crates/vm/src/object/ext.rs

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


📝 Walkthrough

Walkthrough

The object API now exposes payload access for all types and limits implicit dereferencing to opted-in types. Tuple, list, and byte operations use borrowed slices or underlying data. Runtime access patterns changed across the C API, VM, standard library, derive code, and WebAssembly bindings. The summaries report existing behavior and checks as unchanged.

Changes

Payload and Slice Access

Layer / File(s) Summary
Payload access contract and core VM operations
crates/vm/src/object/*, crates/vm/src/builtins/*, crates/vm/src/frame.rs, crates/vm/src/protocol/*, crates/vm/src/types/*
Py<T>::payload is public and available for all T. Deref now requires PayloadDeref, which selected payload types implement. VM, built-in, protocol, and type-slot operations use payload fields and accessors.
Standard-library payload access
crates/vm/src/stdlib/*, crates/stdlib/src/*
Standard-library implementations access object state through payloads. Tuple validation and traversal use as_slice(); byte operations use byte slices. The described operation paths and checks remain unchanged.
C API and platform integrations
crates/capi/src/*, crates/derive-impl/src/*, crates/wasm/src/*
C API operations, derive code, and WebAssembly bindings use payloads or underlying slices. The described conversion and validation behavior remains unchanged.

Priority: ⬇️ Low

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: ⚪ Minimal · up to 18d47

This change primarily makes object and collection access explicit. No user-facing regression is established in the supplied review context, so the remaining merge risk is minimal.

Architecture Summary

Architecture risk: 🔵 Low · up to 18d47

The change affects 1 system.

Changed systems: crates

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — crates (service) was modified; 162 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in crates/capi/src/abstract_.rs: tuple_to_args now creates its positional-argument vector using PyTuple::as_slice().to_vec() rather than iterating and cloning tuple elements.
  • observed — Modified behavior in crates/capi/src/abstract_.rs: PyObject_Vectorcall now passes the keyword-name tuple’s slice directly instead of obtaining a slice through nested dereferencing.
  • observed — Modified behavior in crates/capi/src/abstract_.rs: PyVectorcall_Call now initializes its argument vector from the tuple slice with to_vec() instead of iterating and cloning elements.
  • observed — Modified behavior in crates/capi/src/listobject.rs: PyList_Size now reads the length through borrow_vec() rather than __len__().
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 49.51% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 921 functions across 106 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes payload access changes for several built-in types through views on Py. It omits the broader payload migration and many other affected types, but it remains specific a…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

@youknowone
youknowone force-pushed the explicit-payload branch 3 times, most recently from f4f68a6 to 18d4764 Compare September 26, 2026 07:18
@codspeed

codspeed Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 62 untouched benchmarks
⏩ 4 skipped benchmarks1


Comparing youknowone:explicit-payload (e92674f) with main (5c875d3)

Open in CodSpeed

Footnotes

  1. 4 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@youknowone
youknowone force-pushed the explicit-payload branch 3 times, most recently from 9a62686 to a4bd49b Compare September 26, 2026 14:28
@youknowone youknowone changed the title Stop dereferencing Py<T> to the payload for PyTuple, PyBytes and PyList Read tuple, bytes, list, int, float and complex payloads through views on Py<T> Sep 26, 2026
@youknowone
youknowone force-pushed the explicit-payload branch 2 times, most recently from 179abf9 to bb81feb Compare September 26, 2026 15:04
@youknowone youknowone changed the title Read tuple, bytes, list, int, float and complex payloads through views on Py<T> Read tuple, bytes, int, float and complex payloads through views on Py<T> Sep 26, 2026
@youknowone
youknowone force-pushed the explicit-payload branch 2 times, most recently from de5ddcd to 9639dde Compare September 26, 2026 15:59
- Add Py<PyTuple<R>>::as_slice(); it is generic over R, so typed tuples use
  the same view.
- Callers reach slice methods, indexing and slice arguments through
  as_slice().
- PyTuple's own sequence and mapping slots and marshal's set_marshal_item reach
  the remaining PyTuple methods through the payload field.
- The Py<T>::payload field is pub(crate).

Assisted-by: Claude Code:claude-opus-5-5
- Callers read bytes through Py<PyBytes>::as_bytes().
- bytes.rs reaches PyBytesInner and the remaining PyBytes methods through
  the payload field; fspath and os call payload.contains_nuls().
- DictKey for [u8] falls back to the PyObject key_eq; it previously resolved
  to its own key_eq through Deref.

Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
…es()

Assisted-by: Claude Code:claude-opus-5-5
Add `Py<PyInt>::as_bigint()`, `Py<PyInt>::try_to_primitive()` and
`Py<PyFloat>::to_f64()`, which forward to the payload methods of the same
name. Reads of the `value` field and primitive conversions go through
these views; other PyInt and PyFloat methods are called through the
`payload` field inside rustpython-vm and through payload() outside it.

Assisted-by: Claude Code:claude-opus-5-5
Add Py<PyComplex>::as_complex() and use it at the call sites that
read the payload directly.

Assisted-by: Claude Code:claude-opus-5-5
try_to_i64_fast(), to_str_radix_10(), as_u32_mask(), as_u64_mask(),
try_to_primitive() and try_to_primitive_raw() only read the integer
value, so they are defined on Py<PyInt> and callers no longer go through
the payload. PyRange computes its protocol length from the BigInt
directly instead of building a PyInt payload.

Assisted-by: Claude Code:claude-opus-5-5
… variant

Py<PyInt>::try_to_primitive() and try_to_primitive_in_range(), renamed
from try_to_primitive_raw(), both call to_primitive(). They differ only
in how a negative value for an unsigned type is reported: ValueError for
try_to_primitive(), OverflowError for try_to_primitive_in_range(). The
target type is bound with `for<'a> TryFrom<&'a BigInt>`, so the methods
no longer tie a lifetime to &self.

Assisted-by: Claude Code:claude-opus-5-5
__xor__, __or__, __and__ and their int_op() helper are defined on
Py<PyInt>. PyBool calls them without going through the payload.

Assisted-by: Claude Code:claude-opus-5-5
__add__, __contains__, __getitem__ and __mod__ are defined on
Py<PyBytes>. The subscript, contains and remainder slots call them
without going through the payload.

Assisted-by: Claude Code:claude-opus-5-5
The power slot calls them on Py<PyInt> without going through the
payload.

Assisted-by: Claude Code:claude-opus-5-5
@youknowone
youknowone merged commit dc3b93a into RustPython:main Sep 26, 2026
30 checks passed
@youknowone
youknowone deleted the explicit-payload branch September 26, 2026 22:59
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