Skip to content

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Dec 4, 2025

Summary by CodeRabbit

  • Refactor
    • Core struct-sequence internals moved to a data-backed model; stdlib modules (os, time, pwd, grp, resource, sys, nt, posix) now return/from explicit data objects with thin Python wrappers, preserving visible field layouts.
    • struct_time now exposes explicit timezone/offset fields and mktime treats inputs as local time.
    • Terminal-size, stat/times/uname, passwd/grp/resource, and sys/info objects now use the new data-backed representation while keeping Python-facing behavior.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 4, 2025

Walkthrough

Migrate PyStructSequence from derive-based macros to attribute-based macros and a data-driven model: introduce #[pystruct_sequence] and #[pystruct_sequence_data], add runtime struct-sequence helpers and traits, expand VM interned names, and convert many stdlib types to Data + Py-wrapper pattern.

Changes

Cohort / File(s) Summary
Macro surface & entrypoints
crates/derive/src/lib.rs, crates/derive-impl/src/lib.rs
Replace derive entrypoints with attribute macros pystruct_sequence(attr,item) and pystruct_sequence_data(attr,item); remove old derive wrappers and update delegates to pystructseq::impl_pystruct_sequence / impl_pystruct_sequence_data.
Macro implementation & module integration
crates/derive-impl/src/pystructseq.rs, crates/derive-impl/src/pymodule.rs
Add meta parsing for #[pystruct_sequence(...)] and #[pystruct_sequence_data(...)]; introduce FieldKind/ParsedField/FieldInfo, impl_pystruct_sequence (wrapper/type path) and impl_pystruct_sequence_data (data-driven path); add StructSequenceItem and module integration.
VM struct-sequence runtime & traits
crates/vm/src/types/structseq.rs, crates/vm/src/types/mod.rs
Add PyStructSequenceData trait, PyStructSequence extension with associated Data and from_data, struct_sequence_new, visible-length-aware seq/mapping methods, and re-export PyStructSequenceData and struct_sequence_new.
VM interned names & unraisable flow
crates/vm/src/vm/context.rs, crates/vm/src/vm/mod.rs
Add interned names n_fields, n_sequence_fields, n_unnamed_fields to ConstName; change run_unraisable to construct UnraisableHookArgsData instead of old wrapper.
Stdlib: general migration pattern
crates/stdlib/src/grp.rs, crates/stdlib/src/resource.rs, crates/stdlib/src/pwd.rs, crates/vm/src/stdlib/*
Convert many PyStructSequence-derived public structs into *Data data structs plus Py* wrappers; update From conversions, constructors, and public function return types to use data-backed variants (e.g., GroupData/PyGroup, RUsageData/PyRUsage, PasswdData/PyPasswd, StatResultData/PyStatResult, TimesResultData/PyTimesResult, TerminalSizeData/PyTerminalSize, UnameResultData/PyUnameResult, StructTimeData/PyStructTime).
Stdlib sys-wide conversions
crates/vm/src/stdlib/sys.rs
Replace many public PyStructSequence types with *Data structs and Py* wrappers (FlagsData/PyFlags, ThreadInfoData/PyThreadInfo, FloatInfoData/PyFloatInfo, HashInfoData/PyHashInfo, IntInfoData/PyIntInfo, VersionInfoData/PyVersionInfo, WindowsVersionData/PyWindowsVersion, UnraisableHookArgsData/PyUnraisableHookArgs, AsyncgenHooksData/PyAsyncgenHooks) and update call sites.
Platform glue updates
crates/vm/src/stdlib/posix.rs, crates/vm/src/stdlib/nt.rs
Update function return types and constructions to use new *Data types (e.g., TerminalSizeData, UnameResultData) and adjust callers.
Time module adaptations
crates/vm/src/stdlib/time.rs
Introduce StructTimeData, make time APIs accept/return StructTimeData, add PyStructTime wrapper, update mktime/asctime/strftime/gmtime/localtime signatures and conversions, and add struct-sequence construction helper usage.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Potential hotspots to focus on:

  • crates/derive-impl/src/pystructseq.rs — macro parsing, dual code-gen paths, field-index constants.
  • crates/vm/src/types/structseq.rs — trait invariants, sequence/mapping behavior, edge-case padding and hashing/comparison semantics.
  • Wide API changes across stdlib and vm — verify all changed signatures, From conversions, and call sites (grp, resource, pwd, os, sys, time, posix, nt).
  • run_unraisable/unraisablehook payload compatibility and interned-name usage.

Possibly related PRs

  • Update resource from 3.13.5 #5893 — Fix to ru_stime assignment in crates/stdlib/src/resource.rs; closely related to the RUsageData migration and libc->data conversions.

Suggested reviewers

  • ShaharNaveh
  • arihant2math

Poem

🐰 I hopped through macros, fields, and name,
Swapped derives for attributes — not for fame.
Data structs now carry rows with care,
Wrappers show Python tuples — neat and fair.
A tiny hop to bind each field with flair.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'PyStructSequence Compatibility' is vague and does not clearly convey the primary changes. The PR significantly refactors PyStructSequence from derive macros to attribute macros and introduces a data-driven pattern across multiple stdlib modules, but the title uses generic terminology that obscures these details. Use a more specific title such as 'Refactor PyStructSequence to attribute macros with data-driven pattern' or 'Convert PyStructSequence from derive to attribute macros' to better communicate the main architectural change.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 force-pushed the structseq branch 3 times, most recently from f713c47 to 3c1d284 Compare December 5, 2025 02:19
@youknowone youknowone marked this pull request as ready for review December 5, 2025 06:53
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: 2

Caution

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

⚠️ Outside diff range comments (1)
crates/derive-impl/src/pymodule.rs (1)

270-324: Allow #[pyattr] to precede #[pystruct_sequence] like other type items

In attrs_to_module_items, when py_attrs is non-empty you only accept Class | Function | Exception:

match attr_name {
    AttrName::Class | AttrName::Function | AttrName::Exception => { ... }
    _ => bail!("...[pyclass], #[pyfunction], or #[pyexception] can follow #[pyattr]"),
}

This rejects patterns like:

#[pyattr]
#[pystruct_sequence(name = "stat_result", module = "os", data = "StatResultData")]
struct PyStatResult;

even though StructSequenceItem is designed to use self.py_attrs for custom export names.

Consider treating StructSequence like the other type-defining attributes here:

-            match attr_name {
-                AttrName::Class | AttrName::Function | AttrName::Exception => {
+            match attr_name {
+                AttrName::Class | AttrName::Function | AttrName::Exception
+                | AttrName::StructSequence => {
                     result.push(item_new(i, attr_name, py_attrs.clone()));
                 }

This preserves the existing requirement that only one grouped #[pyattr] applies while enabling #[pyattr] + #[pystruct_sequence] combinations.

🧹 Nitpick comments (5)
crates/derive-impl/src/util.rs (1)

11-25: Remove duplicate "pystruct_sequence" from ALL_ALLOWED_NAMES

ALL_ALLOWED_NAMES lists "pystruct_sequence" twice, which is redundant and may confuse future readers, even though contains() semantics are unchanged.

You can simplify the constant like this:

 pub(crate) const ALL_ALLOWED_NAMES: &[&str] = &[
@@
-    "pystruct_sequence",
-    "pystruct_sequence",
+    "pystruct_sequence",
     "pyattr",
crates/derive-impl/src/pymodule.rs (1)

412-417: Re-use existing pystruct_sequence meta parsing to avoid duplication

StructSequenceItem::gen_module_item manually re-parses #[pystruct_sequence(...)] to extract name, module, and no_attr, while similar logic already exists in the PyStructSequenceMeta machinery in pystructseq.rs.

To keep behavior in sync and reduce maintenance risk, consider refactoring gen_module_item to delegate to the shared meta type (or a small helper built on ItemMetaInner) rather than duplicating parsing here. This would also ensure any future options added to #[pystruct_sequence] are handled consistently in both the macro expansion and module wiring.

Also applies to: 626-729

crates/derive-impl/src/pystructseq.rs (3)

10-65: Field parsing and classification are clear; consider validating conflicting pystruct options

FieldKind / FieldInfo plus parse_fields give a nice, centralized view over named/unnamed/skipped fields, and stripping #[pystruct(..)] off the synthesized input avoids leaking helper attrs into downstream derives.

One edge case you might want to guard against is a field marked with both #[pystruct(skip)] and #[pystruct(unnamed)]. Currently that would silently prioritise skip via:

let kind = if skip {
    FieldKind::Skipped
} else if unnamed {
    FieldKind::Unnamed
} else {
    FieldKind::Named
};

If such a combination is considered invalid, explicitly bailing with a helpful error (rather than silently picking one) would make misuse easier to diagnose.

Also applies to: 67-144


166-285: Strengthen #[pystruct_sequence_data] codegen for generics and TryFromObject semantics

The overall structure of impl_pystruct_sequence_data looks good, but there are a few details worth tightening:

  1. Generics on data structs are currently ignored

    DeriveInput carries input.generics, but the generated impls use a bare #data_ident:

    impl #data_ident { ... }
    impl ::rustpython_vm::types::PyStructSequenceData for #data_ident { ... }

    For a generic data struct (even though you don’t have one today), this will fail to compile. It’d be more robust to thread generics through:

    let generics = input.generics.clone();
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
    
    impl #impl_generics #data_ident #ty_generics #where_clause { ... }
    
    impl #impl_generics ::rustpython_vm::types::PyStructSequenceData
        for #data_ident #ty_generics #where_clause
    { ... }
  2. TryFromObject uses visible_fields.len() as the minimum length

    The current check:

    let n_required = visible_fields.len();
    if seq.len() < #n_required { ... }

    treats all visible fields (named + unnamed) as mandatory. Conceptually, only the named fields are truly “required”; unnamed fields are typically the tail-only, sequence-only extras. If you ever combine #[pystruct(unnamed)] with try_from_object, using named_fields.len() as the lower bound would more closely match the separation encoded in FieldInfo and the REQUIRED_FIELD_NAMES constant.

  3. Avoid extra cloning in try_from_elements

    Inside the generated try_from_elements body you do:

    #(#visible_fields: iter.next().unwrap().clone().try_into_value(vm)?,)*
    #(#skipped_fields: match iter.next() {
        Some(v) => v.clone().try_into_value(vm)?,
        None => vm.ctx.none(),
    },)*

    Because iter owns the vector, iter.next() already yields an owned PyObjectRef; the .clone() is redundant. You can drop the clones to avoid extra Arc bumps:

    #(#visible_fields: iter.next().unwrap().try_into_value(vm)?,)*
    #(#skipped_fields: match iter.next() {
        Some(v) => v.try_into_value(vm)?,
        None => vm.ctx.none(),
    },)*

None of these are blockers, but addressing them would make the macro path more future-proof.


303-394: PyStructSequenceMeta and impl_pystruct_sequence wiring look sound

The meta parser correctly restricts #[pystruct_sequence(...)] to known keys (name, module, data, no_attr) and enforces string values for name/module/data, with a clear error if data is missing. The generated PyClassDef/StaticType/MaybeTraverse/PyStructSequence/ToPyObject impls form a clean, minimal wrapper over the data struct and base PyTuple.

Given current usage, constraining data to a simple identifier via format_ident! is fine, but if you ever need to support a non-local path (e.g., data = "time::StructTimeData"), this will need to be extended to parse a path instead of a bare ident. For now, the approach is straightforward and keeps the attribute surface tight.

Also applies to: 406-494

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2ca9a7 and 1347f6e.

⛔ Files ignored due to path filters (3)
  • Lib/test/datetimetester.py is excluded by !Lib/**
  • Lib/test/test_logging.py is excluded by !Lib/**
  • Lib/test/test_structseq.py is excluded by !Lib/**
📒 Files selected for processing (17)
  • crates/derive-impl/src/lib.rs (1 hunks)
  • crates/derive-impl/src/pymodule.rs (7 hunks)
  • crates/derive-impl/src/pystructseq.rs (3 hunks)
  • crates/derive-impl/src/util.rs (1 hunks)
  • crates/derive/src/lib.rs (1 hunks)
  • crates/stdlib/src/grp.rs (5 hunks)
  • crates/stdlib/src/resource.rs (4 hunks)
  • crates/vm/src/stdlib/nt.rs (2 hunks)
  • crates/vm/src/stdlib/os.rs (9 hunks)
  • crates/vm/src/stdlib/posix.rs (3 hunks)
  • crates/vm/src/stdlib/pwd.rs (7 hunks)
  • crates/vm/src/stdlib/sys.rs (21 hunks)
  • crates/vm/src/stdlib/time.rs (7 hunks)
  • crates/vm/src/types/mod.rs (1 hunks)
  • crates/vm/src/types/structseq.rs (4 hunks)
  • crates/vm/src/vm/context.rs (1 hunks)
  • crates/vm/src/vm/mod.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Follow the default rustfmt code style by running cargo fmt to format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • crates/vm/src/types/mod.rs
  • crates/vm/src/stdlib/posix.rs
  • crates/derive-impl/src/util.rs
  • crates/vm/src/vm/context.rs
  • crates/vm/src/stdlib/nt.rs
  • crates/derive-impl/src/pymodule.rs
  • crates/derive-impl/src/lib.rs
  • crates/vm/src/vm/mod.rs
  • crates/stdlib/src/resource.rs
  • crates/derive/src/lib.rs
  • crates/stdlib/src/grp.rs
  • crates/vm/src/stdlib/pwd.rs
  • crates/vm/src/types/structseq.rs
  • crates/vm/src/stdlib/time.rs
  • crates/derive-impl/src/pystructseq.rs
  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/stdlib/os.rs
🧠 Learnings (6)
📓 Common learnings
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In RustPython's pattern matching implementation, only certain builtin types should have the SEQUENCE flag: list and tuple are confirmed sequences. The user youknowone indicated that bytes, bytearray are not considered sequences in this context, even though they implement sequence-like protocols.
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In RustPython's pattern matching implementation, only certain builtin types should have the SEQUENCE flag: list and tuple are confirmed sequences. The user youknowone indicated that bytes, bytearray are not considered sequences in this context, even though they implement sequence-like protocols.

Applied to files:

  • crates/vm/src/types/mod.rs
  • crates/vm/src/types/structseq.rs
  • crates/vm/src/stdlib/sys.rs
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.

Applied to files:

  • crates/vm/src/types/mod.rs
  • crates/vm/src/types/structseq.rs
  • crates/vm/src/stdlib/sys.rs
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust

Applied to files:

  • crates/derive/src/lib.rs
  • crates/vm/src/stdlib/time.rs
  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/stdlib/os.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration using #ifdef checks rather than providing fallback values for other platforms.

Applied to files:

  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/stdlib/os.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration rather than providing fallback values for other platforms.

Applied to files:

  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/stdlib/os.rs
🧬 Code graph analysis (8)
crates/vm/src/types/mod.rs (1)
crates/vm/src/types/structseq.rs (1)
  • struct_sequence_new (20-69)
crates/vm/src/vm/context.rs (1)
crates/derive-impl/src/pystructseq.rs (1)
  • n_unnamed_fields (58-63)
crates/derive-impl/src/pymodule.rs (1)
crates/derive-impl/src/pystructseq.rs (3)
  • inner (314-316)
  • class_name (320-342)
  • module (344-366)
crates/derive-impl/src/lib.rs (2)
crates/derive/src/lib.rs (2)
  • pystruct_sequence (235-239)
  • pystruct_sequence_data (264-268)
crates/derive-impl/src/pystructseq.rs (2)
  • impl_pystruct_sequence (406-494)
  • impl_pystruct_sequence_data (166-299)
crates/stdlib/src/resource.rs (3)
crates/derive/src/lib.rs (2)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
crates/derive-impl/src/pystructseq.rs (1)
  • module (344-366)
crates/derive-impl/src/util.rs (2)
  • module (429-458)
  • with (335-347)
crates/derive/src/lib.rs (1)
crates/derive-impl/src/util.rs (1)
  • with (335-347)
crates/vm/src/stdlib/time.rs (2)
crates/vm/src/types/structseq.rs (2)
  • struct_sequence_new (20-69)
  • Self (280-283)
crates/vm/src/vm/mod.rs (1)
  • new (116-219)
crates/vm/src/stdlib/sys.rs (6)
crates/vm/src/types/structseq.rs (2)
  • from_data (186-191)
  • Self (280-283)
crates/derive-impl/src/lib.rs (2)
  • pystruct_sequence_data (65-67)
  • pystruct_sequence (61-63)
crates/derive/src/lib.rs (2)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
crates/vm/src/stdlib/os.rs (3)
  • name (480-482)
  • try_from_object (127-130)
  • try_from_object (134-137)
crates/derive-impl/src/pystructseq.rs (1)
  • module (344-366)
crates/vm/src/stdlib/posix.rs (7)
  • try_from_object (282-290)
  • try_from_object (294-302)
  • try_from_object (783-787)
  • try_from_object (1268-1270)
  • try_from_object (1274-1276)
  • try_from_object (1912-1924)
  • try_from_object (2306-2321)
🔇 Additional comments (43)
crates/stdlib/src/grp.rs (4)

16-28: Clean separation of data and Python wrapper.

The new pattern correctly separates the data struct (GroupData) from the Python-exposed wrapper (PyGroup), with proper linkage via the data = "GroupData" attribute. This aligns well with the macro system approach for implementing Python functionality.


30-45: Correct conversion logic with graceful UTF-8 handling.

The cstr_lossy helper properly handles potential UTF-8 conversion failures with a lossy fallback. The field mappings correctly translate from the nix Group structure to GroupData.


47-83: Proper error handling and return type adaptation.

Both functions correctly return GroupData directly, with the macro-generated traits handling the conversion to Python objects. Error handling is appropriate:

  • KeyError for missing group entries with descriptive messages
  • Null byte validation in getgrnam prevents C string issues
  • Nix errors properly converted via IntoPyException

85-101: Thread-safe enumeration with proper unsafe handling.

Good implementation:

  • Static mutex correctly protects non-thread-safe setgrent/getgrent/endgrent calls
  • NonNull::new properly handles null pointer checks for iteration termination
  • Explicit .to_pyobject(vm) is correctly used here since the return type is Vec<PyObjectRef> rather than a data struct that gets auto-converted
crates/vm/src/vm/context.rs (1)

248-250: LGTM!

The new interned constant names (n_fields, n_sequence_fields, n_unnamed_fields) correctly support the PyStructSequence infrastructure. These align with CPython's struct sequence attributes for tracking field counts and visibility.

crates/vm/src/vm/mod.rs (1)

468-475: LGTM!

The switch from UnraisableHookArgs to UnraisableHookArgsData aligns with the data-centric refactoring pattern. The data struct will be automatically converted to a Python object when passed to unraisablehook.call() via the ToPyObject trait implementation generated by #[pystruct_sequence_data].

crates/vm/src/stdlib/pwd.rs (5)

19-34: LGTM!

The data-centric refactoring is well-structured:

  • PasswdData holds the raw field data with #[pystruct_sequence_data]
  • PyPasswd serves as the Python-facing wrapper with #[pystruct_sequence(name = "struct_passwd", module = "pwd", data = "PasswdData")]
  • The empty #[pyclass(with(PyStructSequence))] impl correctly delegates struct sequence behavior to the macro-generated code

This pattern is consistent with other refactored modules (grp, resource, os, sys).


36-58: LGTM!

The From<User> implementation correctly targets PasswdData now. The lossy string conversion helpers remain appropriate for handling non-UTF8 user data gracefully.


60-75: LGTM!

The return type change to PyResult<PasswdData> is correct. The macro-generated ToPyObject implementation handles conversion to Python objects automatically when the function result is used.


77-95: LGTM!

Consistent with getpwnam—returns PyResult<PasswdData> with automatic Python object conversion.


99-115: LGTM!

The explicit .to_pyobject(vm) call is necessary here since the function returns Vec<PyObjectRef>, requiring manual conversion of each PasswdData instance.

crates/stdlib/src/resource.rs (3)

66-90: LGTM!

The data-centric refactoring follows the established pattern:

  • RusageData with #[pystruct_sequence_data] contains all 16 rusage fields
  • PyRusage with #[pystruct_sequence(name = "struct_rusage", module = "resource", data = "RusageData")] provides the Python wrapper
  • Empty #[pyclass(with(PyStructSequence))] impl delegates to macro-generated behavior

The field types correctly match the libc rusage structure types (f64 for time values, libc::c_long for counters).


92-114: LGTM!

The From<libc::rusage> implementation is clean. The tv closure correctly converts timeval to f64 by combining seconds and microseconds.


116-133: LGTM!

Clean functional chain: res.map(RusageData::from) converts the raw rusage to the data struct, with appropriate error mapping for invalid input.

crates/vm/src/stdlib/sys.rs (16)

3-3: LGTM!

The public export of UnraisableHookArgsData aligns with the data-centric pattern—callers (like VirtualMachine::run_unraisable) construct the data struct directly.


452-458: LGTM!

The from_data pattern cleanly separates data construction from Python object creation. FlagsData::from_settings and FloatInfoData::INFO provide the data, and PyXxx::from_data() handles the conversion.


659-672: LGTM!

The Windows version handling correctly constructs WindowsVersionData with all required fields and uses PyWindowsVersion::from_data() for the final conversion.


674-741: LGTM!

The _unraisablehook and unraisablehook functions correctly accept UnraisableHookArgsData directly. The implementation logic remains unchanged—only the input type has been updated to match the data-centric pattern.


744-756: LGTM!

Consistent usage of from_data pattern for hash_info and int_info with their respective INFO constants.


765-765: LGTM!

Correctly accesses str_digits_check_threshold from IntInfoData::INFO for validation.


814-821: LGTM!

Consistent pattern for thread_info and version_info using their respective Data struct constants.


901-923: LGTM!

AsyncgenHooksData and PyAsyncgenHooks follow the established pattern. The get_asyncgen_hooks function returns AsyncgenHooksData directly with automatic Python conversion.


925-1003: LGTM!

The FlagsData struct with from_settings method cleanly encapsulates flag extraction from Settings. The no_attr option in #[pystruct_sequence(...)] and the slot_new implementation preventing direct instantiation are appropriate for this read-only system type.


1005-1030: LGTM!

ThreadInfoData and PyThreadInfo follow the pattern with INFO constant providing static thread configuration. The #[cfg(feature = "threading")] guards are correctly applied to both the data struct and wrapper.


1032-1067: LGTM!

FloatInfoData correctly captures all f64 limit constants. The INFO constant provides compile-time values matching Python's sys.float_info.


1069-1103: LGTM!

HashInfoData properly exposes hash algorithm parameters. The INFO constant correctly uses values from rustpython_common::hash.


1105-1126: LGTM!

IntInfoData correctly defines integer representation parameters including the str_digits_check_threshold used for validation in set_int_max_str_digits.


1128-1161: LGTM!

VersionInfoData and PyVersionInfo correctly expose version information. The Default derive and slot_new preventing instantiation are appropriate for this system type.


1163-1185: LGTM!

WindowsVersionData correctly captures all Windows version fields including the platform_version tuple. The #[cfg(windows)] guards are properly applied.


1187-1201: LGTM!

UnraisableHookArgsData with try_from_object attribute enables conversion from Python objects, which is needed when Python code calls unraisablehook directly. The fields correctly match CPython's UnraisableHookArgs.

crates/vm/src/types/mod.rs (1)

6-6: Struct sequence exports are consistent with new runtime API

Re-exporting PyStructSequence, PyStructSequenceData, and struct_sequence_new from structseq cleanly exposes the new struct-sequence runtime surface without changing behavior in this module.

crates/vm/src/stdlib/nt.rs (1)

178-210: get_terminal_size migration to TerminalSizeData looks correct

The function now returns _os::TerminalSizeData { columns, lines } instead of a PyTerminalSize wrapper, which matches the new data-backed struct-sequence pattern; the Windows logic and field values are otherwise unchanged.

crates/vm/src/stdlib/posix.rs (2)

1332-1341: uname returning UnameResultData aligns with data-struct pattern

Switching the return type to _os::UnameResultData and constructing it field-for-field from uname::uname() preserves behavior while fitting the new data-backed struct-sequence scheme.


1730-1747: POSIX get_terminal_size now returning TerminalSizeData is consistent

The function now returns _os::TerminalSizeData { columns, lines } instead of a PyTerminalSize wrapper, with identical ioctl and field handling. This matches the NT-side and the new struct-sequence Data pattern.

crates/derive-impl/src/lib.rs (1)

61-67: New pystruct_sequence* entry points are wired correctly

Both pystruct_sequence and pystruct_sequence_data mirror the existing pyclass/pymodule pattern, forwarding (attr, item) to pystructseq::{impl_pystruct_sequence, impl_pystruct_sequence_data} via result_to_tokens, so the derive-impl surface stays consistent.

crates/derive/src/lib.rs (1)

217-268: #[pystruct_sequence] / #[pystruct_sequence_data] macros and docs look good

The new attribute macros correctly parse attr as a Punctuated list and delegate to derive_impl::pystruct_sequence / pystruct_sequence_data, matching the style of pyclass/pymodule. The examples accurately reflect the intended “Data + Py type” usage and the try_from_object option.

crates/vm/src/types/structseq.rs (1)

16-69: Struct-sequence runtime design matches the intended Data + PyType model

struct_sequence_new, PyStructSequenceData, and PyStructSequence (with extend_pyclass) nicely encapsulate the CPython-style structseq behavior: visible vs hidden fields, n_sequence_fields / n_fields / n_unnamed_fields, class-level getters, and slot overrides for sequence/mapping/iter/hash/repr. The ToPyObject impl generated for Data structs will integrate cleanly with this API.

Also applies to: 151-325

crates/vm/src/stdlib/time.rs (3)

298-305: StructTimeData argument handling for asctime/ctime/strftime looks consistent

Using OptionalArg<StructTimeData>::naive_or_local and reusing it in asctime, ctime, and strftime cleanly centralizes “treat struct_time as local time” semantics and preserves the t-optional behaviour. The conversion via StructTimeData::to_date_time is straightforward and matches the existing float/seconds handling.

Also applies to: 343-374


329-338: mktime() ignores tm_gmtoff/tm_zone and relies solely on local time rules

mktime() now converts StructTimeData back to a NaiveDateTime via to_date_time() (using only the integer Y/M/D/h/m/s fields) and then feeds that into chrono::Local.from_local_datetime(..).single(). This matches CPython’s documented “interpret as local time” behaviour and correctly maps OverflowError when the local conversion fails.

One behavioural edge case is ambiguous/nonexistent local times around DST transitions: Local::from_local_datetime(...).single() will turn “ambiguous” or “nonexistent” into OverflowError. If we ever want closer parity with CPython’s use of C mktime() (which typically picks one of the candidates rather than erroring), we may need additional handling, but that’s likely out of scope for this refactor and can be revisited based on test results.

Please run the existing time.mktime tests (especially any DST-boundary cases) to confirm this still matches RustPython’s previous behaviour.

Also applies to: 524-540


543-552: PyStructTime wrapper and slot_new wiring look correct

The new PyStructTime wrapper is minimal and delegates construction to struct_sequence_new(cls, seq, vm), which respects n_sequence_fields/n_fields on the type and preserves cls (better for subclassing / future-proofing than the previous _cls-ignoring slot). The combination of #[pystruct_sequence(.., data = "StructTimeData")] and #[pyclass(with(PyStructSequence))] matches the intended macro pattern.

crates/vm/src/stdlib/os.rs (3)

738-835: StatResultData struct and stat()/PyStatResult wiring look internally consistent

The new StatResultData cleanly separates:

  • Core integer fields (st_mode, st_ino, ..., st_size) as named fields.
  • Integer seconds (st_atime_int, st_mtime_int, st_ctime_int) as #[pystruct(unnamed)] with FromArgs defaults.
  • Derived float times / nanosecond fields / st_reparse_tag as #[pystruct(skip)].

from_stat covers all supported platforms, normalises (sec, nsec) into both float seconds and nanoseconds, and gates st_reparse_tag behind #[cfg(windows)]. The updated stat() just returns StatResultData::from_stat(...).to_pyobject(vm), which will go through the new PyStatResult struct-sequence wrapper, and PyStatResult::slot_new preserves CPython-like os.stat_result((...)) flattening before binding via FromArgs.

Given the subtlety of os.stat_result’s sequence vs attribute fields, it’s worth running the existing os.stat/os.fstat tests (including indexing, named attributes, and platform-specific fields like *_ns and st_reparse_tag) to confirm layout parity, but structurally this refactor looks sound.

Also applies to: 928-940


1233-1317: TimesResultData and times() refactor are straightforward

On both Windows and Unix, times() now builds a plain TimesResultData with the expected five floats (user, system, children_user, children_system, elapsed) and returns it via to_pyobject(vm), relying on the #[pystruct_sequence(name = "times_result", module = "os", data = "TimesResultData")] + #[pyclass(with(PyStructSequence))] wiring to materialise the Python os.times_result struct sequence.

The platform-specific computations (FILETIME scaling on Windows; tms/sysconf(_SC_CLK_TCK) on Unix) remain unchanged and only the wrapping changed, so behaviour should be identical aside from the internal representation.

Please run the os.times-related tests to confirm repr/indexing and field names still match CPython.


1480-1508: TerminalSizeData and UnameResultData wrappers match the new struct-sequence pattern

TerminalSizeData and UnameResultData are now simple Rust data structs annotated with #[pystruct_sequence_data], paired with corresponding PyTerminalSize and PyUnameResult wrappers via #[pystruct_sequence(name = "...", module = "os", data = "...")] and #[pyclass(with(PyStructSequence))]. This aligns with the new macro model and keeps the Python-visible types (os.terminal_size, os.uname_result) thin while centralising their field layouts in the data structs.

Barring any pre-existing quirks in how these types are exposed from the module, the refactor looks good and should be functionally neutral.

Comment on lines +101 to +117
item: atomic_func!(|seq, i, vm| {
let n_seq = get_visible_len(seq.obj, vm)?;
let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
let idx = if i < 0 {
let pos_i = n_seq as isize + i;
if pos_i < 0 {
return Err(vm.new_index_error("tuple index out of range"));
}
pos_i as usize
} else {
i as usize
};
if idx >= n_seq {
return Err(vm.new_index_error("tuple index out of range"));
}
Ok(tuple[idx].clone())
}),
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

Clamp visible length to avoid panics if n_sequence_fields is mutated from Python

Several operations assume n_sequence_fields <= tuple.len() and index/slice using it directly:

  • STRUCT_SEQUENCE_AS_SEQUENCE.item: indexes tuple[idx] after only checking idx < n_seq.
  • STRUCT_SEQUENCE_AS_MAPPING.subscript: does tuple.as_slice()[..n_seq].
  • PyStructSequence::__getitem__: also slices zelf.as_slice()[..n_seq].
  • struct_sequence_richcompare: slices both zelf_tuple and other_tuple up to lengths derived from n_sequence_fields.

Because n_sequence_fields is exposed as a writable Python attribute, user code can do:

time.struct_time.n_sequence_fields = 10**9
time.gmtime()[0]  # or slicing / subscripting

and trigger an out-of-bounds slice or index, causing the Rust runtime to panic.

To harden this, treat n_sequence_fields as an upper bound and clamp it to the actual tuple length before slicing or indexing. For example:

 // in STRUCT_SEQUENCE_AS_SEQUENCE.item
-        item: atomic_func!(|seq, i, vm| {
-            let n_seq = get_visible_len(seq.obj, vm)?;
-            let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
+        item: atomic_func!(|seq, i, vm| {
+            let n_seq = get_visible_len(seq.obj, vm)?;
+            let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
+            let visible_len = n_seq.min(tuple.len());
             let idx = if i < 0 {
-                let pos_i = n_seq as isize + i;
+                let pos_i = visible_len as isize + i;
                 if pos_i < 0 {
                     return Err(vm.new_index_error("tuple index out of range"));
                 }
                 pos_i as usize
             } else {
                 i as usize
             };
-            if idx >= n_seq {
+            if idx >= visible_len {
                 return Err(vm.new_index_error("tuple index out of range"));
             }
             Ok(tuple[idx].clone())
         }),
 // in STRUCT_SEQUENCE_AS_MAPPING.subscript
-        subscript: atomic_func!(|mapping, needle, vm| {
-            let n_seq = get_visible_len(mapping.obj, vm)?;
-            let tuple = mapping.obj.downcast_ref::<PyTuple>().unwrap();
-            let visible_elements = &tuple.as_slice()[..n_seq];
+        subscript: atomic_func!(|mapping, needle, vm| {
+            let n_seq = get_visible_len(mapping.obj, vm)?;
+            let tuple = mapping.obj.downcast_ref::<PyTuple>().unwrap();
+            let visible_len = n_seq.min(tuple.len());
+            let visible_elements = &tuple.as_slice()[..visible_len];
 // in __getitem__
-        let n_seq = get_visible_len(zelf.as_ref(), vm)?;
-        let visible_elements = &zelf.as_slice()[..n_seq];
+        let n_seq = get_visible_len(zelf.as_ref(), vm)?;
+        let visible_len = n_seq.min(zelf.len());
+        let visible_elements = &zelf.as_slice()[..visible_len];
 // in struct_sequence_richcompare
-    let zelf_len = get_visible_len(zelf, vm)?;
-    // For other, try to get visible len; if it fails (not a struct sequence), use full length
-    let other_len = get_visible_len(other, vm).unwrap_or(other_tuple.len());
-
-    let zelf_visible = &zelf_tuple.as_slice()[..zelf_len];
-    let other_visible = &other_tuple.as_slice()[..other_len];
+    let zelf_len = get_visible_len(zelf, vm)?.min(zelf_tuple.len());
+    // For other, try to get visible len; if it fails (not a struct sequence), use full length
+    let other_len = match get_visible_len(other, vm) {
+        Ok(n) => n.min(other_tuple.len()),
+        Err(_) => other_tuple.len(),
+    };
+
+    let zelf_visible = &zelf_tuple.as_slice()[..zelf_len];
+    let other_visible = &other_tuple.as_slice()[..other_len];

This keeps semantics the same under normal invariants, but prevents user-modified n_sequence_fields from crashing the interpreter.

Also applies to: 134-147, 235-245, 359-385

🤖 Prompt for AI Agents
In crates/vm/src/types/structseq.rs around lines 101 to 117, the code uses
n_sequence_fields (via get_visible_len) directly for indexing/slicing which can
panic if Python mutates that value larger than the underlying tuple; clamp n_seq
to the tuple length before using it. Replace usages like let n_seq =
get_visible_len(...)? with let n_seq = get_visible_len(...)?; let n_seq =
n_seq.min(tuple.len()); (convert types as needed) and then use this clamped
n_seq for the idx bounds check and any slice ranges. Apply the same clamping fix
at the other mentioned ranges (134-147, 235-245, 359-385) so all indexing and
slicing use min(get_visible_len, tuple.len()) to avoid out-of-bounds panics.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 5, 2025

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 structseq

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: 0

Caution

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

⚠️ Outside diff range comments (1)
crates/derive-impl/src/pymodule.rs (1)

283-321: #[pyattr] cannot currently be combined with #[pystruct_sequence]

In attrs_to_module_items, only Class | Function | Exception are allowed to follow #[pyattr]. With the new AttrName::StructSequence, patterns like:

#[pyattr]
#[pystruct_sequence(...)]
struct PyStructTime;

will hit the “can follow #[pyattr]” error even though StructSequenceItem::gen_module_item supports py_attrs for custom names.

Consider allowing AttrName::StructSequence in that match arm and updating the error message accordingly:

-            match attr_name {
-                AttrName::Class | AttrName::Function | AttrName::Exception => {
+            match attr_name {
+                AttrName::Class | AttrName::Function | AttrName::Exception | AttrName::StructSequence => {
                     result.push(item_new(i, attr_name, py_attrs.clone()));
                 }
                 _ => {
                     bail_span!(
                         attr,
-                        "#[pyclass], #[pyfunction], or #[pyexception] can follow #[pyattr]",
+                        "#[pyclass], #[pyfunction], #[pyexception], or #[pystruct_sequence] can follow #[pyattr]",
                     )
                 }
             }
♻️ Duplicate comments (2)
crates/vm/src/stdlib/time.rs (1)

502-523: gmtime() still uses local timezone for tm_gmtoff/tm_zone instead of UTC

StructTimeData::new currently derives tm_gmtoff and tm_zone from chrono::Local for all callers:

fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
    let local_time = chrono::Local.from_local_datetime(&tm).unwrap();
    let offset_seconds =
        local_time.offset().local_minus_utc() + if isdst == 1 { 3600 } else { 0 };
    let tz_abbr = local_time.format("%Z").to_string();
    // ...
    tm_isdst: vm.ctx.new_int(isdst).into(),
    tm_gmtoff: vm.ctx.new_int(offset_seconds).into(),
    tm_zone: vm.ctx.new_str(tz_abbr).into(),
}

But gmtime() now calls StructTimeData::new(vm, instant, 0), so its struct_time reports the local offset/name instead of UTC. CPython’s time.gmtime() must return a UTC struct_time with:

  • tm_gmtoff == 0
  • tm_zone == "UTC" (or the platform’s canonical UTC name)
  • tm_isdst == 0

This is the same issue previously flagged and remains unresolved.

A minimal fix is to make StructTimeData::new distinguish UTC vs local based on isdst (or split into explicit constructors), e.g.:

 impl StructTimeData {
-    fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
-        let local_time = chrono::Local.from_local_datetime(&tm).unwrap();
-        let offset_seconds =
-            local_time.offset().local_minus_utc() + if isdst == 1 { 3600 } else { 0 };
-        let tz_abbr = local_time.format("%Z").to_string();
-
-        Self {
-            tm_year: vm.ctx.new_int(tm.year()).into(),
-            tm_mon: vm.ctx.new_int(tm.month()).into(),
-            tm_mday: vm.ctx.new_int(tm.day()).into(),
-            tm_hour: vm.ctx.new_int(tm.hour()).into(),
-            tm_min: vm.ctx.new_int(tm.minute()).into(),
-            tm_sec: vm.ctx.new_int(tm.second()).into(),
-            tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()).into(),
-            tm_yday: vm.ctx.new_int(tm.ordinal()).into(),
-            tm_isdst: vm.ctx.new_int(isdst).into(),
-            tm_gmtoff: vm.ctx.new_int(offset_seconds).into(),
-            tm_zone: vm.ctx.new_str(tz_abbr).into(),
-        }
-    }
+    fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
+        // For gmtime() we pass isdst = 0 and must report UTC.
+        let (offset_seconds, tz_abbr, tm_isdst) = if isdst == 0 {
+            (0, "UTC".to_owned(), 0)
+        } else {
+            let local_time = chrono::Local.from_local_datetime(&tm).unwrap();
+            let offset = local_time.offset().local_minus_utc()
+                + if isdst == 1 { 3600 } else { 0 };
+            let tz = local_time.format("%Z").to_string();
+            (offset, tz, isdst)
+        };
+
+        Self {
+            tm_year: vm.ctx.new_int(tm.year()).into(),
+            tm_mon: vm.ctx.new_int(tm.month()).into(),
+            tm_mday: vm.ctx.new_int(tm.day()).into(),
+            tm_hour: vm.ctx.new_int(tm.hour()).into(),
+            tm_min: vm.ctx.new_int(tm.minute()).into(),
+            tm_sec: vm.ctx.new_int(tm.second()).into(),
+            tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()).into(),
+            tm_yday: vm.ctx.new_int(tm.ordinal()).into(),
+            tm_isdst: vm.ctx.new_int(tm_isdst).into(),
+            tm_gmtoff: vm.ctx.new_int(offset_seconds).into(),
+            tm_zone: vm.ctx.new_str(tz_abbr).into(),
+        }
+    }

With this change:

  • gmtime() (which passes isdst = 0) will report UTC offset/name.
  • localtime() (currently passing isdst = -1) continues to use the local timezone path as before.

Also applies to: 309-315

crates/vm/src/types/structseq.rs (1)

101-117: Clamp visible length to tuple length to avoid panics when n_sequence_fields is mutated

get_visible_len reads n_sequence_fields from the Python type, and several places use it directly to index/slice the underlying PyTuple:

  • STRUCT_SEQUENCE_AS_SEQUENCE.item indexes tuple[idx] after checking idx < n_seq.
  • STRUCT_SEQUENCE_AS_MAPPING.subscript slices tuple.as_slice()[..n_seq].
  • PyStructSequence::__getitem__ slices zelf.as_slice()[..n_seq].
  • struct_sequence_richcompare slices both tuples up to lengths derived from n_sequence_fields.

Because n_sequence_fields is a normal, writable Python attribute set in extend_pyclass, user code can do:

time.struct_time.n_sequence_fields = 10**9
time.gmtime(0)[0]  # or subscripting/slicing

and cause an out‑of‑bounds slice/index panic in Rust when n_sequence_fields > tuple.len().

Please clamp the “visible length” to the actual tuple length before using it for indexing/slicing. For example:

-        item: atomic_func!(|seq, i, vm| {
-            let n_seq = get_visible_len(seq.obj, vm)?;
-            let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
+        item: atomic_func!(|seq, i, vm| {
+            let n_seq = get_visible_len(seq.obj, vm)?;
+            let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
+            let visible_len = n_seq.min(tuple.len());
             let idx = if i < 0 {
-                let pos_i = n_seq as isize + i;
+                let pos_i = visible_len as isize + i;
                 if pos_i < 0 {
                     return Err(vm.new_index_error("tuple index out of range"));
                 }
                 pos_i as usize
             } else {
                 i as usize
             };
-            if idx >= n_seq {
+            if idx >= visible_len {
                 return Err(vm.new_index_error("tuple index out of range"));
             }
             Ok(tuple[idx].clone())
         }),
-        subscript: atomic_func!(|mapping, needle, vm| {
-            let n_seq = get_visible_len(mapping.obj, vm)?;
-            let tuple = mapping.obj.downcast_ref::<PyTuple>().unwrap();
-            let visible_elements = &tuple.as_slice()[..n_seq];
+        subscript: atomic_func!(|mapping, needle, vm| {
+            let n_seq = get_visible_len(mapping.obj, vm)?;
+            let tuple = mapping.obj.downcast_ref::<PyTuple>().unwrap();
+            let visible_len = n_seq.min(tuple.len());
+            let visible_elements = &tuple.as_slice()[..visible_len];
-    fn __getitem__(zelf: PyRef<PyTuple>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
-        let n_seq = get_visible_len(zelf.as_ref(), vm)?;
-        let visible_elements = &zelf.as_slice()[..n_seq];
+    fn __getitem__(zelf: PyRef<PyTuple>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
+        let n_seq = get_visible_len(zelf.as_ref(), vm)?;
+        let visible_len = n_seq.min(zelf.len());
+        let visible_elements = &zelf.as_slice()[..visible_len];
-    let zelf_len = get_visible_len(zelf, vm)?;
-    // For other, try to get visible len; if it fails (not a struct sequence), use full length
-    let other_len = get_visible_len(other, vm).unwrap_or(other_tuple.len());
-
-    let zelf_visible = &zelf_tuple.as_slice()[..zelf_len];
-    let other_visible = &other_tuple.as_slice()[..other_len];
+    let zelf_len = get_visible_len(zelf, vm)?.min(zelf_tuple.len());
+    // For other, try to get visible len; if it fails (not a struct sequence), use full length
+    let other_len = match get_visible_len(other, vm) {
+        Ok(n) => n.min(other_tuple.len()),
+        Err(_) => other_tuple.len(),
+    };
+
+    let zelf_visible = &zelf_tuple.as_slice()[..zelf_len];
+    let other_visible = &other_tuple.as_slice()[..other_len];

This preserves existing behavior under normal invariants while preventing user‑level mutations of n_sequence_fields from crashing the VM.

Also applies to: 137-147, 235-245, 373-385

🧹 Nitpick comments (2)
crates/derive-impl/src/pymodule.rs (1)

626-730: Minor: duplicate parsing of #[pystruct_sequence] and missing module = false handling

StructSequenceItem::gen_module_item reparses #[pystruct_sequence] by hand to extract name, module, and no_attr, even though PyStructSequenceMeta in pystructseq.rs already encapsulates this logic. It also ignores the module = false case (treated as “no module” by PyStructSequenceMeta), defaulting module_name to the surrounding module instead.

To avoid drift between the macro and pymodule views of the same attribute, consider:

  • Reusing PyStructSequenceMeta (or a shared helper) here rather than open‑coding the parsing.
  • Honoring module = false by allowing module_name to be None and skipping the __module__ set, if that form is intended to represent builtin/module‑less types.
crates/derive-impl/src/pystructseq.rs (1)

81-139: Tighten validation for per‑field #[pystruct_sequence(...)] usage

Two edge cases could be worth guarding against at macro time:

  1. Mixed NamedUnnamedNamed order

    Because into_tuple emits all visible fields in declaration order and PyStructSequence::slot_repr zips the first REQUIRED_FIELD_NAMES.len() tuple elements with REQUIRED_FIELD_NAMES, placing an #[pystruct_sequence(unnamed)] field before a later named field would misalign names and values in __repr__ and getters.

  2. Conflicting per‑field options

    A field marked with both skip and unnamed silently becomes Skipped (skip wins), which is surprising.

You could reject these at compile time, e.g.:

fn parse_fields(input: &mut DeriveInput) -> Result<FieldInfo> {
    // ...
-    for field in &mut fields.named {
+    let mut seen_unnamed = false;
+    for field in &mut fields.named {
         let mut skip = false;
         let mut unnamed = false;
         // ...
             for ident in idents {
                 match ident.to_string().as_str() {
                     "skip" => {
                         skip = true;
                     }
                     "unnamed" => {
                         unnamed = true;
                     }
                     _ => {
                         bail_span!(ident, "Unknown item for #[pystruct_sequence(...)]")
                     }
                 }
             }
         // ...
-        let kind = if skip {
-            FieldKind::Skipped
-        } else if unnamed {
-            FieldKind::Unnamed
-        } else {
-            FieldKind::Named
-        };
+        if skip && unnamed {
+            bail_span!(field, "field cannot be both `skip` and `unnamed` in #[pystruct_sequence(...)]");
+        }
+        let kind = if skip {
+            FieldKind::Skipped
+        } else if unnamed {
+            seen_unnamed = true;
+            FieldKind::Unnamed
+        } else {
+            if seen_unnamed {
+                bail_span!(field, "named fields must not follow `#[pystruct_sequence(unnamed)]` fields");
+            }
+            FieldKind::Named
+        };

This keeps the macro behavior predictable and aligned with how repr/getters interpret the tuple layout.

Also applies to: 210-253

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1347f6e and 736b8bd.

⛔ Files ignored due to path filters (3)
  • Lib/test/datetimetester.py is excluded by !Lib/**
  • Lib/test/test_logging.py is excluded by !Lib/**
  • Lib/test/test_structseq.py is excluded by !Lib/**
📒 Files selected for processing (16)
  • crates/derive-impl/src/lib.rs (1 hunks)
  • crates/derive-impl/src/pymodule.rs (7 hunks)
  • crates/derive-impl/src/pystructseq.rs (3 hunks)
  • crates/derive/src/lib.rs (1 hunks)
  • crates/stdlib/src/grp.rs (5 hunks)
  • crates/stdlib/src/resource.rs (4 hunks)
  • crates/vm/src/stdlib/nt.rs (2 hunks)
  • crates/vm/src/stdlib/os.rs (9 hunks)
  • crates/vm/src/stdlib/posix.rs (3 hunks)
  • crates/vm/src/stdlib/pwd.rs (7 hunks)
  • crates/vm/src/stdlib/sys.rs (21 hunks)
  • crates/vm/src/stdlib/time.rs (7 hunks)
  • crates/vm/src/types/mod.rs (1 hunks)
  • crates/vm/src/types/structseq.rs (4 hunks)
  • crates/vm/src/vm/context.rs (1 hunks)
  • crates/vm/src/vm/mod.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/vm/src/vm/context.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Follow the default rustfmt code style by running cargo fmt to format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • crates/derive-impl/src/pymodule.rs
  • crates/vm/src/stdlib/pwd.rs
  • crates/vm/src/stdlib/nt.rs
  • crates/derive-impl/src/lib.rs
  • crates/stdlib/src/grp.rs
  • crates/vm/src/types/mod.rs
  • crates/derive/src/lib.rs
  • crates/stdlib/src/resource.rs
  • crates/vm/src/stdlib/posix.rs
  • crates/derive-impl/src/pystructseq.rs
  • crates/vm/src/types/structseq.rs
  • crates/vm/src/vm/mod.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/time.rs
  • crates/vm/src/stdlib/sys.rs
🧠 Learnings (6)
📓 Common learnings
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In RustPython's pattern matching implementation, only certain builtin types should have the SEQUENCE flag: list and tuple are confirmed sequences. The user youknowone indicated that bytes, bytearray are not considered sequences in this context, even though they implement sequence-like protocols.
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In RustPython's pattern matching implementation, only certain builtin types should have the SEQUENCE flag: list and tuple are confirmed sequences. The user youknowone indicated that bytes, bytearray are not considered sequences in this context, even though they implement sequence-like protocols.

Applied to files:

  • crates/vm/src/types/mod.rs
  • crates/vm/src/types/structseq.rs
  • crates/vm/src/stdlib/sys.rs
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.

Applied to files:

  • crates/vm/src/types/mod.rs
  • crates/vm/src/types/structseq.rs
  • crates/vm/src/stdlib/sys.rs
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust

Applied to files:

  • crates/derive/src/lib.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/time.rs
  • crates/vm/src/stdlib/sys.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration rather than providing fallback values for other platforms.

Applied to files:

  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/sys.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration using #ifdef checks rather than providing fallback values for other platforms.

Applied to files:

  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/sys.rs
🧬 Code graph analysis (7)
crates/derive-impl/src/pymodule.rs (1)
crates/derive-impl/src/pystructseq.rs (3)
  • inner (316-318)
  • class_name (322-344)
  • module (346-368)
crates/vm/src/stdlib/pwd.rs (3)
crates/derive-impl/src/lib.rs (3)
  • pystruct_sequence_data (65-67)
  • pystruct_sequence (61-63)
  • pyclass (41-47)
crates/derive/src/lib.rs (3)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
  • pyclass (124-128)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/stdlib/src/grp.rs (2)
crates/derive/src/lib.rs (3)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
  • pyclass (124-128)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/vm/src/types/mod.rs (1)
crates/vm/src/types/structseq.rs (1)
  • struct_sequence_new (20-69)
crates/derive/src/lib.rs (1)
crates/derive-impl/src/lib.rs (2)
  • pystruct_sequence (61-63)
  • pystruct_sequence_data (65-67)
crates/stdlib/src/resource.rs (2)
crates/derive/src/lib.rs (3)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
  • pyclass (124-128)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/vm/src/stdlib/time.rs (1)
crates/vm/src/types/structseq.rs (1)
  • struct_sequence_new (20-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Check Rust code with clippy
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Ensure compilation on various targets
🔇 Additional comments (42)
crates/vm/src/vm/mod.rs (1)

468-475: LGTM!

The migration to UnraisableHookArgsData correctly follows the new data-driven architecture. The struct is constructed with all required fields and passed to the unraisable hook, which will convert it to a Python object via the ToPyObject implementation provided by the #[pystruct_sequence_data] macro.

crates/stdlib/src/grp.rs (5)

16-28: LGTM! Clean separation of data and wrapper types.

The new pattern correctly separates the data struct (GroupData with #[pystruct_sequence_data]) from the Python-visible wrapper (PyGroup with #[pystruct_sequence]). The attribute configuration data = "GroupData" properly links them.


30-45: LGTM!

The from_unistd_group method is correctly placed on GroupData and produces the data struct directly.


47-66: LGTM!

Return type change to PyResult<GroupData> is correct. The framework will automatically convert the data struct to a Python object via the generated ToPyObject implementation.


68-83: LGTM!

Consistent with getgrgid - returns PyResult<GroupData> with proper conversion.


85-101: LGTM!

For getgrall, the explicit .to_pyobject(vm) call is correctly used since the function returns Vec<PyObjectRef> rather than a single data struct.

crates/stdlib/src/resource.rs (3)

66-90: LGTM! Correct data-driven struct sequence implementation.

The RUsageData and PyRUsage pattern follows the same architecture as other migrated types. Using From<libc::rusage> is idiomatic and clean.


92-114: LGTM!

The From implementation correctly maps all 16 rusage fields and handles the timeval-to-f64 conversion inline with the helper closure.


116-133: LGTM!

The getrusage function cleanly uses .map(RUsageData::from) to convert the raw rusage struct, maintaining the same error handling logic.

crates/vm/src/types/mod.rs (1)

6-6: LGTM!

The expanded exports correctly expose PyStructSequenceData (the trait for data structs) and struct_sequence_new (the constructor function) which are required for the new attribute-based macro system.

crates/vm/src/stdlib/pwd.rs (5)

19-34: LGTM! Consistent with the data-driven architecture.

The PasswdData and PyPasswd pattern matches the structure used in grp.rs and resource.rs, maintaining consistency across the codebase.


36-58: LGTM!

The From<User> implementation handles CString and PathBuf conversions gracefully with lossy fallbacks, which is appropriate for system data that may contain non-UTF8 sequences.


60-75: LGTM!

Return type correctly changed to PyResult<PasswdData>.


77-95: LGTM!

Consistent with getpwnam - returns PyResult<PasswdData>.


97-115: LGTM!

For getpwall, the explicit .to_pyobject(vm) is correctly used since the function returns Vec<PyObjectRef>.

crates/vm/src/stdlib/posix.rs (2)

1333-1341: UnameResult now cleanly forwards all uname fields into data struct

The change to return _os::UnameResultData and populate all five fields (including version and machine) looks correct and keeps error handling intact.


1730-1747: TerminalSizeData return type matches new data‑driven structseq pattern

Switching get_terminal_size to return _os::TerminalSizeData { columns, lines } preserves behavior while aligning with the new data‑backed struct sequence design.

crates/vm/src/stdlib/nt.rs (1)

178-210: NT get_terminal_size correctly migrated to TerminalSizeData

The function now returns _os::TerminalSizeData { columns, lines } with unchanged Windows console logic, which is consistent with the new struct sequence data model.

crates/derive-impl/src/pymodule.rs (1)

15-22: StructSequence integration into pymodule looks consistent with existing item handling

AttrName::StructSequence, its Display/FromStr wiring, module_item_new dispatch, and StructSequenceItem’s ContentItem impl all follow the existing patterns for functions, attrs, and classes and should integrate cleanly with the module item pipeline.

Also applies to: 24-32, 37-47, 222-245, 412-448

crates/derive-impl/src/lib.rs (1)

61-67: New pystruct_sequence_ forwarding functions align with existing macro plumbing*

Both pystruct_sequence and pystruct_sequence_data mirror the pattern used by pyclass/pyexception, cleanly delegating to pystructseq::impl_pystruct_sequence{,_data} via result_to_tokens.

crates/derive/src/lib.rs (2)

217-239: Attribute macro pystruct_sequence is wired correctly and documentation matches usage

The new #[proc_macro_attribute] pub fn pystruct_sequence(attr, item) mirrors pyclass/pymodule parsing (Punctuated meta + Item) and forwards to derive_impl::pystruct_sequence, with doc examples that align with the data + wrapper pattern.


241-268: pystruct_sequence_data macro and options are consistent with impl side

The pystruct_sequence_data attribute macro correctly parses its meta (try_from_object) and item, then delegates to derive_impl::pystruct_sequence_data. The docstring describes field‑level #[pystruct_sequence(skip)] and the try_from_object option in a way that matches the implementation in pystructseq.rs.

crates/derive-impl/src/pystructseq.rs (2)

10-32: Data‑oriented pystruct_sequence_data implementation looks solid

The new FieldKind/FieldInfo model, parse_fields, and impl_pystruct_sequence_data together provide a clear separation of:

  • visible named fields (REQUIRED_FIELD_NAMES)
  • visible unnamed fields (UNNAMED_FIELDS_LEN)
  • hidden/skipped fields (OPTIONAL_FIELD_NAMES),

and generate appropriate index constants, into_tuple, and an optional TryFromObject/try_from_elements path when try_from_object is requested. This matches the intended struct‑sequence data semantics and composes cleanly with the runtime PyStructSequenceData trait.

Also applies to: 33-65, 166-233, 255-301


305-396: Wrapper generation for #[pystruct_sequence] matches runtime expectations

impl_pystruct_sequence correctly:

  • enforces use on a struct and requires name and data = "DataType" in the attribute,
  • maps name/module into PyClassDef::NAME, MODULE_NAME, and TP_NAME,
  • makes the Python type a PyTuple subclass via type Base = PyTuple,
  • implements StaticType, MaybeTraverse, and PyStructSequence<Data = #data_ident>,
  • and provides a ToPyObject impl for the data struct that calls PyStructSequence::from_data.

This lines up with the new runtime APIs in types::structseq and gives a clean, attribute‑driven way to bind data structs to Python-visible struct sequence types.

Also applies to: 409-495

crates/vm/src/stdlib/time.rs (1)

34-41: StructTimeData + PyStructTime integration with the new struct sequence runtime looks good

  • StructTimeData is annotated with #[pystruct_sequence_data(try_from_object)], matching the new derive‑impl; fields and skips (tm_gmtoff, tm_zone) align with _STRUCT_TM_ITEMS = 11.
  • gmtime, localtime, mktime, asctime, and strftime now take/return StructTimeData and rely on ToPyObject to produce/consume the Python struct_time, which is wrapped by PyStructTime.
  • PyStructTime is declared with #[pystruct_sequence(name = "struct_time", module = "time", data = "StructTimeData")] and #[pyclass(with(PyStructSequence))], and slot_new delegates to struct_sequence_new, so construction and length validation reuse the common runtime path.

This is a clean application of the new data‑driven struct sequence infrastructure.

Also applies to: 298-305, 309-338, 343-374, 478-553

crates/vm/src/stdlib/sys.rs (10)

451-454: LGTM!

The migration to PyFlags::from_data(FlagsData::from_settings(...), vm) pattern cleanly separates data construction from Python object creation.


674-741: LGTM!

The _unraisablehook and unraisablehook functions now correctly accept UnraisableHookArgsData directly. The function body accesses fields consistently with the data struct pattern.


901-923: LGTM!

Clean implementation of AsyncgenHooksData with #[pystruct_sequence_data] and the corresponding PyAsyncgenHooks wrapper. The get_asyncgen_hooks function correctly returns AsyncgenHooksData.


925-1003: LGTM!

The FlagsData struct and PyFlags wrapper follow the new data-driven pattern correctly. The from_settings method is properly placed on the data struct, and the slot_new implementation correctly prevents instantiation.


1005-1031: LGTM!

Consistent pattern applied to ThreadInfoData and PyThreadInfo with proper #[cfg(feature = "threading")] gating on both the data struct and wrapper.


1032-1068: LGTM!

FloatInfoData and PyFloatInfo correctly implement the data-driven pattern with the INFO constant properly placed on the data struct.


1069-1127: LGTM!

Both HashInfoData/PyHashInfo and IntInfoData/PyIntInfo follow the established pattern consistently. The INFO constants are correctly placed on the data structs.


1128-1161: Visibility consistency: VersionInfoData and PyVersionInfo are pub while others are pub(super).

These types are pub (lines 1130, 1149) while similar types like FlagsData, ThreadInfoData, etc., use pub(super). This appears intentional since VersionInfoData may need broader exposure, but verify this is the intended visibility.


1163-1186: LGTM!

Windows-specific WindowsVersionData and PyWindowsVersion correctly implement the data-driven pattern with proper #[cfg(windows)] gating.


1187-1201: Verify try_from_object attribute behavior.

The #[pystruct_sequence_data(try_from_object)] attribute on UnraisableHookArgsData (line 1188) differs from other data structs. Ensure this generates the expected TryFromObject implementation for use in function parameter binding.

crates/vm/src/stdlib/os.rs (7)

837-867: LGTM!

PyStatResult wrapper correctly references StatResultData and the slot_new implementation properly binds args to StatResultData before conversion. The flattening logic for nested tuples is preserved.


936-940: LGTM!

The stat function correctly uses StatResultData::from_stat(&stat, vm).to_pyobject(vm) to return the result, following the data-driven pattern.


1233-1251: LGTM!

TimesResultData and PyTimesResult correctly implement the data-driven pattern with proper #[cfg(all(any(unix, windows), not(target_os = "redox")))] gating applied consistently to both the data struct and wrapper.


1279-1316: LGTM!

The times function correctly constructs TimesResultData on both Windows and Unix paths and converts to PyObject via to_pyobject(vm).


1480-1491: LGTM!

TerminalSizeData and PyTerminalSize follow the pattern correctly. The pub(crate) visibility is appropriate for cross-module usage within the crate.


1493-1507: LGTM!

UnameResultData and PyUnameResult correctly implement the data-driven pattern with pub(crate) visibility for cross-module access.


738-835: Verify macro compatibility for StatResultData attribute combination.

StatResultData combines #[derive(Debug, FromArgs)] with #[pystruct_sequence_data] and uses field-level attributes #[pystruct_sequence(unnamed)] and #[pystruct_sequence(skip)]. The unnamed fields (lines 750-757) participate in the tuple representation without named accessors, while skip fields (lines 760-780) are excluded entirely. Verify that the pystruct_sequence_data derive macro correctly handles this mixed-attribute configuration alongside FromArgs.

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: 1

♻️ Duplicate comments (2)
crates/vm/src/types/structseq.rs (1)

101-117: Clamp visible length to prevent panics if n_sequence_fields is mutated from Python

The n_sequence_fields attribute is accessible from Python and could be modified to a value larger than the actual tuple length. Several operations use n_seq directly for indexing/slicing without clamping:

  • STRUCT_SEQUENCE_AS_SEQUENCE.item (line 116): tuple[idx]
  • STRUCT_SEQUENCE_AS_MAPPING.subscript (line 139): tuple.as_slice()[..n_seq]
  • __getitem__ (line 238): zelf.as_slice()[..n_seq]
  • struct_sequence_richcompare (lines 377-378): slices both tuples

Apply clamping like n_seq.min(tuple.len()) before using the value:

 // Example fix for item:
 item: atomic_func!(|seq, i, vm| {
     let n_seq = get_visible_len(seq.obj, vm)?;
     let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
+    let n_seq = n_seq.min(tuple.len());
     let idx = if i < 0 {

Also applies to: 134-147, 235-245, 359-385

crates/vm/src/stdlib/time.rs (1)

309-315: gmtime() uses local timezone info instead of UTC for tm_gmtoff/tm_zone

StructTimeData::new() derives tm_gmtoff and tm_zone from chrono::Local for all cases. For gmtime(), CPython returns tm_gmtoff = 0 and tm_zone = "UTC".

Split into separate constructors:

+impl StructTimeData {
+    fn new_utc(vm: &VirtualMachine, tm: NaiveDateTime) -> Self {
+        Self {
+            tm_year: vm.ctx.new_int(tm.year()).into(),
+            // ... other fields ...
+            tm_isdst: vm.ctx.new_int(0).into(),
+            tm_gmtoff: vm.ctx.new_int(0).into(),
+            tm_zone: vm.ctx.new_str("UTC").into(),
+        }
+    }
+
+    fn new_local(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
+        // existing implementation
+    }
+}

Then use new_utc() in gmtime() and new_local() in localtime().

🧹 Nitpick comments (9)
crates/vm/src/stdlib/sys.rs (3)

451-459: Struct-sequence data/wrapper pattern for sys metadata looks consistent

The new *_Data + Py* pairs for flags, float_info, hash_info, int_info, thread_info, version_info, and asyncgen_hooks, together with the updated accessors (e.g. PyFlags::from_data(FlagsData::from_settings(...), vm)), form a clean, uniform pattern. Field layouts and types look appropriate for the corresponding CPython struct sequences, and centralizing constants in *_Data::INFO / FlagsData::from_settings will make future changes less error‑prone. Nice use of the #[pystruct_sequence] / #[pystruct_sequence_data] macros as per the macro-system guidelines.

Also applies to: 743-756, 812-821, 863-924, 929-1128


624-672: getwindowsversion() struct-sequence wiring looks correct; name nit (optional)

Populating WindowsVersionData from both OSVERSIONINFOEXW and get_kernel32_version() and returning it via PyWindowsVersion::from_data(winver, vm) matches the expected (major, minor, build, platform, service_pack, ..., platform_version) layout, and using (u32, u32, u32) for platform_version will convert cleanly to a Python tuple. If you’re aiming for exact CPython parity, you might consider naming the struct-sequence type getwindowsversion_result instead of getwindowsversion, but that’s cosmetic and non‑blocking.

Also applies to: 1164-1187


674-731: UnraisableHookArgsData + __unraisablehook__: minor type/traceback nits

The move to UnraisableHookArgsData/PyUnraisableHookArgs with try_from_object and a default __unraisablehook__ implemented in Rust fits well with the new struct-sequence model and keeps VM ↔ sys coupling clean. A couple of small follow‑ups you might consider:

  • UnraisableHookArgsData.exc_type is a PyTypeRef, but _unraisablehook still checks vm.is_none(unraisable.exc_type.as_object()). With the current type, that branch is effectively dead; either drop the None check or relax the field type (e.g. PyObjectRef or Option<PyTypeRef>) if you want to accept exc_type being None like CPython sometimes allows.
  • The TODO around traceback printing currently calls traceback.print_stack() and ignores unraisable.exc_traceback. When you revisit this, switching to traceback.print_exception(unraisable.exc_type, unraisable.exc_value, unraisable.exc_traceback) would be closer to CPython’s behavior while still using PyStderr.

Functionally this is fine as-is, so these are non‑blocking cleanups.

Also applies to: 1188-1203

crates/vm/src/vm/mod.rs (1)

460-477: run_unraisable now using UnraisableHookArgsData is correct; robustness nits

Constructing stdlib::sys::UnraisableHookArgsData here with (exc_type, exc_value, exc_traceback, err_msg, object) matches the new struct-sequence definition and keeps the VM side data‑oriented, which is nice.

Two non‑blocking robustness tweaks you might consider:

  • Both import("sys", 0).unwrap() and get_attr("unraisablehook", ...) .unwrap() will panic if sys or the hook are missing (e.g. during odd teardown paths). Falling back to a minimal stderr print instead of panicking could make this path more resilient.
  • On hook failure you currently println! the exception repr to stdout; using sys::PyStderr(self) instead would align with how other unraisable/exit paths report errors.

Behavior is otherwise unchanged and looks good.

crates/derive/src/lib.rs (1)

241-268: #[pystruct_sequence_data] macro wiring matches its documentation

The pystruct_sequence_data attribute macro correctly parses arguments and delegates to derive_impl::pystruct_sequence_data. The doc comments about generating field index constants, into_tuple(), and optional try_from_object are consistent with the intended behavior in the derive-impl crate (modulo the tuple/skip detail noted in pystructseq.rs).

crates/derive-impl/src/pystructseq.rs (4)

10-144: Field parsing and classification are solid; consider tightening edge cases

The FieldKind/ParsedField/FieldInfo model and parse_fields logic correctly:

  • Restrict #[pystruct_sequence_data] to named-field structs.
  • Parse per-field #[pystruct_sequence(skip)] and #[pystruct_sequence(unnamed)] markers.
  • Strip those attributes from the emitted struct while retaining other attributes.

Two small follow‑ups you might consider:

  • Right now, a field with both skip and unnamed silently becomes Skipped. That’s probably not a meaningful combination; explicitly rejecting it with a diagnostic would make misuse more obvious.
  • attr.parse_meta() failures are silently ignored. If that can ever happen for well‑formed attributes, surfacing an error instead of continue would make debugging easier.

These are not blockers but would tighten the developer experience.


209-253: Guard against panics in try_from_elements when called directly

The generated try_from_elements implementation relies on the caller ensuring there are at least visible_fields.len() elements:

let mut iter = elements.into_iter();
Ok(Self {
    #(#visible_fields: iter.next().unwrap().clone().try_into_value(vm)?,)*
    #(#skipped_fields: match iter.next() {
        Some(v) => v.clone().try_into_value(vm)?,
        None => vm.ctx.none(),
    },)*
})

When used via the generated TryFromObject impl, you do check seq.len() < n_required first, so in that path unwrap() is safe. However, try_from_elements is part of the public PyStructSequenceData trait and can be called directly; in that case a too-short elements vector will panic.

If you want this to be more robust, consider turning those unwrap()s into Python TypeErrors instead of panicking, e.g. by matching and returning an error when iter.next() is None. Not strictly required for this PR to function, but it would make the trait safer to use in other call sites.


305-400: PyStructSequenceMeta wiring is consistent; consider future-proofing the data parameter

The PyStructSequenceMeta + ItemMeta implementation:

  • Restricts allowed keys to "name", "module", "data", and "no_attr".
  • Parses name and module strictly as string literals.
  • Parses data as a string literal and turns it into an Ident via format_ident!, with a clear error if data is missing.

That’s sufficient for the current API, where the example uses data = "StructTimeData" in the same module. If you later find you need cross-module data types, you might want to allow a path expression for data instead of only a bare string, but that can be an incremental enhancement.


402-500: Type-level impl_pystruct_sequence generation looks correct and matches the new macro docs

impl_pystruct_sequence:

  • Enforces that the attribute is on a struct.
  • Re-parses the attribute via PyStructSequenceMeta, requiring name and data while making module optional.
  • Re-emits the Python type as an empty struct with the original visibility (as per the macro docs, the data lives in a separate Data struct).
  • Implements PyClassDef with Base = PyTuple, StaticType, and a trivial MaybeTraverse, which is appropriate for an empty wrapper.
  • Implements the PyStructSequence trait binding the wrapper type to the generated Data type.
  • Implements ToPyObject for the Data type in terms of PyStructSequence::from_data, so data instances naturally convert to the corresponding Python struct-sequence objects.

This design cleanly matches the attribute macro documentation in crates/derive/src/lib.rs and should integrate with the existing VM-side PyStructSequence / PyStructSequenceData traits.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 736b8bd and c6f53fb.

⛔ Files ignored due to path filters (3)
  • Lib/test/datetimetester.py is excluded by !Lib/**
  • Lib/test/test_logging.py is excluded by !Lib/**
  • Lib/test/test_structseq.py is excluded by !Lib/**
📒 Files selected for processing (16)
  • crates/derive-impl/src/lib.rs (1 hunks)
  • crates/derive-impl/src/pymodule.rs (9 hunks)
  • crates/derive-impl/src/pystructseq.rs (3 hunks)
  • crates/derive/src/lib.rs (1 hunks)
  • crates/stdlib/src/grp.rs (5 hunks)
  • crates/stdlib/src/resource.rs (4 hunks)
  • crates/vm/src/stdlib/nt.rs (2 hunks)
  • crates/vm/src/stdlib/os.rs (9 hunks)
  • crates/vm/src/stdlib/posix.rs (3 hunks)
  • crates/vm/src/stdlib/pwd.rs (7 hunks)
  • crates/vm/src/stdlib/sys.rs (21 hunks)
  • crates/vm/src/stdlib/time.rs (7 hunks)
  • crates/vm/src/types/mod.rs (1 hunks)
  • crates/vm/src/types/structseq.rs (4 hunks)
  • crates/vm/src/vm/context.rs (1 hunks)
  • crates/vm/src/vm/mod.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/vm/src/types/mod.rs
  • crates/vm/src/vm/context.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Follow the default rustfmt code style by running cargo fmt to format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • crates/stdlib/src/grp.rs
  • crates/derive-impl/src/pymodule.rs
  • crates/derive/src/lib.rs
  • crates/stdlib/src/resource.rs
  • crates/vm/src/stdlib/nt.rs
  • crates/vm/src/stdlib/posix.rs
  • crates/vm/src/stdlib/pwd.rs
  • crates/derive-impl/src/lib.rs
  • crates/derive-impl/src/pystructseq.rs
  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/vm/mod.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/types/structseq.rs
  • crates/vm/src/stdlib/time.rs
🧠 Learnings (6)
📓 Common learnings
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In RustPython's pattern matching implementation, only certain builtin types should have the SEQUENCE flag: list and tuple are confirmed sequences. The user youknowone indicated that bytes, bytearray are not considered sequences in this context, even though they implement sequence-like protocols.
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust

Applied to files:

  • crates/derive/src/lib.rs
  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/time.rs
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In RustPython's pattern matching implementation, only certain builtin types should have the SEQUENCE flag: list and tuple are confirmed sequences. The user youknowone indicated that bytes, bytearray are not considered sequences in this context, even though they implement sequence-like protocols.

Applied to files:

  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/types/structseq.rs
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.

Applied to files:

  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/types/structseq.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration using #ifdef checks rather than providing fallback values for other platforms.

Applied to files:

  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/stdlib/os.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration rather than providing fallback values for other platforms.

Applied to files:

  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/stdlib/os.rs
🧬 Code graph analysis (8)
crates/derive-impl/src/pymodule.rs (1)
crates/derive-impl/src/pystructseq.rs (4)
  • inner (316-318)
  • class_name (322-344)
  • no_attr (397-399)
  • module (346-368)
crates/derive/src/lib.rs (1)
crates/derive-impl/src/lib.rs (2)
  • pystruct_sequence (61-63)
  • pystruct_sequence_data (65-67)
crates/stdlib/src/resource.rs (2)
crates/derive/src/lib.rs (3)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
  • pyclass (124-128)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/vm/src/stdlib/pwd.rs (3)
crates/derive-impl/src/lib.rs (3)
  • pystruct_sequence_data (65-67)
  • pystruct_sequence (61-63)
  • pyclass (41-47)
crates/derive/src/lib.rs (3)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
  • pyclass (124-128)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/derive-impl/src/lib.rs (2)
crates/derive/src/lib.rs (2)
  • pystruct_sequence (235-239)
  • pystruct_sequence_data (264-268)
crates/derive-impl/src/pystructseq.rs (2)
  • impl_pystruct_sequence (412-500)
  • impl_pystruct_sequence_data (166-301)
crates/derive-impl/src/pystructseq.rs (1)
crates/vm/src/types/structseq.rs (4)
  • Self (280-283)
  • try_from_elements (171-173)
  • into_tuple (166-166)
  • from_data (186-191)
crates/vm/src/stdlib/os.rs (2)
crates/derive/src/lib.rs (3)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
  • pyclass (124-128)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/vm/src/types/structseq.rs (1)
crates/vm/src/vm/context.rs (1)
  • new_tuple (462-464)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Check Rust code with clippy
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: Run tests under miri
🔇 Additional comments (44)
crates/vm/src/stdlib/pwd.rs (4)

19-28: LGTM!

The PasswdData struct correctly uses the new #[pystruct_sequence_data] attribute macro, cleanly separating the data representation from the Python binding. Field definitions match the expected Unix passwd structure.


30-35: LGTM!

The PyPasswd wrapper struct correctly uses the new attribute-based macro pattern with proper module and name bindings. The #[pyattr] makes the type accessible from Python, and the #[pyclass(with(PyStructSequence))] correctly applies the struct sequence implementation. Based on coding guidelines, this follows the macro system pattern for implementing Python functionality in Rust.


37-59: LGTM!

The From<User> implementation correctly targets PasswdData, and the function signatures for getpwnam and getpwuid properly return PyResult<PasswdData>. The data-centric pattern allows the macro-generated traits to handle Python object conversion automatically.

Also applies to: 61-76, 78-96


98-116: LGTM!

The getpwall function correctly uses the explicit .to_pyobject(vm) conversion pattern since it returns a Vec<PyObjectRef> rather than relying on automatic conversion. The thread-safety mechanism with parking_lot::Mutex is properly maintained.

crates/vm/src/stdlib/posix.rs (2)

1333-1342: LGTM!

The uname function correctly returns UnameResultData with all fields properly populated. This aligns with the new data-driven struct sequence pattern.


1729-1747: LGTM!

The get_terminal_size function correctly returns TerminalSizeData with the columns and lines fields. This is consistent with the NT module implementation and the new data-centric pattern.

crates/vm/src/stdlib/nt.rs (1)

178-210: LGTM!

The get_terminal_size function correctly migrates to TerminalSizeData, maintaining consistency with the POSIX implementation and the new data-driven pattern.

crates/vm/src/types/structseq.rs (5)

16-69: LGTM!

The struct_sequence_new function correctly validates sequence length against n_sequence_fields (minimum) and n_fields (maximum), and pads with None values. Error messages are clear and follow CPython patterns.


71-76: LGTM!

The get_visible_len helper correctly retrieves n_sequence_fields from the class.


151-174: LGTM!

The PyStructSequenceData trait provides a clean interface for data structs with required/optional field separation and conversion methods.


176-325: LGTM!

The PyStructSequence trait implementation correctly:

  • Wires up field getters for both required and optional fields
  • Sets up __match_args__, n_sequence_fields, n_fields, and n_unnamed_fields
  • Overrides as_sequence, as_mapping, iter, hash, and richcompare slots

The from_data method correctly converts data to a typed tuple reference.


327-355: LGTM!

The struct_sequence_iter and struct_sequence_hash functions correctly operate only on visible elements by taking the first n_seq items.

crates/vm/src/stdlib/os.rs (8)

738-781: LGTM!

The StatResultData struct correctly separates:

  • Required visible fields (st_mode through st_size)
  • Unnamed visible fields (st_atime_int, st_mtime_int, st_ctime_int) with #[pystruct_sequence(unnamed)]
  • Hidden/skip fields (float times, ns times, reparse_tag) with #[pystruct_sequence(skip)]

This matches CPython's stat_result structure where indices 0-6 are named, 7-9 are integer timestamps, and the rest are attribute-only.


783-835: LGTM!

The from_stat method correctly computes all time-related fields from the platform-specific stat struct, handling the various timestamp representations across platforms (standard unix, netbsd, wasi, windows).


837-868: LGTM!

The PyStatResult wrapper and slot_new implementation correctly:

  • Flattens nested tuple arguments (matching CPython behavior)
  • Binds to StatResultData and converts via to_pyobject

940-941: LGTM!

The stat function correctly returns StatResultData::from_stat(&stat, vm).to_pyobject(vm).


1235-1252: LGTM!

The TimesResultData and PyTimesResult are properly defined with the five standard fields (user, system, children_user, children_system, elapsed).


1281-1319: LGTM!

The times function correctly constructs TimesResultData on both Windows and Unix platforms and returns it via to_pyobject(vm).


1482-1494: LGTM!

The TerminalSizeData and PyTerminalSize are properly defined with columns and lines fields.


1496-1511: LGTM!

The UnameResultData and PyUnameResult are properly defined with sysname, nodename, release, version, and machine fields.

crates/vm/src/stdlib/time.rs (7)

36-41: LGTM!

Import changes correctly add struct_sequence_new and PyStructSequence while removing TryFromObject (now handled via macro-generated code).


279-305: LGTM!

Both OptionalArg implementations correctly handle conversion to NaiveDateTime for local time.


317-326: LGTM!

The localtime function correctly uses StructTimeData::new with the local instant and isdst=-1 (unknown).


328-338: LGTM!

The mktime function correctly:

  • Converts StructTimeData to NaiveDateTime
  • Interprets it as local time using chrono::Local.from_local_datetime()
  • Handles ambiguous/invalid local times with proper error
  • Returns epoch timestamp as f64

342-374: LGTM!

Both asctime and strftime correctly accept OptionalArg<StructTimeData> and use naive_or_local for default handling.


478-541: LGTM!

The StructTimeData struct properly defines all 11 struct_time fields with:

  • 9 visible required fields (tm_year through tm_isdst)
  • 2 hidden skip fields (tm_gmtoff, tm_zone)

The to_date_time method correctly reconstructs NaiveDateTime from the fields.


543-554: LGTM!

The PyStructTime wrapper correctly uses struct_sequence_new in slot_new to construct instances from sequences, delegating validation to the shared implementation.

crates/stdlib/src/resource.rs (4)

66-84: LGTM: Data struct correctly defined.

The RUsageData struct properly uses the new #[pystruct_sequence_data] macro with all fields correctly typed. The field layout matches the original libc::rusage structure.


86-91: LGTM: Wrapper struct correctly configured.

The PyRUsage wrapper properly uses #[pystruct_sequence] with all required parameters (name, module, data) and is correctly registered with #[pyattr] for module visibility.


93-115: LGTM: Conversion implementation is correct.

The From<libc::rusage> trait implementation correctly maps all 16 fields with proper timeval-to-f64 conversion for timing fields.


118-118: Verify that the data struct return type integrates correctly with the macro system using RustPython's conversion APIs.

The function now returns PyResult<RUsageData> (the data struct) rather than the Python wrapper type. Ensure that the appropriate RustPython conversion macro (such as PyStructSequence or TryIntoPyStructSequence from rustpython-derive) correctly converts RUsageData into a Python struct sequence when returned from a #[pyfunction]. Note: RustPython uses its own conversion system via rustpython-derive and rustpython-vm, not PyO3's ToPyObject trait.

crates/stdlib/src/grp.rs (2)

16-46: PyStructSequence data type and wrapper wiring look correct

The GroupData layout (gr_name, gr_passwd, gr_gid, gr_mem) matches CPython’s struct_group fields, and the combination of #[pystruct_sequence_data] on GroupData with #[pyattr] + #[pystruct_sequence(name = "struct_group", module = "grp", data = "GroupData")] and #[pyclass(with(PyStructSequence))] impl PyGroup {} is consistent with the new data‑backed struct‑sequence pattern. The from_unistd_group constructor cleanly encapsulates the nix::unistd::Group → Python conversion, including robust handling of non‑UTF‑8 passwords via cstr_lossy.


49-84: Function returns and getgrall conversion align with the new data‑driven struct‑sequence model

Having getgrgid and getgrnam return PyResult<GroupData> (rather than the wrapper type directly) fits the attribute‑based pystruct_sequence_data approach, relying on the generated ToPyObject for GroupData to materialize grp.struct_group instances at the Python level. The error handling and lookup logic (range check via libc::gid_t::try_from, Group::from_gid/from_name, and KeyError messages) remain intact.

In getgrall, converting via GroupData::from_unistd_group(group, vm).to_pyobject(vm) ensures all three APIs go through the same data constructor, so field semantics stay consistent across getgrgid, getgrnam, and getgrall.

Also applies to: 93-101

crates/vm/src/stdlib/sys.rs (2)

1-3: Re-exporting UnraisableHookArgsData for VM integration is sound

Re-exporting UnraisableHookArgsData from sys keeps the VM side using the new data-centric API without depending on the concrete struct-sequence wrapper. No issues here.


763-773: Single-sourcing the int max‑digits threshold via IntInfoData is a good change

Using IntInfoData::INFO.str_digits_check_threshold inside set_int_max_str_digits keeps the validation rule (0 or >= threshold) in one place and aligned with what sys.int_info reports. The logic and error path look correct.

crates/derive-impl/src/pymodule.rs (5)

16-53: AttrName extension for pystruct_sequence looks consistent

The new StructSequence variant is correctly threaded through Display and FromStr, so #[pystruct_sequence] will be recognized and formatted like existing #[py*] attributes. No issues here.


223-247: Factory wiring for StructSequenceItem matches existing patterns

The new AttrName::StructSequence arm in module_item_new mirrors the existing Function/Class/Attr cases and correctly fills ContentItemInner { index, attr_name } plus py_attrs. This will integrate struct sequences cleanly into the module item pipeline.


308-325: Allowing #[pystruct_sequence] after #[pyattr] is aligned with other #[py*] items

Including AttrName::StructSequence in the list of items that may follow #[pyattr] and updating the error message keeps the module attribute surface coherent with classes, functions, and exceptions.


416-452: StructSequenceItem as a ContentItem is minimal and correct

The new StructSequenceItem wrapper and its ContentItem impl are straightforward copies of the existing patterns for FunctionItem/ClassItem/AttributeItem, so they integrate properly with shared infrastructure.


630-705: Struct sequence module wiring mirrors #[pyclass] behavior; semantics look sound

StructSequenceItem::gen_module_item:

  • Validates the item is a struct, using bail_span! on misuse.
  • Uses PyStructSequenceMeta::from_attr so the attribute parsing is centralized with the rest of the struct-sequence machinery.
  • Enforces name and module the same way every time, defaulting module to the pymodule name.
  • Requires either #[pyattr] or no_attr, matching #[pyclass] semantics for whether the type is exported as a module attribute.
  • Handles multiple #[pyattr] names and generates either a single or cloned registration.

Assuming the existing blanket PyClassImpl implementation still applies to types that implement PyClassDef + StaticType + MaybeTraverse, this should behave correctly at runtime. Please just ensure existing tests around #[pyclass]+#[pyattr]-style module wiring are extended to cover #[pystruct_sequence] as well.

crates/derive/src/lib.rs (1)

217-239: Attribute macro surface for #[pystruct_sequence] is wired correctly

The new pystruct_sequence proc-macro attribute mirrors patterns used by pyclass, pyexception, and pymodule (parse attr via Punctuated::parse_terminated, parse item as Item, then delegate to derive_impl). The documentation and example match the implementation contract (empty struct type wrapping a data struct).

crates/derive-impl/src/lib.rs (1)

61-67: New derive-impl entry points for struct sequences are consistent with existing API

pystruct_sequence and pystruct_sequence_data follow the same pattern as pyclass/pymodule: they accept (PunctuatedNestedMeta, Item) and delegate to the corresponding impl_ functions via result_to_tokens. This keeps the public derive-impl surface coherent.

crates/derive-impl/src/pystructseq.rs (2)

146-153: has_try_from_object helper is straightforward

Scanning the nested meta list for an ident equal to try_from_object is a simple and robust way to gate generation of the extra conversion impls. No functional concerns here.


255-301: Data struct codegen and attribute stripping look correct

The generated output for #[pystruct_sequence_data]:

  • Adds an inherent impl with field index constants derived from field idents.
  • Implements PyStructSequenceData with REQUIRED_FIELD_NAMES, OPTIONAL_FIELD_NAMES, and UNNAMED_FIELDS_LEN all based on the parsed FieldInfo.
  • Appends the optional try_from_elements override and TryFromObject impl only when try_from_object is requested.
  • Re-emits a cleaned version of the original struct with only #[pystruct_sequence(...)] field attributes removed.

This matches the documented behavior and keeps user-visible struct definitions free of macro-only attributes.

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: 0

♻️ Duplicate comments (6)
crates/vm/src/types/structseq.rs (4)

101-117: Clamp visible length to prevent panics if n_sequence_fields is modified from Python.

The item function uses n_seq from get_visible_len directly to bounds-check and index into the tuple without clamping to the actual tuple length. If Python code mutates n_sequence_fields to exceed the tuple's length, this will panic when accessing tuple[idx].

Apply this defensive fix:

 item: atomic_func!(|seq, i, vm| {
     let n_seq = get_visible_len(seq.obj, vm)?;
     let tuple = seq.obj.downcast_ref::<PyTuple>().unwrap();
+    let visible_len = n_seq.min(tuple.len());
     let idx = if i < 0 {
-        let pos_i = n_seq as isize + i;
+        let pos_i = visible_len as isize + i;
         if pos_i < 0 {
             return Err(vm.new_index_error("tuple index out of range"));
         }
         pos_i as usize
     } else {
         i as usize
     };
-    if idx >= n_seq {
+    if idx >= visible_len {
         return Err(vm.new_index_error("tuple index out of range"));
     }
     Ok(tuple[idx].clone())
 }),

136-147: Clamp visible length before slicing to prevent panics.

The subscript function slices tuple.as_slice()[..n_seq] without clamping n_seq to the tuple's actual length. A mutated n_sequence_fields larger than the tuple will cause an out-of-bounds panic.

Apply this fix:

 subscript: atomic_func!(|mapping, needle, vm| {
     let n_seq = get_visible_len(mapping.obj, vm)?;
     let tuple = mapping.obj.downcast_ref::<PyTuple>().unwrap();
-    let visible_elements = &tuple.as_slice()[..n_seq];
+    let visible_len = n_seq.min(tuple.len());
+    let visible_elements = &tuple.as_slice()[..visible_len];

     match SequenceIndex::try_from_borrowed_object(vm, needle, "tuple")? {
         SequenceIndex::Int(i) => visible_elements.getitem_by_index(vm, i),
         SequenceIndex::Slice(slice) => visible_elements
             .getitem_by_slice(vm, slice)
             .map(|x| vm.ctx.new_tuple(x).into()),
     }
 }),

235-246: Clamp visible length in __getitem__ to prevent panics.

The method slices zelf.as_slice()[..n_seq] without clamping. If n_sequence_fields is modified to exceed the tuple length, this will panic.

Apply this fix:

 #[pymethod]
 fn __getitem__(zelf: PyRef<PyTuple>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
     let n_seq = get_visible_len(zelf.as_ref(), vm)?;
-    let visible_elements = &zelf.as_slice()[..n_seq];
+    let visible_len = n_seq.min(zelf.len());
+    let visible_elements = &zelf.as_slice()[..visible_len];

     match SequenceIndex::try_from_borrowed_object(vm, &needle, "tuple")? {
         SequenceIndex::Int(i) => visible_elements.getitem_by_index(vm, i),
         SequenceIndex::Slice(slice) => visible_elements
             .getitem_by_slice(vm, slice)
             .map(|x| vm.ctx.new_tuple(x).into()),
     }
 }

359-385: Clamp visible lengths in richcompare to prevent panics.

Both zelf_len and other_len are used to slice tuples without clamping to actual tuple lengths. Mutated n_sequence_fields values will cause out-of-bounds panics.

Apply this fix:

 fn struct_sequence_richcompare(
     zelf: &PyObject,
     other: &PyObject,
     op: PyComparisonOp,
     vm: &VirtualMachine,
 ) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
     let zelf_tuple = zelf
         .downcast_ref::<PyTuple>()
         .ok_or_else(|| vm.new_type_error("expected tuple"))?;

     // If other is not a tuple, return NotImplemented
     let Some(other_tuple) = other.downcast_ref::<PyTuple>() else {
         return Ok(Either::B(PyComparisonValue::NotImplemented));
     };

-    let zelf_len = get_visible_len(zelf, vm)?;
+    let zelf_len = get_visible_len(zelf, vm)?.min(zelf_tuple.len());
     // For other, try to get visible len; if it fails (not a struct sequence), use full length
-    let other_len = get_visible_len(other, vm).unwrap_or(other_tuple.len());
+    let other_len = match get_visible_len(other, vm) {
+        Ok(n) => n.min(other_tuple.len()),
+        Err(_) => other_tuple.len(),
+    };

     let zelf_visible = &zelf_tuple.as_slice()[..zelf_len];
     let other_visible = &other_tuple.as_slice()[..other_len];

     // Use the same comparison logic as regular tuples
     zelf_visible
         .iter()
         .richcompare(other_visible.iter(), op, vm)
         .map(|v| Either::B(PyComparisonValue::Implemented(v)))
 }
crates/vm/src/stdlib/time.rs (1)

309-326: gmtime() must return UTC offset and zone, not local timezone.

Both gmtime() (line 314) and localtime() (line 325) call StructTimeData::new(), which always derives tm_gmtoff and tm_zone from chrono::Local. This is incorrect: per CPython behavior, gmtime() must return tm_gmtoff = 0 and tm_zone = "UTC".

Split StructTimeData::new into UTC and local variants:

 impl StructTimeData {
+    fn new_utc(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
+        Self {
+            tm_year: vm.ctx.new_int(tm.year()).into(),
+            tm_mon: vm.ctx.new_int(tm.month()).into(),
+            tm_mday: vm.ctx.new_int(tm.day()).into(),
+            tm_hour: vm.ctx.new_int(tm.hour()).into(),
+            tm_min: vm.ctx.new_int(tm.minute()).into(),
+            tm_sec: vm.ctx.new_int(tm.second()).into(),
+            tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()).into(),
+            tm_yday: vm.ctx.new_int(tm.ordinal()).into(),
+            tm_isdst: vm.ctx.new_int(isdst).into(),
+            tm_gmtoff: vm.ctx.new_int(0).into(),
+            tm_zone: vm.ctx.new_str("UTC").into(),
+        }
+    }
+
-    fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
+    fn new_local(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
         let local_time = chrono::Local.from_local_datetime(&tm).unwrap();
         let offset_seconds =
             local_time.offset().local_minus_utc() + if isdst == 1 { 3600 } else { 0 };
         let tz_abbr = local_time.format("%Z").to_string();

         Self {
             tm_year: vm.ctx.new_int(tm.year()).into(),
             tm_mon: vm.ctx.new_int(tm.month()).into(),
             tm_mday: vm.ctx.new_int(tm.day()).into(),
             tm_hour: vm.ctx.new_int(tm.hour()).into(),
             tm_min: vm.ctx.new_int(tm.minute()).into(),
             tm_sec: vm.ctx.new_int(tm.second()).into(),
             tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()).into(),
             tm_yday: vm.ctx.new_int(tm.ordinal()).into(),
             tm_isdst: vm.ctx.new_int(isdst).into(),
             tm_gmtoff: vm.ctx.new_int(offset_seconds).into(),
             tm_zone: vm.ctx.new_str(tz_abbr).into(),
         }
     }
 }

 #[pyfunction]
 fn gmtime(
     secs: OptionalArg<Either<f64, i64>>,
     vm: &VirtualMachine,
 ) -> PyResult<StructTimeData> {
     let instant = secs.naive_or_utc(vm)?;
-    Ok(StructTimeData::new(vm, instant, 0))
+    Ok(StructTimeData::new_utc(vm, instant, 0))
 }

 #[pyfunction]
 fn localtime(
     secs: OptionalArg<Either<f64, i64>>,
     vm: &VirtualMachine,
 ) -> PyResult<StructTimeData> {
     let instant = secs.naive_or_local(vm)?;
-    Ok(StructTimeData::new(vm, instant, -1))
+    Ok(StructTimeData::new_local(vm, instant, -1))
 }
crates/derive-impl/src/pystructseq.rs (1)

255-278: Skipped-field semantics vs into_tuple and docs still seem misaligned

FieldKind::Skipped is documented as a “hidden/skipped field (stored in tuple, but hidden from repr/len/index)”, and the higher-level docs (e.g. the #[pystruct_sequence_data] example in crates/derive/src/lib.rs) still describe #[pystruct_sequence(skip)] as “optional field, not included in tuple”. However, into_tuple currently appends both visible_fields and skipped_fields:

let items = vec![
    // visible_fields...
    #(::rustpython_vm::convert::ToPyObject::to_pyobject(self.#skipped_fields, vm),)*
];

So the underlying tuple length always includes skipped fields, which conflicts with the “not included in tuple” wording and makes it non-obvious whether len(), indexing, and tuple(obj) should see those optional fields or not. This was already pointed out in an earlier review; the core concern still seems unresolved.

I’d recommend deciding on a single contract and aligning both code and docs with it—for example:

  • If “skipped” truly means “not part of the sequence”, change into_tuple to emit only visible_fields (as previously suggested) and keep skipped data purely as construction-time extras; or
  • If the intent is “stored but logically hidden”, update the public docs to say so, and ensure the PyStructSequence implementation in vm actually hides those tail elements from __len__ / __getitem__ / repr so behavior matches the description.

Right now it’s hard for users (and for tests) to know what to expect.

🧹 Nitpick comments (3)
crates/vm/src/stdlib/sys.rs (1)

914-924: Verify return type handling: inconsistent with other struct sequence functions.

This function returns AsyncgenHooksData directly, while all other struct sequence functions (lines 453, 458, 745, 755, 815, 820) explicitly call from_data and return PyTupleRef.

For consistency and clarity, consider updating this to match the pattern:

 #[pyfunction]
-fn get_asyncgen_hooks(vm: &VirtualMachine) -> AsyncgenHooksData {
-    AsyncgenHooksData {
+fn get_asyncgen_hooks(vm: &VirtualMachine) -> PyTupleRef {
+    PyAsyncgenHooks::from_data(
+        AsyncgenHooksData {
+            firstiter: crate::vm::thread::ASYNC_GEN_FIRSTITER
+                .with_borrow(Clone::clone)
+                .to_pyobject(vm),
+            finalizer: crate::vm::thread::ASYNC_GEN_FINALIZER
+                .with_borrow(Clone::clone)
+                .to_pyobject(vm),
+        },
+        vm,
+    )
+}

If returning Data directly is intentional and supported by the macro system, please verify it works correctly and consider adding a comment explaining why this function differs from the others.

crates/derive-impl/src/pymodule.rs (1)

416-705: StructSequenceItem module emission is correct but could share more with ClassItem

StructSequenceItem::gen_module_item validates that #[pystruct_sequence] is on a struct, uses PyStructSequenceMeta to retrieve name/module/no_attr, enforces the “either #[pyattr] or no_attr” rule, and then generates make_class + module attribute wiring in parallel to ClassItem. Semantically this matches how other type-defining py* items behave.

If you find future evolution is duplicating more logic between ClassItem and StructSequenceItem (e.g., defaulting the module name into the attribute meta, or tweaking no_attr behavior), consider extracting a small helper that takes { ident, meta, py_attrs, cfgs } and returns { class_new, py_names, tokens } so both paths stay in lockstep. Right now it’s fine; this is just a maintainability suggestion.

crates/derive-impl/src/pystructseq.rs (1)

412-497: impl_pystruct_sequence discards the original struct body and generics; consider validating or forwarding

The impl_pystruct_sequence macro currently:

  • Requires the input to be a struct via let Item::Struct(struct_item) = item, but
  • Ignores struct_item’s fields, generics, and any additional attributes, and
  • Re-emits a fresh zero-sized #pytype_vis struct #pytype_ident; before adding the trait impls.

Given the docs state that this macro is intended for an “empty struct to create a Python type that wraps a Data struct”, this is probably fine for the intended usage, but it has a couple of sharp edges:

  • If someone accidentally adds fields or generic parameters to the annotated struct, they’ll be silently dropped, and generic uses like Foo<T> will start failing with confusing type errors because only a monomorphic Foo is emitted.
  • Any extra attributes or doc comments on the original struct are also lost.

Two possible improvements:

-    let output = quote! {
-        // The Python type struct (user-defined, possibly empty)
-        #pytype_vis struct #pytype_ident;
+    // Enforce the “empty, non-generic marker struct” contract up front.
+    if !struct_item.generics.params.is_empty() || !matches!(struct_item.fields, syn::Fields::Unit) {
+        bail_span!(
+            struct_item,
+            "#[pystruct_sequence] expects an empty, non-generic struct (e.g., `struct PyStructTime;`)"
+        );
+    }
+
+    let output = quote! {
+        // The Python type struct (user-defined, but treated as an empty marker)
+        #pytype_vis struct #pytype_ident;
         // trait impls...
     };

Or, if you’d like to be more flexible, you could instead emit #struct_item verbatim and only add the trait impls, so any fields/generics/attributes the user wrote are preserved.

Either way, making this explicit would save users from subtle surprises if they ever extend the marker struct.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6f53fb and a5aa409.

⛔ Files ignored due to path filters (3)
  • Lib/test/datetimetester.py is excluded by !Lib/**
  • Lib/test/test_logging.py is excluded by !Lib/**
  • Lib/test/test_structseq.py is excluded by !Lib/**
📒 Files selected for processing (16)
  • crates/derive-impl/src/lib.rs (1 hunks)
  • crates/derive-impl/src/pymodule.rs (9 hunks)
  • crates/derive-impl/src/pystructseq.rs (3 hunks)
  • crates/derive/src/lib.rs (1 hunks)
  • crates/stdlib/src/grp.rs (5 hunks)
  • crates/stdlib/src/resource.rs (4 hunks)
  • crates/vm/src/stdlib/nt.rs (2 hunks)
  • crates/vm/src/stdlib/os.rs (9 hunks)
  • crates/vm/src/stdlib/posix.rs (3 hunks)
  • crates/vm/src/stdlib/pwd.rs (7 hunks)
  • crates/vm/src/stdlib/sys.rs (21 hunks)
  • crates/vm/src/stdlib/time.rs (7 hunks)
  • crates/vm/src/types/mod.rs (1 hunks)
  • crates/vm/src/types/structseq.rs (4 hunks)
  • crates/vm/src/vm/context.rs (1 hunks)
  • crates/vm/src/vm/mod.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/vm/src/stdlib/posix.rs
  • crates/vm/src/vm/context.rs
  • crates/vm/src/types/mod.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Follow the default rustfmt code style by running cargo fmt to format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • crates/vm/src/stdlib/nt.rs
  • crates/stdlib/src/resource.rs
  • crates/vm/src/stdlib/pwd.rs
  • crates/stdlib/src/grp.rs
  • crates/derive-impl/src/pystructseq.rs
  • crates/vm/src/vm/mod.rs
  • crates/derive/src/lib.rs
  • crates/derive-impl/src/lib.rs
  • crates/vm/src/types/structseq.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/time.rs
  • crates/vm/src/stdlib/sys.rs
  • crates/derive-impl/src/pymodule.rs
🧠 Learnings (6)
📓 Common learnings
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In RustPython's pattern matching implementation, only certain builtin types should have the SEQUENCE flag: list and tuple are confirmed sequences. The user youknowone indicated that bytes, bytearray are not considered sequences in this context, even though they implement sequence-like protocols.
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.rs : Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust

Applied to files:

  • crates/stdlib/src/resource.rs
  • crates/derive/src/lib.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/time.rs
  • crates/vm/src/stdlib/sys.rs
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In the RustPython codebase, only certain builtin types should be marked with the SEQUENCE flag for pattern matching. List and tuple are sequences, but bytes, bytearray, and range are not considered sequences in this context, even though they may implement sequence-like protocols.

Applied to files:

  • crates/vm/src/types/structseq.rs
  • crates/vm/src/stdlib/sys.rs
📚 Learning: 2025-08-26T05:20:54.540Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6110
File: vm/src/frame.rs:1311-1316
Timestamp: 2025-08-26T05:20:54.540Z
Learning: In RustPython's pattern matching implementation, only certain builtin types should have the SEQUENCE flag: list and tuple are confirmed sequences. The user youknowone indicated that bytes, bytearray are not considered sequences in this context, even though they implement sequence-like protocols.

Applied to files:

  • crates/vm/src/types/structseq.rs
  • crates/vm/src/stdlib/sys.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration rather than providing fallback values for other platforms.

Applied to files:

  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/sys.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration using #ifdef checks rather than providing fallback values for other platforms.

Applied to files:

  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/sys.rs
🧬 Code graph analysis (10)
crates/stdlib/src/resource.rs (2)
crates/derive/src/lib.rs (3)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
  • pyclass (124-128)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/vm/src/stdlib/pwd.rs (3)
crates/derive-impl/src/lib.rs (3)
  • pystruct_sequence_data (65-67)
  • pystruct_sequence (61-63)
  • pyclass (41-47)
crates/derive/src/lib.rs (3)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
  • pyclass (124-128)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/stdlib/src/grp.rs (2)
crates/derive/src/lib.rs (3)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
  • pyclass (124-128)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/derive-impl/src/pystructseq.rs (1)
crates/vm/src/types/structseq.rs (3)
  • Self (280-283)
  • try_from_elements (171-173)
  • into_tuple (166-166)
crates/derive/src/lib.rs (1)
crates/derive-impl/src/lib.rs (2)
  • pystruct_sequence (61-63)
  • pystruct_sequence_data (65-67)
crates/derive-impl/src/lib.rs (2)
crates/derive/src/lib.rs (2)
  • pystruct_sequence (235-239)
  • pystruct_sequence_data (264-268)
crates/derive-impl/src/pystructseq.rs (2)
  • impl_pystruct_sequence (412-500)
  • impl_pystruct_sequence_data (166-301)
crates/vm/src/stdlib/os.rs (2)
crates/derive-impl/src/lib.rs (3)
  • pystruct_sequence_data (65-67)
  • pystruct_sequence (61-63)
  • pyclass (41-47)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/vm/src/stdlib/time.rs (4)
crates/vm/src/types/structseq.rs (1)
  • struct_sequence_new (20-69)
crates/derive-impl/src/lib.rs (2)
  • pystruct_sequence_data (65-67)
  • pystruct_sequence (61-63)
crates/derive/src/lib.rs (2)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
crates/derive-impl/src/pystructseq.rs (1)
  • module (346-368)
crates/vm/src/stdlib/sys.rs (4)
crates/vm/src/types/structseq.rs (2)
  • from_data (186-191)
  • Self (280-283)
crates/derive-impl/src/lib.rs (3)
  • pystruct_sequence_data (65-67)
  • pystruct_sequence (61-63)
  • pyclass (41-47)
crates/derive/src/lib.rs (3)
  • pystruct_sequence_data (264-268)
  • pystruct_sequence (235-239)
  • pyclass (124-128)
crates/derive-impl/src/pystructseq.rs (2)
  • module (346-368)
  • no_attr (397-399)
crates/derive-impl/src/pymodule.rs (3)
crates/derive-impl/src/util.rs (12)
  • attr_name (106-108)
  • inner (102-102)
  • inner (283-283)
  • inner (310-312)
  • inner (324-326)
  • inner (358-360)
  • inner (381-383)
  • inner (485-487)
  • class_name (387-410)
  • class_name (491-525)
  • syn (202-202)
  • module (428-457)
crates/derive-impl/src/pystructseq.rs (4)
  • inner (316-318)
  • class_name (322-344)
  • no_attr (397-399)
  • module (346-368)
crates/derive-impl/src/from_args.rs (1)
  • meta (95-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run tests under miri
  • GitHub Check: Check Rust code with clippy
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Ensure compilation on various targets
🔇 Additional comments (18)
crates/vm/src/vm/mod.rs (1)

468-474: LGTM!

The migration from UnraisableHookArgs to UnraisableHookArgsData is consistent with the data-backed wrapper pattern introduced across the PR. All fields are properly initialized.

crates/stdlib/src/grp.rs (1)

16-96: LGTM!

The migration to the data-backed pattern is well-executed:

  • GroupData serves as the data struct with #[pystruct_sequence_data]
  • PyGroup serves as the Python-facing wrapper with data = "GroupData"
  • All construction and conversion logic properly updated to use GroupData
  • Field structure and semantics preserved
crates/vm/src/stdlib/nt.rs (1)

182-209: LGTM!

The function signature and return construction correctly updated to use TerminalSizeData, aligning with the data-backed wrapper pattern.

crates/stdlib/src/resource.rs (1)

66-134: LGTM!

The refactor to the data-backed pattern is complete and correct:

  • RUsageData with #[pystruct_sequence_data] holds all rusage fields
  • PyRUsage wrapper properly configured with data = "RUsageData"
  • From<libc::rusage> implementation correctly populates all 16 fields
  • Function signatures and call sites consistently updated
crates/vm/src/stdlib/pwd.rs (1)

19-116: LGTM!

The migration follows the data-backed pattern correctly:

  • PasswdData serves as the data struct with proper field definitions
  • PyPasswd wrapper configured with data = "PasswdData"
  • From<User> implementation properly handles all password fields with lossy conversion fallbacks
  • All function signatures and call sites consistently updated
crates/vm/src/stdlib/os.rs (3)

738-868: LGTM!

The StatResult refactor is well-designed:

  • StatResultData properly separates visible fields (indices 0-6 + unnamed 7-9 for integer seconds) from skipped fields (float times, nanosecond times, reparse_tag)
  • from_stat correctly populates all fields including the new st_atime_int, st_mtime_int, st_ctime_int and their float/ns variants
  • PyStatResult wrapper properly configured with slot_new using struct_sequence_new
  • Maintains backward compatibility with Python's stat_result interface

1235-1252: LGTM!

The TimesResult refactor follows the data-backed pattern correctly:

  • TimesResultData with 5 float fields (user, system, children_user, children_system, elapsed)
  • PyTimesResult wrapper properly configured
  • Both Windows and Unix implementations updated to construct and return TimesResultData

1482-1511: LGTM!

Both TerminalSize and UnameResult follow the data-backed pattern consistently:

  • TerminalSizeData with columns and lines fields
  • PyTerminalSize wrapper properly configured
  • UnameResultData with 5 string fields (sysname, nodename, release, version, machine)
  • PyUnameResult wrapper properly configured
crates/vm/src/stdlib/time.rs (1)

478-554: LGTM with required fix for gmtime().

The refactor to the data-backed pattern is well-executed:

  • StructTimeData properly defines all 11 fields with appropriate #[pystruct_sequence(skip)] annotations
  • PyStructTime wrapper correctly implements slot_new using struct_sequence_new
  • All time functions updated to use StructTimeData
  • mktime correctly interprets input as local time using chrono::Local.from_local_datetime

However, the gmtime() UTC issue (flagged separately) must be addressed.

crates/vm/src/stdlib/sys.rs (5)

3-3: LGTM: Public export change aligns with data-driven pattern.

Exposing UnraisableHookArgsData allows external code to construct these data structures before wrapping them, which is consistent with the new PyStructSequence architecture.


453-453: LGTM: Consistent from_data pattern.

All these functions consistently use the Py*::from_data(Data, vm) pattern to construct struct sequence instances. The explicit wrapping makes the conversion from data to Python objects clear.

Also applies to: 458-458, 745-745, 755-755, 815-815, 820-820


659-671: LGTM: Windows version construction follows the new pattern.

The function correctly constructs WindowsVersionData from the Windows API results and wraps it using PyWindowsVersion::from_data.


674-674: LGTM: Function signatures updated for data-driven model.

Both _unraisablehook and unraisablehook now accept UnraisableHookArgsData directly. The #[pystruct_sequence_data(try_from_object)] attribute on line 1189 enables automatic conversion from Python objects to the data struct.

Also applies to: 730-730


929-1202: LGTM: Well-structured data-driven struct sequence definitions.

The refactoring consistently applies the new pattern:

  • Data structs hold the actual data with #[pystruct_sequence_data]
  • Py* wrapper structs expose to Python with #[pystruct_sequence(...)]
  • All wrappers correctly use #[pyclass(with(PyStructSequence))]
  • Appropriate visibility modifiers (pub vs pub(super)) based on usage
  • Conditional compilation attributes correctly applied for platform-specific types
  • slot_new overrides on PyFlags and PyVersionInfo properly prevent direct instantiation from Python

The direct field access to IntInfoData::INFO.str_digits_check_threshold on line 765 is correct with the new data-driven model.

crates/derive/src/lib.rs (1)

217-268: pystruct_sequence / pystruct_sequence_data attribute wiring looks consistent

The new attribute macros follow the same pattern as pyclass/pymodule (parse attributes with Punctuated::parse_terminated, parse item, then delegate to derive_impl), and the docs clearly describe the data + wrapper pattern. From this file’s perspective the API surface and delegation look sound.

crates/derive-impl/src/lib.rs (1)

61-67: New pystruct_sequence entry points are correctly routed*

Both pystruct_sequence and pystruct_sequence_data now take (attr: PunctuatedNestedMeta, item: Item) and simply forward into pystructseq::impl_pystruct_sequence(_data) via the common result_to_tokens helper. This matches the updated attribute-based design and keeps the public API surface minimal.

crates/derive-impl/src/pymodule.rs (2)

16-53: AttrName::StructSequence integration under #[pymodule] is coherent

Adding StructSequence to AttrName, its Display/FromStr impls, and allowing it in ALL_ALLOWED_NAMES (via other file) keeps the attribute parsing model uniform with pyfunction/pyclass/pyexception. From this file’s perspective, recognition of #[pystruct_sequence] under #[pymodule] looks consistent and non-breaking.


223-335: pyattr + pystruct_sequence ordering rules mirror existing behavior

Extending module_item_new and attrs_to_module_items so that #[pyattr] can be followed by #[pystruct_sequence] (in addition to pyclass / pyfunction / pyexception) preserves the existing “single py* item per pyattr group” invariants and reuses the same validation paths and error messages. The constraints and diagnostics look aligned with what users already expect from other #[py*] combinations.

@youknowone youknowone merged commit c826f9d into RustPython:main Dec 5, 2025
13 checks passed
@youknowone youknowone deleted the structseq branch December 5, 2025 13:44
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