Clippy warn on unnecessary wraps#7869
Conversation
📝 WalkthroughWalkthroughThis PR adds the Clippy lint ChangesInfallibility refactor across codegen, VM, and stdlib
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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 winPreserve 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 liftAvoid 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 withTypeError.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 liftDo 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 winExtract shared normalization logic to avoid duplicated match branches.
Line 194 and Line 223 implement the same
match formnormalization 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 valueMinor: consider a flatter form to avoid the
Some(match ...)with earlyreturn None.The current form works correctly but mixes a wrapping
Some(...)with an innerreturn None, which is slightly awkward to read. A direct match returningOptionper-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
📒 Files selected for processing (72)
Cargo.tomlcrates/codegen/src/compile.rscrates/codegen/src/string_parser.rscrates/common/src/format.rscrates/derive-impl/src/lib.rscrates/derive-impl/src/pyclass.rscrates/derive-impl/src/pymodule.rscrates/derive-impl/src/pypayload.rscrates/derive-impl/src/util.rscrates/stdlib/src/_asyncio.rscrates/stdlib/src/_opcode.rscrates/stdlib/src/_sqlite3.rscrates/stdlib/src/contextvars.rscrates/stdlib/src/grp.rscrates/stdlib/src/locale.rscrates/stdlib/src/mmap.rscrates/stdlib/src/overlapped.rscrates/stdlib/src/pyexpat.rscrates/stdlib/src/select.rscrates/stdlib/src/sha256.rscrates/stdlib/src/sha512.rscrates/stdlib/src/socket.rscrates/stdlib/src/ssl.rscrates/stdlib/src/ssl/cert.rscrates/stdlib/src/unicodedata.rscrates/vm/src/builtins/asyncgenerator.rscrates/vm/src/builtins/frame.rscrates/vm/src/builtins/function.rscrates/vm/src/builtins/genericalias.rscrates/vm/src/builtins/int.rscrates/vm/src/builtins/map.rscrates/vm/src/builtins/object.rscrates/vm/src/builtins/range.rscrates/vm/src/builtins/set.rscrates/vm/src/builtins/slice.rscrates/vm/src/builtins/str.rscrates/vm/src/builtins/type.rscrates/vm/src/builtins/zip.rscrates/vm/src/codecs.rscrates/vm/src/exceptions.rscrates/vm/src/frame.rscrates/vm/src/stdlib/_ast.rscrates/vm/src/stdlib/_codecs.rscrates/vm/src/stdlib/_collections.rscrates/vm/src/stdlib/_ctypes.rscrates/vm/src/stdlib/_ctypes/array.rscrates/vm/src/stdlib/_ctypes/base.rscrates/vm/src/stdlib/_ctypes/function.rscrates/vm/src/stdlib/_ctypes/pointer.rscrates/vm/src/stdlib/_ctypes/structure.rscrates/vm/src/stdlib/_ctypes/union.rscrates/vm/src/stdlib/_functools.rscrates/vm/src/stdlib/_imp.rscrates/vm/src/stdlib/_io.rscrates/vm/src/stdlib/_operator.rscrates/vm/src/stdlib/_signal.rscrates/vm/src/stdlib/_symtable.rscrates/vm/src/stdlib/_thread.rscrates/vm/src/stdlib/_typing.rscrates/vm/src/stdlib/builtins.rscrates/vm/src/stdlib/itertools.rscrates/vm/src/stdlib/nt.rscrates/vm/src/stdlib/os.rscrates/vm/src/stdlib/pwd.rscrates/vm/src/stdlib/sys.rscrates/vm/src/stdlib/time.rscrates/vm/src/stdlib/typevar.rscrates/vm/src/stdlib/winreg.rscrates/vm/src/types/slot.rscrates/vm/src/vm/mod.rscrates/vm/src/warn.rscrates/wasm/src/vm_class.rs
| 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(()) | ||
| } |
There was a problem hiding this comment.
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(()).
| fn write(&self, s: PyStrRef) -> usize { | ||
| let bytes = s.as_bytes(); | ||
| let _ = std::io::stderr().write_all(bytes); | ||
| Ok(bytes.len()) | ||
| bytes.len() | ||
| } |
There was a problem hiding this comment.
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.
| 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.
Summary by CodeRabbit
Resultwrappers from functions that don't propagate errors, resulting in cleaner and more maintainable code with no user-visible changes.