Skip to content

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Dec 28, 2025

Summary by CodeRabbit

  • New Features

    • Track characters-written on blocking I/O errors
    • Return block size and block count in os.stat on Unix
    • Automatic EINTR-aware retry for interrupted I/O
    • More informative file object representations (include name/mode/encoding) and expose block-size
  • Bug Fixes

    • Stronger bounds and embedded-null validation for I/O paths
    • Better preservation of error cause chains
    • Warn when a boolean is used as a file descriptor

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

📝 Walkthrough

Walkthrough

Adds BlockingIOError handling and character-count tracking (PyOSError.written), EINTR-aware retry logic across IO syscalls, exposes block-size fields on FileIO/TextIOWrapper with Representable repr changes, and appends Unix st_blksize/st_blocks to os.stat results.

Changes

Cohort / File(s) Summary
PyOSError / exceptions
crates/vm/src/exceptions.rs
Add written: AtomicCell<isize> to PyOSError; implement characters_written getter and set_characters_written setter (pygetset); populate written during BlockingIOError construction and adjust filename handling on that path.
IO subsystem & classes
crates/vm/src/stdlib/io.rs
Add EAGAIN constant, EINTR-trapping/retry logic, and BlockingIOError construction with characters-written plumbing; add _blksize storage and getter on FileIO, expose chunk size on TextIOWrapper; mark FileIO/TextIOWrapper as Representable and implement repr_str; improve bounds, embedded-null checks, and error cause preservation.
OS stat result
crates/vm/src/stdlib/os.rs
Add Unix-only st_blksize: i64 and st_blocks: i64 to StatResultData and populate them from platform stat in from_stat.
CRT FD helper
crates/common/src/crt_fd.rs
Make ftruncate handling platform-conditional: Windows branch converts return value to Windows error; non-Windows uses previous cvt behavior.
derive macro (pystructseq)
crates/derive-impl/src/pystructseq.rs
Thread per-field #[cfg(...)] attributes through ParsedField and codegen so structseq fields/constants/tuple construction respect per-field cfg.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Py as Python IO wrapper
    participant Sys as OS syscall
    participant Err as PyOSError
    participant VM as VM Error Handler

    User->>Py: read/write call (may include buffer)
    Py->>Sys: perform syscall
    alt syscall returns data / bytes_written
        Sys-->>Py: success
        Py-->>User: return result
    else syscall returns EINTR
        Sys-->>VM: errno = EINTR
        Note over VM,Py: Trap EINTR (PEP 475) and retry
        VM->>Py: retry syscall
        Py->>Sys: perform syscall (retry)
    else syscall returns EAGAIN / would block
        Sys-->>VM: errno = EAGAIN
        VM->>Err: construct BlockingIOError(errno=EAGAIN)
        Note over Err: set `written` from syscall partial result (if provided)
        VM-->>User: raise BlockingIOError with characters_written
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ShaharNaveh
  • coolreader18

Poem

🐰 Hopped through syscalls, soft and spry,

EINTR bows out — we try, retry.
When writes are halted, I softly note,
"Characters saved" on my little mote.
A rabbit's cheer — bytes kept afloat 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title refers to multiple aspects of the changeset (updating _pyio and test_fileio from Python 3.13.11, implementing additional IO features) that are present in the actual changes across exceptions, io, and os modules.
Docstring Coverage ✅ Passed Docstring coverage is 88.10% 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

📜 Recent 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 fbb33c3 and 6ac1266.

