Skip to content

Clippy warn on unnecessary wraps#7869

Merged
youknowone merged 66 commits into
RustPython:mainfrom
ShaharNaveh:clippy-unnecessary-wraps
May 13, 2026
Merged

Clippy warn on unnecessary wraps#7869
youknowone merged 66 commits into
RustPython:mainfrom
ShaharNaveh:clippy-unnecessary-wraps

Conversation

@ShaharNaveh
Copy link
Copy Markdown
Contributor

@ShaharNaveh ShaharNaveh commented May 13, 2026

Summary by CodeRabbit

  • Chores
    • Simplified internal error handling signatures across the codebase by removing unnecessary Result wrappers from functions that don't propagate errors, resulting in cleaner and more maintainable code with no user-visible changes.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

📝 Walkthrough

Walkthrough

This PR adds the Clippy lint unnecessary_wraps = "warn" to the workspace and systematically removes PyResult wrapping from hundreds of helper functions, internal methods, and stdlib functions across codegen, VM builtins, and stdlib modules. Functions now return direct values (bool, usize, String, PyObjectRef, etc.) instead of Result-wrapped equivalents, simplifying call sites by eliminating ? operators and Ok(...) wrappers where applicable. Several module-level module_exec functions and similar required-signature spots gain #[expect] annotations.

Changes

Infallibility refactor across codegen, VM, and stdlib

