Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 87 additions & 35 deletions crates/vm/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1677,7 +1677,7 @@ pub(super) mod types {
// SAFETY: slot_init is called during object initialization,
// so fields are None and swap result can be safely ignored
if len <= 5 {
// Only set errno/strerror when args len is 2-5 (CPython behavior)
// Only set errno/strerror when args len is 2-5
if 2 <= len {
let _ = unsafe { exc.errno.swap(Some(new_args.args[0].clone())) };
let _ = unsafe { exc.strerror.swap(Some(new_args.args[1].clone())) };
Expand Down Expand Up @@ -1708,53 +1708,105 @@ pub(super) mod types {
}
}

// args are truncated to 2 for compatibility (only when 2-5 args)
if (3..=5).contains(&len) {
// args are truncated to 2 for compatibility (only when 2-5 args and filename is not None)
// truncation happens inside "if (filename && filename != Py_None)" block
let has_filename = exc.filename.to_owned().filter(|f| !vm.is_none(f)).is_some();
if (3..=5).contains(&len) && has_filename {
new_args.args.truncate(2);
}
PyBaseException::slot_init(zelf, new_args, vm)
}

#[pymethod]
fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
let args = exc.args();
let obj = exc.as_object().to_owned();

let str = if args.len() == 2 {
// SAFETY: len() == 2 is checked so get_arg 1 or 2 won't panic
let errno = exc.get_arg(0).unwrap().str(vm)?;
let msg = exc.get_arg(1).unwrap().str(vm)?;

// On Windows, use [WinError X] format when winerror is set
#[cfg(windows)]
let (label, code) = match obj.get_attr("winerror", vm) {
Ok(winerror) if !vm.is_none(&winerror) => ("WinError", winerror.str(vm)?),
_ => ("Errno", errno.clone()),
};
#[cfg(not(windows))]
let (label, code) = ("Errno", errno.clone());
// Get OSError fields directly
let errno_field = obj.get_attr("errno", vm).ok().filter(|v| !vm.is_none(v));
let strerror = obj.get_attr("strerror", vm).ok().filter(|v| !vm.is_none(v));
let filename = obj.get_attr("filename", vm).ok().filter(|v| !vm.is_none(v));
let filename2 = obj
.get_attr("filename2", vm)
.ok()
.filter(|v| !vm.is_none(v));
#[cfg(windows)]
let winerror = obj.get_attr("winerror", vm).ok().filter(|v| !vm.is_none(v));

let s = match obj.get_attr("filename", vm) {
Ok(filename) if !vm.is_none(&filename) => match obj.get_attr("filename2", vm) {
Ok(filename2) if !vm.is_none(&filename2) => format!(
"[{} {}] {}: '{}' -> '{}'",
label,
// Windows: winerror takes priority over errno
#[cfg(windows)]
if let Some(ref win_err) = winerror {
let code = win_err.str(vm)?;
if let Some(ref f) = filename {
let msg = strerror
.as_ref()
.map(|s| s.str(vm))
.transpose()?
.map(|s| s.to_string())
.unwrap_or_else(|| "None".to_owned());
if let Some(ref f2) = filename2 {
return Ok(vm.ctx.new_str(format!(
"[WinError {}] {}: {} -> {}",
code,
msg,
filename.str(vm)?,
filename2.str(vm)?
),
_ => format!("[{} {}] {}: '{}'", label, code, msg, filename.str(vm)?),
},
_ => {
format!("[{label} {code}] {msg}")
f.repr(vm)?,
f2.repr(vm)?
)));
}
};
vm.ctx.new_str(s)
} else {
exc.__str__(vm)
};
Ok(str)
return Ok(vm.ctx.new_str(format!(
"[WinError {}] {}: {}",
code,
msg,
f.repr(vm)?
)));
}
// winerror && strerror (no filename)
if let Some(ref s) = strerror {
return Ok(vm
.ctx
.new_str(format!("[WinError {}] {}", code, s.str(vm)?)));
}
}

// Non-Windows or fallback: use errno
if let Some(ref f) = filename {
let errno_str = errno_field
.as_ref()
.map(|e| e.str(vm))
.transpose()?
.map(|s| s.to_string())
.unwrap_or_else(|| "None".to_owned());
let msg = strerror
.as_ref()
.map(|s| s.str(vm))
.transpose()?
.map(|s| s.to_string())
.unwrap_or_else(|| "None".to_owned());
if let Some(ref f2) = filename2 {
return Ok(vm.ctx.new_str(format!(
"[Errno {}] {}: {} -> {}",
errno_str,
msg,
f.repr(vm)?,
f2.repr(vm)?
)));
}
return Ok(vm.ctx.new_str(format!(
"[Errno {}] {}: {}",
errno_str,
msg,
f.repr(vm)?
)));
}

// errno && strerror (no filename)
if let (Some(e), Some(s)) = (&errno_field, &strerror) {
return Ok(vm
.ctx
.new_str(format!("[Errno {}] {}", e.str(vm)?, s.str(vm)?)));
}

// fallback to BaseException.__str__
Ok(exc.__str__(vm))
}

#[pymethod]
Expand Down
11 changes: 7 additions & 4 deletions crates/vm/src/stdlib/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1084,10 +1084,13 @@ mod _io {
self.write_end = buffer_size;
// TODO: BlockingIOError(errno, msg, written)
// written += self.buffer.len();
return Err(vm.new_exception_msg(
vm.ctx.exceptions.blocking_io_error.to_owned(),
"write could not complete without blocking".to_owned(),
));
return Err(vm
.new_os_subtype_error(
vm.ctx.exceptions.blocking_io_error.to_owned(),
None,
"write could not complete without blocking".to_owned(),
)
.upcast());
} else {
break;
}
Expand Down
Loading