⛔ Files ignored due to path filters (6)
  • Lib/_pyio.py is excluded by !Lib/**
  • Lib/test/test_file_eintr.py is excluded by !Lib/**
  • Lib/test/test_fileio.py is excluded by !Lib/**
  • Lib/test/test_io.py is excluded by !Lib/**
  • Lib/test/test_subprocess.py is excluded by !Lib/**
  • Lib/test/test_tarfile.py is excluded by !Lib/**
📒 Files selected for processing (5)
  • crates/common/src/crt_fd.rs
  • crates/derive-impl/src/pystructseq.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/io.rs
  • crates/vm/src/stdlib/os.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 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/common/src/crt_fd.rs
  • crates/vm/src/exceptions.rs
  • crates/derive-impl/src/pystructseq.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/io.rs
🧠 Learnings (4)
📚 Learning: 2025-11-10T06:27:41.954Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6243
File: vm/src/function/buffer.rs:123-129
Timestamp: 2025-11-10T06:27:41.954Z
Learning: In vm/src/function/buffer.rs, line 36 in the try_rw_bytes_like method intentionally uses TypeError (not BufferError) for the "buffer is not a read-write bytes-like object" error case, even though a similar error message in ArgMemoryBuffer::try_from_borrowed_object uses BufferError. The TypeError is the intended exception type for this specific code path.

Applied to files:

  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/io.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/derive-impl/src/pystructseq.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
📚 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
🧬 Code graph analysis (2)
crates/common/src/crt_fd.rs (2)
crates/vm/src/stdlib/os.rs (1)
  • ftruncate (1566-1568)
crates/common/src/os.rs (1)
  • errno_to_winerror (100-124)
crates/vm/src/stdlib/io.rs (1)
crates/vm/src/exceptions.rs (6)
  • cstring_error (1172-1174)
  • v (1994-1996)
  • repr_str (738-742)
  • zelf (1712-1712)
  • new (547-555)
  • new (1088-1090)
⏰ 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 rust tests (macos-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Check Rust code with clippy
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: Run tests under miri
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Check the WASM package and demo
🔇 Additional comments (35)
crates/common/src/crt_fd.rs (1)

336-353: LGTM! Platform-specific error handling is correct.

The refactored ftruncate implementation properly handles the difference between Windows _chsize_s (which returns errno directly) and POSIX ftruncate (which returns -1 and sets errno). The conversion from errno to Windows error code via errno_to_winerror is appropriate.

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

896-904: LGTM! Unix-specific stat fields properly declared.

The new st_blksize and st_blocks fields are correctly declared with appropriate platform guards and attributes, consistent with CPython's stat result structure.


957-962: LGTM! Correct handling of 32-bit platform compatibility.

The use of i64::from() with the clippy allow is appropriate for ensuring compatibility across 32-bit and 64-bit platforms where the underlying stat field types may differ.


981-984: LGTM! Field initialization is correct.

The initialization of st_blksize and st_blocks is properly guarded and consistent with the field declarations.

crates/vm/src/exceptions.rs (4)

1595-1597: LGTM! Field declaration is appropriate.

The written field using AtomicCell<isize> with -1 as a sentinel value for "not set" follows established patterns and provides thread-safe access for BlockingIOError's characters_written attribute.


1672-1672: LGTM! Field initialization is correct.

Initializing written to -1 in py_new correctly establishes the "not set" state.


1967-1975: LGTM! Getter correctly implements Python attribute semantics.

The characters_written getter appropriately raises AttributeError when the field is unset (-1) and returns the value otherwise, matching standard Python attribute behavior.


1977-2006: LGTM! Setter correctly handles assignment and deletion.

The set_characters_written setter properly implements Python's descriptor protocol:

  • Deletion raises AttributeError when already unset (correct behavior)
  • Assignment converts via try_index and try_to_primitive with appropriate error handling
  • Uses ValueError for conversion failures
crates/vm/src/stdlib/io.rs (22)

216-244: LGTM: Well-designed EINTR trap pattern.

The trap_eintr function correctly implements PEP 475 semantics by:

  • Detecting OSError with errno == EINTR
  • Calling vm.check_signals() to handle pending signals
  • Returning Ok(None) to indicate retry needed

The WASM no-op version is appropriate since EINTR is not relevant in that environment.


693-712: LGTM: Proper bounds validation added.

The bounds check for readinto return values prevents potential issues from invalid implementations. The validation ensures the returned size is within [0, size] and provides a clear error message on violation.


723-730: LGTM: Correct EINTR retry pattern.

The EINTR-aware retry loop properly implements PEP 475 for the readall method, ensuring interrupted reads are automatically retried after signal handling.


944-957: LGTM: Proper BlockingIOError construction.

The BlockingIOError is correctly constructed with:

  • EAGAIN errno
  • Descriptive message
  • characters_written attribute set to 0

This matches CPython's behavior for non-blocking write failures.


1149-1159: LGTM: Correct partial write tracking.

The BlockingIOError construction properly tracks characters_written as written + buffer_len, allowing callers to know exactly how much data was buffered before the operation would block.


1224-1234: Verify loop condition change.

The while remaining > 0 && !self.buffer.is_empty() condition at line 1224 appears to add a buffer emptiness check. Ensure this aligns with the intended logic - specifically that the loop should exit when the buffer is empty, even if remaining > 0.

Based on the surrounding context, this appears correct, but confirming the behavior matches CPython would be helpful.


1295-1331: LGTM: Excellent EINTR handling with proper cleanup.

The EINTR-aware retry loops are correctly implemented in both branches of raw_read. Particularly good is the comment and code ensuring the buffer is always restored, even when an error occurs.


1336-1349: Excellent error handling with cause chain.

The error handling for invalid readinto return values is well-designed:

  • Attempts to convert to isize, capturing conversion errors
  • Constructs an informative OSError about the invalid return
  • Chains the original TypeError as the cause

This provides clear error context for debugging.


1391-1398: LGTM: Consistent EINTR handling.

The EINTR retry loop in read_all is consistent with the pattern used throughout this file.


1669-1672: LGTM: Proper writable check for truncate.

The check ensures truncate is only called on writable files, preventing invalid operations with a clear error message.


2506-2529: LGTM: Essential null character validation.

The embedded null character checks prevent issues when these strings are used with C APIs:

  • Line 2508: Checks encoding string for \0
  • Line 2527: Checks errors string using as_wtf8().as_bytes().contains(&0) to handle surrogates correctly

This is a critical security/correctness measure.


2635-2643: LGTM: Representable trait addition.

Adding Representable to the with clause enables the custom repr_str implementation for TextIOWrapper.


3520-3558: Excellent repr implementation with robust error handling.

The repr_str implementation for TextIOWrapper is well-designed:

  • Handles reentrant calls gracefully (line 3524-3527)
  • Checks for uninitialized state (line 3528-3530)
  • Safely retrieves optional attributes using get_attribute_opt
  • Includes relevant information: type name, name, mode, encoding

The use of let-chains for attribute extraction is clean and idiomatic.


4637-4638: LGTM: _blksize field addition.

The blksize field is properly added as AtomicCell<i64> with a sensible default of 8192 bytes (DEFAULT_BUFFER_SIZE). This aligns with storing st_blksize from file stat information.

Also applies to: 4660-4660


4673-4681: Good defensive warning for bool-as-FD.

The warning when a bool is passed as a file descriptor helps catch a common Python gotcha where bool is a subtype of int. Since using True/False as file descriptors (1/0) is almost certainly unintentional, this warning is valuable.


4732-4785: Excellent resource management for user-provided FDs.

The fd_is_own tracking properly prevents closing user-provided file descriptors when initialization fails. This is critical for correct resource management:

  • Line 4732: Tracks ownership based on whether FD was passed by user
  • Lines 4753-4755, 4767-4770, 4779-4785: On error paths, sets zelf.fd to -1 if FD is not owned, preventing cleanup
  • Lines 4759-4763: Stores st_blksize from stat for Unix systems

This prevents closing FDs that the caller is still responsible for.


4795-4813: LGTM: Clean FileIO repr implementation.

The repr implementation for FileIO is well-structured and informative:

  • Handles closed file state
  • Includes type name, name/fd, mode, and closefd flag
  • Consistent with the TextIOWrapper repr style

4844-4847: LGTM: Simple _blksize getter.

The getter implementation is straightforward and correctly loads the value from the AtomicCell.


4865-4878: LGTM: Improved readable/writable with error checking.

Changing the return type from bool to PyResult<bool> allows these methods to properly report errors when the file is closed, rather than silently returning false. This improves error reporting clarity.


4921-4951: LGTM: Comprehensive EINTR handling in read.

Both branches of the read method properly implement PEP 475:

  • Lines 4922-4931: Sized read with EINTR retry
  • Lines 4937-4946: Read-to-end with EINTR retry

Both use the correct pattern: check for EINTR, call check_signals(), and retry.


4966-4977: LGTM: Consistent EINTR handling in readinto.

The readinto method follows the same EINTR retry pattern as read, ensuring interrupted system calls are automatically retried per PEP 475.


12-19: The hardcoded EAGAIN value of 11 does not apply to WASI targets. The cfg condition any(not(target_arch = "wasm32"), target_os = "wasi") explicitly includes WASI targets, which will use libc::EAGAIN (value 6 per WASI specification). The hardcoded 11 only applies to pure WASM targets without WASI. For pure WASM environments, a placeholder errno value is acceptable since errno semantics don't apply.

Likely an incorrect or invalid review comment.

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

22-63: LGTM!

The addition of cfg_attrs to ParsedField and the refactoring of accessor methods to return &ParsedField instead of &Ident is a clean design that enables downstream code to access conditional compilation attributes.


84-91: LGTM!

The cfg attribute collection logic correctly identifies #[cfg(...)] attributes and preserves them for downstream use while removing only #[pystruct_sequence(...)] attributes.

Also applies to: 144-148


207-219: LGTM!

Field index constants are correctly wrapped with their corresponding cfg attributes, ensuring constants are only defined when their fields are active.


255-290: No action required. The generated into_tuple method correctly uses cfg attributes within the quote! macro system. These are not literal vec![] with cfg attributes in source code—they are TokenStream expansions generated by the proc-macro. When the quote! macro expands #(#visible_tuple_items)* and #(#skipped_tuple_items)*, it properly produces valid Rust code with cfg guards in the vector literal. This pattern is verified to work across multiple uses in the codebase (StatResultData, TimesResultData, TerminalSizeData, StructTimeData) and compiles successfully.

Likely an incorrect or invalid review comment.


221-253: The review comment's concern about cfg attributes on array elements is incorrect. In Rust, block expressions can have outer attributes, so the syntax #[cfg(...)] { expr } is valid for use in array literals. The generated code pattern is legitimate Rust code. This is confirmed by real-world usage in the codebase: StatResultData in crates/vm/src/stdlib/os.rs is decorated with #[pystruct_sequence_data] and contains cfg-guarded fields (st_blksize, st_blocks with #[cfg(not(windows))]). The RustPython project compiles successfully, proving this pattern works as intended.


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 io branch 8 times, most recently from 7d00ace to fbb33c3 Compare December 28, 2025 07:49
@youknowone youknowone marked this pull request as ready for review December 28, 2025 08:07
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

🧹 Nitpick comments (2)
crates/vm/src/exceptions.rs (1)

1728-1744: Verify CPython behavior for numeric third argument detection.

The logic correctly handles the case where BlockingIOError's 3rd argument is the characters written count rather than a filename. The condition chain properly:

  1. Checks if it's BlockingIOError
  2. Verifies the arg is not None and is numeric
  3. Converts to isize and stores it
  4. Clears the filename that was prematurely set in py_new

However, using PyNumber::check may be overly broad (includes floats). Consider whether try_index alone would suffice since it handles the integer conversion.

🔎 Simplified condition
                     // BlockingIOError's 3rd argument can be the number of characters written
                     if is_blocking_io_error
                         && !vm.is_none(third_arg)
-                        && crate::protocol::PyNumber::check(third_arg)
                         && let Ok(written) = third_arg.try_index(vm)
                         && let Ok(n) = written.try_to_primitive::<isize>(vm)
                     {
crates/vm/src/stdlib/io.rs (1)

3520-3558: Potential infinite recursion without ReprGuard.

The repr_str implementation calls name.repr(vm) and mode.repr(vm) (lines 3536, 3544) without using ReprGuard. If the name or mode object has a __repr__ that accesses this TextIOWrapper (e.g., through a circular reference), this could cause infinite recursion.

Compare with repr_file_obj_name (line 1538) which uses ReprGuard::enter(vm, obj) to prevent reentrant repr calls.

🔎 Proposed fix to add ReprGuard
 impl Representable for TextIOWrapper {
     #[inline]
     fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
         let type_name = zelf.class().slot_name();
+        let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) else {
+            return Ok(format!("<{type_name}>"));
+        };
         let Some(data) = zelf.data.lock() else {
             // Reentrant call
             return Ok(format!("<{type_name}>"));
         };
📜 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 44327a8 and fbb33c3.

⛔ Files ignored due to path filters (6)
  • Lib/_pyio.py is excluded by !Lib/**
  • Lib/test/test_file_eintr.py is excluded by !Lib/**
  • Lib/test/test_fileio.py is excluded by !Lib/**
  • Lib/test/test_io.py is excluded by !Lib/**
  • Lib/test/test_subprocess.py is excluded by !Lib/**
  • Lib/test/test_tarfile.py is excluded by !Lib/**
📒 Files selected for processing (3)
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/io.rs
  • crates/vm/src/stdlib/os.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 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/os.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/io.rs
🧠 Learnings (3)
📚 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
📚 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
📚 Learning: 2025-11-10T06:27:41.954Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6243
File: vm/src/function/buffer.rs:123-129
Timestamp: 2025-11-10T06:27:41.954Z
Learning: In vm/src/function/buffer.rs, line 36 in the try_rw_bytes_like method intentionally uses TypeError (not BufferError) for the "buffer is not a read-write bytes-like object" error case, even though a similar error message in ArgMemoryBuffer::try_from_borrowed_object uses BufferError. The TypeError is the intended exception type for this specific code path.

Applied to files:

  • crates/vm/src/stdlib/io.rs
⏰ 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). (1)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
🔇 Additional comments (23)
crates/vm/src/stdlib/os.rs (2)

896-902: LGTM!

The Unix-specific stat fields st_blksize and st_blocks are correctly added with appropriate attributes. Using #[pystruct_sequence(skip)] ensures they're accessible as named attributes while not affecting tuple indexing.


955-959: LGTM!

The platform-specific handling is correct:

  • On Unix/WASI: reads actual values from the stat structure with appropriate conversion
  • On Windows: defaults to 0 as these fields aren't available
  • The #[allow(clippy::useless_conversion)] is correctly placed for 32-bit platform compatibility
crates/vm/src/exceptions.rs (3)

1595-1597: LGTM!

Good design choice using -1 as a sentinel value for "not set". This allows the attribute to raise AttributeError when accessed on exceptions where it wasn't initialized, matching CPython's behavior for BlockingIOError.


1967-1975: LGTM!

The getter correctly returns the value when set, or raises AttributeError when the sentinel value -1 indicates the attribute was never initialized. This matches CPython's behavior for accessing characters_written on non-BlockingIOError exceptions.


1977-2006: The implementation correctly matches CPython behavior.

The -1 value is CPython's internal sentinel indicating the characters_written attribute is unset. The getter raises AttributeError when the value is -1, and deleting the attribute resets it to -1. CPython's setter allows storing -1 (and any integer value) without validation—this is intentional design per Python issue 35504. The RustPython implementation here faithfully replicates this behavior, so no validation to reject -1 is needed.

crates/vm/src/stdlib/io.rs (18)

12-19: LGTM!

The EAGAIN constant is correctly defined with platform-specific handling. The hardcoded value 11 is the standard POSIX value for EAGAIN, appropriate for WASM targets where libc may not be available.


216-243: LGTM!

The trap_eintr function correctly implements PEP 475 EINTR handling. The pattern of returning Ok(None) to signal retry is clean, and the WASM fallback appropriately skips EINTR handling since signals aren't applicable there.


693-711: LGTM!

Good addition of bounds validation for the readinto return value. This ensures that invalid return values from readinto are caught early with a clear error message rather than causing silent data corruption or panics.


723-730: LGTM!

Correct implementation of PEP 475 EINTR retry semantics for the readall method.


944-957: LGTM!

Correct BlockingIOError construction with the proper errno (EAGAIN), descriptive message, and characters_written field set to 0 since no bytes were written during this flush attempt.


1149-1158: LGTM!

Correct BlockingIOError handling that properly tracks characters_written to include both the bytes successfully written to the raw stream and the bytes buffered. This allows callers to handle partial writes correctly.


1295-1329: LGTM!

Good implementation of PEP 475 EINTR retry loops for both code paths in raw_read. The buffer restoration on line 1311 ensures the buffer is properly returned even if an error occurs, preventing resource leaks.


1336-1348: LGTM!

Excellent error chaining implementation. When the return value can't be converted to an integer, the code properly creates an OSError and chains the underlying TypeError as the __cause__, providing better debugging context for developers.


1391-1398: LGTM!

Consistent PEP 475 EINTR retry handling for the read_all method.


1669-1672: LGTM!

Good reordering to check writability before performing any work. This matches CPython's behavior and provides clearer error messages.


2506-2529: LGTM!

Good defensive checks for embedded null characters in encoding and errors strings. These protect against potential C string issues when the values are passed to lower-level functions. Using as_wtf8().as_bytes() for the errors check properly handles potential surrogate characters.


4674-4681: LGTM!

Good addition of a runtime warning when a bool is used as a file descriptor. This matches CPython's behavior and helps users catch potential bugs where they accidentally pass a boolean instead of an actual file descriptor.


4732-4732: LGTM on fd ownership tracking.

The fd_is_own variable correctly tracks whether the fd was provided by the user or opened internally. This ensures user-provided fds are not inadvertently closed when errors occur during initialization (lines 4754-4755, 4768-4770, 4782-4784), which is the correct behavior matching CPython.

Also applies to: 4753-4770


4866-4878: LGTM!

Good improvement to check if the file is closed before returning the readable/writable status. This matches CPython behavior where these methods raise ValueError on closed files rather than returning stale mode information.


4921-4946: LGTM!

Correct PEP 475 EINTR retry implementation for FileIO::read. The loop properly calls check_signals() on EINTR to allow Python signal handlers to run before retrying.


4966-4976: LGTM!

Correct PEP 475 EINTR retry implementation for FileIO::readinto, consistent with the read method.


4796-4812: LGTM on FileIO repr implementation.

The repr_str method correctly uses slot_name() to get the dynamic type name, supporting subclasses properly. The representation format matches CPython's FileIO repr.


1224-1224: LGTM!

Good defensive check to ensure the buffer is not empty before the modulo operation on line 1226, preventing a potential division by zero.

Comment on lines +1714 to +1718
// Check if this is BlockingIOError - need to handle characters_written
let is_blocking_io_error =
zelf.class()
.is(vm.ctx.exceptions.blocking_io_error.as_ref());

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for blocking_io_error usage in the codebase
rg -n "blocking_io_error" --type=rust -B2 -A2

Repository: RustPython/RustPython

Length of output: 7432


🏁 Script executed:

#!/bin/bash
# Search for .is() method definition in the codebase
rg -n "\.is\(" --type=rust crates/vm/src | head -30

Repository: RustPython/RustPython

Length of output: 2670


🏁 Script executed:

#!/bin/bash
# Search for fast_isinstance definition
rg -n "fn fast_isinstance" --type=rust -B2 -A5

Repository: RustPython/RustPython

Length of output: 594


🏁 Script executed:

#!/bin/bash
# Look for PyType's .is() method definition
rg -n "fn is\(" --type=rust crates/vm/src/types -A5 | head -40

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search for .is() method definition
rg -n "fn is\(" --type=rust crates/vm/src/object -B2 -A8

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Also search for .is() on Py type or PyType
rg -n "pub.*fn is\(" --type=rust -B2 -A8 | head -60

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search more broadly for is method
rg "fn is" --type=rust crates/vm/src | grep -E "(fn is\(|fn is<)" | head -20

Repository: RustPython/RustPython

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Check the PyType implementation
fd -e rs "type" crates/vm/src/object | xargs grep -l "impl.*PyType" | head -5

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Look for .is method on Py type
rg -n "\.is\(" --type=rust crates/vm/src/obj | head -20

Repository: RustPython/RustPython

Length of output: 122


🏁 Script executed:

#!/bin/bash
# Search in pytype.rs or similar
fd -e rs "pytype" crates/vm/src

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Get the full implementation of .is() method
rg -n "fn is<T>" --type=rust -B2 -A8 crates/vm/src/object/ext.rs

Repository: RustPython/RustPython

Length of output: 313


🏁 Script executed:

#!/bin/bash
# Check what fast_issubclass does
rg -n "fn fast_issubclass" --type=rust -B2 -A10

Repository: RustPython/RustPython

Length of output: 1021


🏁 Script executed:

#!/bin/bash
# Check if BlockingIOError has any subclasses defined in the codebase
rg -n "BlockingIOError" --type=rust | grep -i "subclass\|extends\|parent\|base"

Repository: RustPython/RustPython

Length of output: 1562


🏁 Script executed:

#!/bin/bash
# Check if there are any Python exception subclass examples in the codebase
rg -n "class.*\(.*Error\)" --type=rust | head -10

Repository: RustPython/RustPython

Length of output: 47


Use fast_isinstance for subclass support.

The current code uses .is() which performs identity comparison only and won't match subclasses of BlockingIOError. This means subclasses won't receive the special 3rd-argument handling for characters_written.

Replace .is(vm.ctx.exceptions.blocking_io_error.as_ref()) with fast_isinstance(vm.ctx.exceptions.blocking_io_error) to properly support subclasses, consistent with how crates/stdlib/src/ssl/compat.rs handles the same exception.

🤖 Prompt for AI Agents
In crates/vm/src/exceptions.rs around lines 1714 to 1718, the code uses identity
comparison zelf.class().is(vm.ctx.exceptions.blocking_io_error.as_ref()) which
does not match subclasses; replace this check with a call to
fast_isinstance(vm.ctx.exceptions.blocking_io_error) on the object to allow
subclass matches (mirror the pattern used in crates/stdlib/src/ssl/compat.rs).
Ensure you reference the correct fast_isinstance function in scope (import or
fully-qualify if necessary) and keep the subsequent characters_written handling
unchanged.

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/pystructseq.rs (1)

318-338: Apply cfg guards in try_from_elements field initialization.

The try_from_elements implementation collects field identifiers without their cfg attributes and uses them in struct initialization. If a field is conditionally compiled (e.g., #[cfg(unix)]), the struct definition will change based on the target platform, but the field initialization in try_from_elements will always reference all fields, causing compilation errors.

🔎 Proposed fix

Modify the code to include cfg guards when generating field initializations:

 let try_from_elements_trait_override = if try_from_object {
-    let visible_field_idents: Vec<_> = visible_fields.iter().map(|f| &f.ident).collect();
-    let skipped_field_idents: Vec<_> = skipped_fields.iter().map(|f| &f.ident).collect();
+    let visible_field_inits: Vec<_> = visible_fields
+        .iter()
+        .map(|f| {
+            let ident = &f.ident;
+            let cfg_attrs = &f.cfg_attrs;
+            if cfg_attrs.is_empty() {
+                quote! { #ident: iter.next().unwrap().clone().try_into_value(vm)?, }
+            } else {
+                quote! {
+                    #(#cfg_attrs)*
+                    #ident: iter.next().unwrap().clone().try_into_value(vm)?,
+                }
+            }
+        })
+        .collect();
+    let skipped_field_inits: Vec<_> = skipped_fields
+        .iter()
+        .map(|f| {
+            let ident = &f.ident;
+            let cfg_attrs = &f.cfg_attrs;
+            if cfg_attrs.is_empty() {
+                quote! {
+                    #ident: match iter.next() {
+                        Some(v) => v.clone().try_into_value(vm)?,
+                        None => vm.ctx.none(),
+                    },
+                }
+            } else {
+                quote! {
+                    #(#cfg_attrs)*
+                    #ident: match iter.next() {
+                        Some(v) => v.clone().try_into_value(vm)?,
+                        None => vm.ctx.none(),
+                    },
+                }
+            }
+        })
+        .collect();
     quote! {
         fn try_from_elements(
             elements: Vec<::rustpython_vm::PyObjectRef>,
             vm: &::rustpython_vm::VirtualMachine,
         ) -> ::rustpython_vm::PyResult<Self> {
             let mut iter = elements.into_iter();
             Ok(Self {
-                #(#visible_field_idents: iter.next().unwrap().clone().try_into_value(vm)?,)*
-                #(#skipped_field_idents: match iter.next() {
-                    Some(v) => v.clone().try_into_value(vm)?,
-                    None => vm.ctx.none(),
-                },)*
+                #(#visible_field_inits)*
+                #(#skipped_field_inits)*
             })
         }
     }
♻️ Duplicate comments (1)
crates/vm/src/exceptions.rs (1)

1714-1745: Duplicate issue: Use fast_isinstance for BlockingIOError check.

Lines 1715-1717 use .is() which only matches exact type identity, not subclasses. This issue was already flagged in previous review comments and should use fast_isinstance(vm.ctx.exceptions.blocking_io_error) instead.

The rest of the logic correctly handles the 3rd argument as either characters_written (when it's a number) or filename (otherwise), with appropriate state management.

📜 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 fbb33c3 and 6ac1266.

⛔ Files ignored due to path filters (6)
  • Lib/_pyio.py is excluded by !Lib/**
  • Lib/test/test_file_eintr.py is excluded by !Lib/**
  • Lib/test/test_fileio.py is excluded by !Lib/**
  • Lib/test/test_io.py is excluded by !Lib/**
  • Lib/test/test_subprocess.py is excluded by !Lib/**
  • Lib/test/test_tarfile.py is excluded by !Lib/**
📒 Files selected for processing (5)
  • crates/common/src/crt_fd.rs
  • crates/derive-impl/src/pystructseq.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/io.rs
  • crates/vm/src/stdlib/os.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 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/common/src/crt_fd.rs
  • crates/vm/src/exceptions.rs
  • crates/derive-impl/src/pystructseq.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/io.rs
🧠 Learnings (4)
📚 Learning: 2025-11-10T06:27:41.954Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6243
File: vm/src/function/buffer.rs:123-129
Timestamp: 2025-11-10T06:27:41.954Z
Learning: In vm/src/function/buffer.rs, line 36 in the try_rw_bytes_like method intentionally uses TypeError (not BufferError) for the "buffer is not a read-write bytes-like object" error case, even though a similar error message in ArgMemoryBuffer::try_from_borrowed_object uses BufferError. The TypeError is the intended exception type for this specific code path.

Applied to files:

  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/io.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/derive-impl/src/pystructseq.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
📚 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
🧬 Code graph analysis (2)
crates/common/src/crt_fd.rs (2)
crates/vm/src/stdlib/os.rs (1)
  • ftruncate (1566-1568)
crates/common/src/os.rs (1)
  • errno_to_winerror (100-124)
crates/vm/src/stdlib/io.rs (1)
crates/vm/src/exceptions.rs (6)
  • cstring_error (1172-1174)
  • v (1994-1996)
  • repr_str (738-742)
  • zelf (1712-1712)
  • new (547-555)
  • new (1088-1090)
⏰ 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 rust tests (macos-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Check Rust code with clippy
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Ensure compilation on various targets
  • GitHub Check: Run tests under miri
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Check the WASM package and demo
🔇 Additional comments (35)
crates/common/src/crt_fd.rs (1)

336-353: LGTM! Platform-specific error handling is correct.

The refactored ftruncate implementation properly handles the difference between Windows _chsize_s (which returns errno directly) and POSIX ftruncate (which returns -1 and sets errno). The conversion from errno to Windows error code via errno_to_winerror is appropriate.

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

896-904: LGTM! Unix-specific stat fields properly declared.

The new st_blksize and st_blocks fields are correctly declared with appropriate platform guards and attributes, consistent with CPython's stat result structure.


957-962: LGTM! Correct handling of 32-bit platform compatibility.

The use of i64::from() with the clippy allow is appropriate for ensuring compatibility across 32-bit and 64-bit platforms where the underlying stat field types may differ.


981-984: LGTM! Field initialization is correct.

The initialization of st_blksize and st_blocks is properly guarded and consistent with the field declarations.

crates/vm/src/exceptions.rs (4)

1595-1597: LGTM! Field declaration is appropriate.

The written field using AtomicCell<isize> with -1 as a sentinel value for "not set" follows established patterns and provides thread-safe access for BlockingIOError's characters_written attribute.


1672-1672: LGTM! Field initialization is correct.

Initializing written to -1 in py_new correctly establishes the "not set" state.


1967-1975: LGTM! Getter correctly implements Python attribute semantics.

The characters_written getter appropriately raises AttributeError when the field is unset (-1) and returns the value otherwise, matching standard Python attribute behavior.


1977-2006: LGTM! Setter correctly handles assignment and deletion.

The set_characters_written setter properly implements Python's descriptor protocol:

  • Deletion raises AttributeError when already unset (correct behavior)
  • Assignment converts via try_index and try_to_primitive with appropriate error handling
  • Uses ValueError for conversion failures
crates/vm/src/stdlib/io.rs (22)

216-244: LGTM: Well-designed EINTR trap pattern.

The trap_eintr function correctly implements PEP 475 semantics by:

  • Detecting OSError with errno == EINTR
  • Calling vm.check_signals() to handle pending signals
  • Returning Ok(None) to indicate retry needed

The WASM no-op version is appropriate since EINTR is not relevant in that environment.


693-712: LGTM: Proper bounds validation added.

The bounds check for readinto return values prevents potential issues from invalid implementations. The validation ensures the returned size is within [0, size] and provides a clear error message on violation.


723-730: LGTM: Correct EINTR retry pattern.

The EINTR-aware retry loop properly implements PEP 475 for the readall method, ensuring interrupted reads are automatically retried after signal handling.


944-957: LGTM: Proper BlockingIOError construction.

The BlockingIOError is correctly constructed with:

  • EAGAIN errno
  • Descriptive message
  • characters_written attribute set to 0

This matches CPython's behavior for non-blocking write failures.


1149-1159: LGTM: Correct partial write tracking.

The BlockingIOError construction properly tracks characters_written as written + buffer_len, allowing callers to know exactly how much data was buffered before the operation would block.


1224-1234: Verify loop condition change.

The while remaining > 0 && !self.buffer.is_empty() condition at line 1224 appears to add a buffer emptiness check. Ensure this aligns with the intended logic - specifically that the loop should exit when the buffer is empty, even if remaining > 0.

Based on the surrounding context, this appears correct, but confirming the behavior matches CPython would be helpful.


1295-1331: LGTM: Excellent EINTR handling with proper cleanup.

The EINTR-aware retry loops are correctly implemented in both branches of raw_read. Particularly good is the comment and code ensuring the buffer is always restored, even when an error occurs.


1336-1349: Excellent error handling with cause chain.

The error handling for invalid readinto return values is well-designed:

  • Attempts to convert to isize, capturing conversion errors
  • Constructs an informative OSError about the invalid return
  • Chains the original TypeError as the cause

This provides clear error context for debugging.


1391-1398: LGTM: Consistent EINTR handling.

The EINTR retry loop in read_all is consistent with the pattern used throughout this file.


1669-1672: LGTM: Proper writable check for truncate.

The check ensures truncate is only called on writable files, preventing invalid operations with a clear error message.


2506-2529: LGTM: Essential null character validation.

The embedded null character checks prevent issues when these strings are used with C APIs:

  • Line 2508: Checks encoding string for \0
  • Line 2527: Checks errors string using as_wtf8().as_bytes().contains(&0) to handle surrogates correctly

This is a critical security/correctness measure.


2635-2643: LGTM: Representable trait addition.

Adding Representable to the with clause enables the custom repr_str implementation for TextIOWrapper.


3520-3558: Excellent repr implementation with robust error handling.

The repr_str implementation for TextIOWrapper is well-designed:

  • Handles reentrant calls gracefully (line 3524-3527)
  • Checks for uninitialized state (line 3528-3530)
  • Safely retrieves optional attributes using get_attribute_opt
  • Includes relevant information: type name, name, mode, encoding

The use of let-chains for attribute extraction is clean and idiomatic.


4637-4638: LGTM: _blksize field addition.

The blksize field is properly added as AtomicCell<i64> with a sensible default of 8192 bytes (DEFAULT_BUFFER_SIZE). This aligns with storing st_blksize from file stat information.

Also applies to: 4660-4660


4673-4681: Good defensive warning for bool-as-FD.

The warning when a bool is passed as a file descriptor helps catch a common Python gotcha where bool is a subtype of int. Since using True/False as file descriptors (1/0) is almost certainly unintentional, this warning is valuable.


4732-4785: Excellent resource management for user-provided FDs.

The fd_is_own tracking properly prevents closing user-provided file descriptors when initialization fails. This is critical for correct resource management:

  • Line 4732: Tracks ownership based on whether FD was passed by user
  • Lines 4753-4755, 4767-4770, 4779-4785: On error paths, sets zelf.fd to -1 if FD is not owned, preventing cleanup
  • Lines 4759-4763: Stores st_blksize from stat for Unix systems

This prevents closing FDs that the caller is still responsible for.


4795-4813: LGTM: Clean FileIO repr implementation.

The repr implementation for FileIO is well-structured and informative:

  • Handles closed file state
  • Includes type name, name/fd, mode, and closefd flag
  • Consistent with the TextIOWrapper repr style

4844-4847: LGTM: Simple _blksize getter.

The getter implementation is straightforward and correctly loads the value from the AtomicCell.


4865-4878: LGTM: Improved readable/writable with error checking.

Changing the return type from bool to PyResult<bool> allows these methods to properly report errors when the file is closed, rather than silently returning false. This improves error reporting clarity.


4921-4951: LGTM: Comprehensive EINTR handling in read.

Both branches of the read method properly implement PEP 475:

  • Lines 4922-4931: Sized read with EINTR retry
  • Lines 4937-4946: Read-to-end with EINTR retry

Both use the correct pattern: check for EINTR, call check_signals(), and retry.


4966-4977: LGTM: Consistent EINTR handling in readinto.

The readinto method follows the same EINTR retry pattern as read, ensuring interrupted system calls are automatically retried per PEP 475.


12-19: The hardcoded EAGAIN value of 11 does not apply to WASI targets. The cfg condition any(not(target_arch = "wasm32"), target_os = "wasi") explicitly includes WASI targets, which will use libc::EAGAIN (value 6 per WASI specification). The hardcoded 11 only applies to pure WASM targets without WASI. For pure WASM environments, a placeholder errno value is acceptable since errno semantics don't apply.

Likely an incorrect or invalid review comment.

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

22-63: LGTM!

The addition of cfg_attrs to ParsedField and the refactoring of accessor methods to return &ParsedField instead of &Ident is a clean design that enables downstream code to access conditional compilation attributes.


84-91: LGTM!

The cfg attribute collection logic correctly identifies #[cfg(...)] attributes and preserves them for downstream use while removing only #[pystruct_sequence(...)] attributes.

Also applies to: 144-148


207-219: LGTM!

Field index constants are correctly wrapped with their corresponding cfg attributes, ensuring constants are only defined when their fields are active.


255-290: No action required. The generated into_tuple method correctly uses cfg attributes within the quote! macro system. These are not literal vec![] with cfg attributes in source code—they are TokenStream expansions generated by the proc-macro. When the quote! macro expands #(#visible_tuple_items)* and #(#skipped_tuple_items)*, it properly produces valid Rust code with cfg guards in the vector literal. This pattern is verified to work across multiple uses in the codebase (StatResultData, TimesResultData, TerminalSizeData, StructTimeData) and compiles successfully.

Likely an incorrect or invalid review comment.


221-253: The review comment's concern about cfg attributes on array elements is incorrect. In Rust, block expressions can have outer attributes, so the syntax #[cfg(...)] { expr } is valid for use in array literals. The generated code pattern is legitimate Rust code. This is confirmed by real-world usage in the codebase: StatResultData in crates/vm/src/stdlib/os.rs is decorated with #[pystruct_sequence_data] and contains cfg-guarded fields (st_blksize, st_blocks with #[cfg(not(windows))]). The RustPython project compiles successfully, proving this pattern works as intended.

@youknowone youknowone merged commit 3600b66 into RustPython:main Dec 28, 2025
13 checks passed
@youknowone youknowone deleted the io branch December 28, 2025 09:06
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