Layer / File(s) Summary
Clippy lint and codegen
Cargo.toml, crates/codegen/src/compile.rs
Enable unnecessary_wraps lint; refactor codegen helper signatures (varname, get_*_var_index, emit_format_validation, pattern helpers) from CompileResult to infallible; update all call sites to remove ? operators.
String parsing and format specs
crates/codegen/src/string_parser.rs, crates/common/src/format.rs
Make parse_unicode_literal and format_sign_and_align infallible; update call sites accordingly.
Derive macros and utilities
crates/derive-impl/src/*
Refactor proc-macro functions to return TokenStream directly; make ItemNursery::add_item and metadata helpers infallible; remove error propagation from code-gen item factories.
Asyncio, socket, and core stdlib
crates/stdlib/src/_asyncio.rs, crates/stdlib/src/socket.rs, crates/stdlib/src/locale.rs, crates/stdlib/src/grp.rs, crates/stdlib/src/mmap.rs, crates/stdlib/src/overlapped.rs, crates/stdlib/src/pyexpat.rs, crates/stdlib/src/select.rs, crates/stdlib/src/_sqlite3.rs, crates/stdlib/src/contextvars.rs
Update Future/Task/await, socket parse_ancillary_data, locale helpers, and context var methods to return direct values; add clippy expects where needed.
SSL and cryptography
crates/stdlib/src/ssl.rs, crates/stdlib/src/ssl/cert.rs
Simplify _SSLContext, _SSLSocket, MemoryBIO methods and cert name conversion to infallible returns.
VM builtins pickle and arithmetic
crates/vm/src/builtins/{range,set,slice,str,zip,map,int,genericalias,type}.rs, crates/vm/src/builtins/{frame,function,object,asyncgenerator}.rs
Update reduce, setstate, str, round, reversed and similar protocol methods to return direct values; add clippy expects for required-signature methods.
Exception and warning systems
crates/vm/src/exceptions.rs, crates/vm/src/warn.rs
Refactor exception str and traceback handling to infallible; convert warnings module attribute lookup from PyResult to Option-based returns with fallback logic.
ctypes FFI layer
crates/vm/src/stdlib/_ctypes.rs, crates/vm/src/stdlib/_ctypes/{array,base,pointer,structure,union,function}.rs
Update LoadLibrary, check_hresult, format_error_func, CopyComPointer, struct_union_paramfunc, get_field_size, and array element reading to direct returns; refactor error handling at call boundaries.
Other stdlib and VM
crates/stdlib/src/{_operator,_functools,_imp,_io,_signal,_symtable,_thread,_typing,builtins,itertools,nt,os,pwd,sys,time,typevar,winreg}.rs, crates/vm/src/{codecs,frame,stdlib/_ast,stdlib/_codecs,stdlib/_collections}.rs, crates/vm/src/types/slot.rs, crates/vm/src/vm/mod.rs, crates/wasm/src/vm_class.rs
Simplify dozens of method and function signatures (operator.is_, override, extension_suffixes, device_encoding, DirEntry accessors, _BootstrapStderr, _getframemodulename, TypeVar/ParamSpec getset accessors, etc.) to return direct values; add clippy expects where signatures are mandated.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • youknowone
  • coolreader18

Bunny unwrapped the wrappers with care,
Set Results free into open air.
No more Ok, no trailing sigh—
Just values hopping quickly by.
Clippy nods, the fields align;
The code now dances, sleek and fine. 🐇✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@ShaharNaveh ShaharNaveh marked this pull request as ready for review May 13, 2026 11:38
Copy link
Copy Markdown
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 (3)
crates/vm/src/stdlib/_ctypes.rs (1)

546-557: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Preserve Python error propagation in LoadLibrary (avoid panic).

Line 555 uses unwrap() inside a Python-callable function. On library load failure this can panic the VM instead of raising an exception.

Proposed fix
- fn load_library_windows(
+ fn load_library_windows(
     name: String,
     _load_flags: OptionalArg<i32>,
     vm: &VirtualMachine,
-) -> usize {
+) -> PyResult<usize> {
   let cache = library::libcache();
   let mut cache_write = cache.write();
-  let (id, _) = cache_write.get_or_insert_lib(&name, vm).unwrap();
-  id
+  let (id, _) = cache_write
+      .get_or_insert_lib(&name, vm)
+      .map_err(|e| vm.new_os_error(e.to_string()))?;
+  Ok(id)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_ctypes.rs` around lines 546 - 557, The function
load_library_windows currently calls cache.write().get_or_insert_lib(&name,
vm).unwrap() which can panic; change load_library_windows to return
PyResult<usize> (instead of usize) and propagate the error instead of
unwrapping: call get_or_insert_lib(&name, vm) and map or match its Err to a
Python exception via the VirtualMachine (e.g. vm.new_os_error or an appropriate
vm.new_exception with a descriptive message), returning Err(...) on failure, or
use the ? operator after converting the error into a PyResult to return the id
on success; keep references to library::libcache(), cache.write(), and
get_or_insert_lib to locate the code.
crates/vm/src/stdlib/_ctypes/base.rs (2)

2465-2495: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid pointer-size fallback for unknown field types.

Line 2494 defaults to size_of::<usize>() when size resolution fails. That can let invalid _fields_ entries pass with a bogus size instead of failing fast with TypeError.

Suggested direction
-pub(super) fn get_field_size(field_type: &PyObject, vm: &VirtualMachine) -> usize {
+pub(super) fn get_field_size(field_type: &PyObject, vm: &VirtualMachine) -> PyResult<usize> {
  ...
- core::mem::size_of::<usize>()
+ Err(vm.new_type_error("field type has no determinable ctypes size"))
}

Update callers in structure/union/CField initialization to propagate this error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_ctypes/base.rs` around lines 2465 - 2495, Replace the
pointer-size fallback in get_field_size by returning a failure instead: change
get_field_size to return Option<usize> (or Result<usize, PyErr>) instead of
usize, remove the final core::mem::size_of::<usize>() default and return
None/Error when no size could be resolved, and then update all callers (e.g.,
the structure/union initialization paths and CField construction sites that
currently call get_field_size) to check the Option/Result and propagate a
TypeError via the VM (e.g., vm.new_type_error or equivalent) when size
resolution fails so invalid _fields_ entries fail fast.

1590-1643: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Do not silently zero-fill failed ctypes conversions.

Line 1642 (and unwrap_or(0) at Lines 1624, 1628, 1632, 1636) turns invalid/out-of-range assignments into zero bytes instead of raising, which can silently corrupt structure/union field data.

Suggested direction
-fn value_to_bytes(value: &PyObject, size: usize, vm: &VirtualMachine) -> Vec<u8> {
+fn value_to_bytes(value: &PyObject, size: usize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
   ...
-  _ => vec![0u8; size],
+  _ => {
+      return Err(vm.new_type_error(format!(
+          "cannot convert {} to ctypes field value",
+          value.class().name()
+      )));
+  }
}

Then propagate ? at call sites where assignment must fail on invalid conversion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_ctypes/base.rs` around lines 1590 - 1643, The helper
value_to_bytes currently silently zero-fills on failed numeric conversions (uses
unwrap_or(0) and returns zero bytes), which hides errors; change value_to_bytes
to return a PyResult<Vec<u8>> (or Result) and propagate conversion failures
instead of substituting zeros: replace the unwrap_or(0) calls in value_to_bytes
and the final fallback path so that when int conversions via value.try_int(vm)
or bigint->to_i8/to_i16/to_i32/to_i64 fail you return an appropriate Python
error (e.g. OverflowError/TypeError via the VirtualMachine) instead of producing
zero bytes, and likewise make float/int size-mismatch cases return an error
rather than unreachable/zero-fill so callers of value_to_bytes (e.g. code using
PyCData) can handle the failure.
🧹 Nitpick comments (2)
crates/stdlib/src/unicodedata.rs (1)

194-217: ⚡ Quick win

Extract shared normalization logic to avoid duplicated match branches.

Line 194 and Line 223 implement the same match form normalization pipeline in two places. Please extract this into one helper (e.g., fn normalize_text(form, text) -> Wtf8Buf) and reuse it from both methods to keep behavior aligned and reduce drift risk.

As per coding guidelines, "When branches differ only in a value but share common logic, extract the differing value first, then call the common logic once to avoid duplicate code".

Also applies to: 221-245

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/stdlib/src/unicodedata.rs` around lines 194 - 217, Extract the
duplicated normalization pipeline into a single helper (e.g., fn
normalize_text(form: super::NormalizeForm, text: &Wtf8Str) -> Wtf8Buf) that
first maps the form to the correct normalizer instance
(ComposingNormalizerBorrowed::new_nfc(), ::new_nfkc(),
DecomposingNormalizerBorrowed::new_nfd(), ::new_nfkd()) and then performs
text.map_utf8(|s| normalizer.normalize_iter(s.chars())).collect(); replace the
duplicated match blocks in normalize(...) and the other location (the branch
around lines 221–245) to call this helper so only the normalizer selection
differs while the mapping/collect logic is centralized.
crates/vm/src/exceptions.rs (1)

1102-1113: 💤 Low value

Minor: consider a flatter form to avoid the Some(match ...) with early return None.

The current form works correctly but mixes a wrapping Some(...) with an inner return None, which is slightly awkward to read. A direct match returning Option per-arm is more idiomatic.

♻️ Proposed refactor
     let args = exc.args.read();
-    Some(match args.len() {
-        0 => return None,
-        1 => args.first().unwrap().clone(),
-        _ => args.as_object().to_owned(),
-    })
+    match args.len() {
+        0 => None,
+        1 => Some(args.first().unwrap().clone()),
+        _ => Some(args.as_object().to_owned()),
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/exceptions.rs` around lines 1102 - 1113, Refactor
system_exit_code to return an Option directly from a match instead of wrapping a
match in Some(...) and using an early return; replace the current Some(match
args.len() { 0 => return None, 1 => ..., _ => ... }) with match args.len() { 0
=> None, 1 => Some(args.first().unwrap().clone()), _ =>
Some(args.as_object().to_owned()) } so the function returns Option<PyObjectRef>
idiomatically and removes the inner return; keep the same uses of args.read(),
args.first(), and args.as_object() from the existing function.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/vm/src/stdlib/_ctypes/function.rs`:
- Around line 1906-1913: Change set_argtypes back to return PyResult<()> and
perform the same input validation pattern used by set_restype and set_errcheck:
if vm.is_none(&value) set self.argtypes to None and return Ok(()); otherwise
ensure value is an iterable (reject non-iterable with vm.new_type_error) and
iterate its items validating each is either None or an acceptable ctypes
type/callable (reject invalid items with a descriptive Err). Update assignments
to set self.argtypes = Some(value) only after validation succeeds and return
Ok(()).

In `@crates/vm/src/stdlib/sys.rs`:
- Around line 88-92: The write method in sys.rs currently ignores
std::io::stderr().write_all(bytes) errors but always returns bytes.len(), which
violates the write contract; change write to check the Result from write_all on
the PyStrRef bytes and return bytes.len() only on Ok(()), otherwise return 0
(i.e., treat a write failure as zero bytes written). Update the function named
write to capture the result (match or if let Err(_)) and return 0 on error while
preserving the existing successful return value.

---

Outside diff comments:
In `@crates/vm/src/stdlib/_ctypes.rs`:
- Around line 546-557: The function load_library_windows currently calls
cache.write().get_or_insert_lib(&name, vm).unwrap() which can panic; change
load_library_windows to return PyResult<usize> (instead of usize) and propagate
the error instead of unwrapping: call get_or_insert_lib(&name, vm) and map or
match its Err to a Python exception via the VirtualMachine (e.g. vm.new_os_error
or an appropriate vm.new_exception with a descriptive message), returning
Err(...) on failure, or use the ? operator after converting the error into a
PyResult to return the id on success; keep references to library::libcache(),
cache.write(), and get_or_insert_lib to locate the code.

In `@crates/vm/src/stdlib/_ctypes/base.rs`:
- Around line 2465-2495: Replace the pointer-size fallback in get_field_size by
returning a failure instead: change get_field_size to return Option<usize> (or
Result<usize, PyErr>) instead of usize, remove the final
core::mem::size_of::<usize>() default and return None/Error when no size could
be resolved, and then update all callers (e.g., the structure/union
initialization paths and CField construction sites that currently call
get_field_size) to check the Option/Result and propagate a TypeError via the VM
(e.g., vm.new_type_error or equivalent) when size resolution fails so invalid
_fields_ entries fail fast.
- Around line 1590-1643: The helper value_to_bytes currently silently zero-fills
on failed numeric conversions (uses unwrap_or(0) and returns zero bytes), which
hides errors; change value_to_bytes to return a PyResult<Vec<u8>> (or Result)
and propagate conversion failures instead of substituting zeros: replace the
unwrap_or(0) calls in value_to_bytes and the final fallback path so that when
int conversions via value.try_int(vm) or bigint->to_i8/to_i16/to_i32/to_i64 fail
you return an appropriate Python error (e.g. OverflowError/TypeError via the
VirtualMachine) instead of producing zero bytes, and likewise make float/int
size-mismatch cases return an error rather than unreachable/zero-fill so callers
of value_to_bytes (e.g. code using PyCData) can handle the failure.

---

Nitpick comments:
In `@crates/stdlib/src/unicodedata.rs`:
- Around line 194-217: Extract the duplicated normalization pipeline into a
single helper (e.g., fn normalize_text(form: super::NormalizeForm, text:
&Wtf8Str) -> Wtf8Buf) that first maps the form to the correct normalizer
instance (ComposingNormalizerBorrowed::new_nfc(), ::new_nfkc(),
DecomposingNormalizerBorrowed::new_nfd(), ::new_nfkd()) and then performs
text.map_utf8(|s| normalizer.normalize_iter(s.chars())).collect(); replace the
duplicated match blocks in normalize(...) and the other location (the branch
around lines 221–245) to call this helper so only the normalizer selection
differs while the mapping/collect logic is centralized.

In `@crates/vm/src/exceptions.rs`:
- Around line 1102-1113: Refactor system_exit_code to return an Option directly
from a match instead of wrapping a match in Some(...) and using an early return;
replace the current Some(match args.len() { 0 => return None, 1 => ..., _ => ...
}) with match args.len() { 0 => None, 1 => Some(args.first().unwrap().clone()),
_ => Some(args.as_object().to_owned()) } so the function returns
Option<PyObjectRef> idiomatically and removes the inner return; keep the same
uses of args.read(), args.first(), and args.as_object() from the existing
function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: ea378332-1b44-40ac-b3c9-66ff36dae5e8

📥 Commits

Reviewing files that changed from the base of the PR and between 7ebffd0 and fc58a80.

📒 Files selected for processing (72)
  • Cargo.toml
  • crates/codegen/src/compile.rs
  • crates/codegen/src/string_parser.rs
  • crates/common/src/format.rs
  • crates/derive-impl/src/lib.rs
  • crates/derive-impl/src/pyclass.rs
  • crates/derive-impl/src/pymodule.rs
  • crates/derive-impl/src/pypayload.rs
  • crates/derive-impl/src/util.rs
  • crates/stdlib/src/_asyncio.rs
  • crates/stdlib/src/_opcode.rs
  • crates/stdlib/src/_sqlite3.rs
  • crates/stdlib/src/contextvars.rs
  • crates/stdlib/src/grp.rs
  • crates/stdlib/src/locale.rs
  • crates/stdlib/src/mmap.rs
  • crates/stdlib/src/overlapped.rs
  • crates/stdlib/src/pyexpat.rs
  • crates/stdlib/src/select.rs
  • crates/stdlib/src/sha256.rs
  • crates/stdlib/src/sha512.rs
  • crates/stdlib/src/socket.rs
  • crates/stdlib/src/ssl.rs
  • crates/stdlib/src/ssl/cert.rs
  • crates/stdlib/src/unicodedata.rs
  • crates/vm/src/builtins/asyncgenerator.rs
  • crates/vm/src/builtins/frame.rs
  • crates/vm/src/builtins/function.rs
  • crates/vm/src/builtins/genericalias.rs
  • crates/vm/src/builtins/int.rs
  • crates/vm/src/builtins/map.rs
  • crates/vm/src/builtins/object.rs
  • crates/vm/src/builtins/range.rs
  • crates/vm/src/builtins/set.rs
  • crates/vm/src/builtins/slice.rs
  • crates/vm/src/builtins/str.rs
  • crates/vm/src/builtins/type.rs
  • crates/vm/src/builtins/zip.rs
  • crates/vm/src/codecs.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/frame.rs
  • crates/vm/src/stdlib/_ast.rs
  • crates/vm/src/stdlib/_codecs.rs
  • crates/vm/src/stdlib/_collections.rs
  • crates/vm/src/stdlib/_ctypes.rs
  • crates/vm/src/stdlib/_ctypes/array.rs
  • crates/vm/src/stdlib/_ctypes/base.rs
  • crates/vm/src/stdlib/_ctypes/function.rs
  • crates/vm/src/stdlib/_ctypes/pointer.rs
  • crates/vm/src/stdlib/_ctypes/structure.rs
  • crates/vm/src/stdlib/_ctypes/union.rs
  • crates/vm/src/stdlib/_functools.rs
  • crates/vm/src/stdlib/_imp.rs
  • crates/vm/src/stdlib/_io.rs
  • crates/vm/src/stdlib/_operator.rs
  • crates/vm/src/stdlib/_signal.rs
  • crates/vm/src/stdlib/_symtable.rs
  • crates/vm/src/stdlib/_thread.rs
  • crates/vm/src/stdlib/_typing.rs
  • crates/vm/src/stdlib/builtins.rs
  • crates/vm/src/stdlib/itertools.rs
  • crates/vm/src/stdlib/nt.rs
  • crates/vm/src/stdlib/os.rs
  • crates/vm/src/stdlib/pwd.rs
  • crates/vm/src/stdlib/sys.rs
  • crates/vm/src/stdlib/time.rs
  • crates/vm/src/stdlib/typevar.rs
  • crates/vm/src/stdlib/winreg.rs
  • crates/vm/src/types/slot.rs
  • crates/vm/src/vm/mod.rs
  • crates/vm/src/warn.rs
  • crates/wasm/src/vm_class.rs

Comment on lines +1906 to 1913
fn set_argtypes(&self, value: PyObjectRef, vm: &VirtualMachine) {
if vm.is_none(&value) {
*self.argtypes.write() = None;
} else {
// Store the argtypes object directly as it is
*self.argtypes.write() = Some(value);
}
Ok(())
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore validation and PyResult<()> return type for consistency.

The removal of PyResult<()> from set_argtypes creates an inconsistency with other setters in this impl block. Both set_restype (line 1884) and set_errcheck (line 1922) return PyResult<()> and validate their inputs, rejecting invalid values at set time. However, set_argtypes now accepts any value without validation, deferring errors until call time.

This degrades user experience by producing less clear error messages and breaks consistency with CPython's ctypes behavior.

🔧 Proposed fix to restore validation
     #[pygetset(name = "argtypes", setter)]
-    fn set_argtypes(&self, value: PyObjectRef, vm: &VirtualMachine) {
+    fn set_argtypes(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
         if vm.is_none(&value) {
             *self.argtypes.write() = None;
+        } else if value.try_to_value::<Vec<PyObjectRef>>(vm).is_ok() {
+            // Validate it's a sequence; detailed type checking happens at call time
+            *self.argtypes.write() = Some(value);
         } else {
-            // Store the argtypes object directly as it is
-            *self.argtypes.write() = Some(value);
+            return Err(vm.new_type_error("argtypes must be a sequence or None"));
         }
+        Ok(())
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_ctypes/function.rs` around lines 1906 - 1913, Change
set_argtypes back to return PyResult<()> and perform the same input validation
pattern used by set_restype and set_errcheck: if vm.is_none(&value) set
self.argtypes to None and return Ok(()); otherwise ensure value is an iterable
(reject non-iterable with vm.new_type_error) and iterate its items validating
each is either None or an acceptable ctypes type/callable (reject invalid items
with a descriptive Err). Update assignments to set self.argtypes = Some(value)
only after validation succeeds and return Ok(()).

Comment on lines +88 to 92
fn write(&self, s: PyStrRef) -> usize {
let bytes = s.as_bytes();
let _ = std::io::stderr().write_all(bytes);
Ok(bytes.len())
bytes.len()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return value is incorrect when write fails.

The method ignores write errors (line 90) but still returns bytes.len() (line 91), misleading callers into thinking all bytes were successfully written. While ignoring errors during bootstrap may be acceptable, returning an incorrect length violates the write method contract.

Proposed fix: return 0 on write failure
     #[pymethod]
     fn write(&self, s: PyStrRef) -> usize {
         let bytes = s.as_bytes();
-        let _ = std::io::stderr().write_all(bytes);
-        bytes.len()
+        std::io::stderr()
+            .write_all(bytes)
+            .map(|_| bytes.len())
+            .unwrap_or(0)
     }
📝 Committable suggestion

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

Suggested change
fn write(&self, s: PyStrRef) -> usize {
let bytes = s.as_bytes();
let _ = std::io::stderr().write_all(bytes);
Ok(bytes.len())
bytes.len()
}
#[pymethod]
fn write(&self, s: PyStrRef) -> usize {
let bytes = s.as_bytes();
std::io::stderr()
.write_all(bytes)
.map(|_| bytes.len())
.unwrap_or(0)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/sys.rs` around lines 88 - 92, The write method in sys.rs
currently ignores std::io::stderr().write_all(bytes) errors but always returns
bytes.len(), which violates the write contract; change write to check the Result
from write_all on the PyStrRef bytes and return bytes.len() only on Ok(()),
otherwise return 0 (i.e., treat a write failure as zero bytes written). Update
the function named write to capture the result (match or if let Err(_)) and
return 0 on error while preserving the existing successful return value.

@youknowone youknowone merged commit e8711ed into RustPython:main May 13, 2026
25 checks passed
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.

2 participants