From 1a2263903960487c038c20ba40c823739abd2788 Mon Sep 17 00:00:00 2001 From: KangDora Date: Tue, 28 Jul 2026 01:49:45 +0900 Subject: [PATCH 1/8] Add new_payload_exception helper for built-in payload exceptions --- crates/vm/src/exceptions.rs | 10 ++-------- crates/vm/src/vm/vm_new.rs | 40 ++++++++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index fe07a7e3c9e..bead07f6d13 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1385,14 +1385,8 @@ impl OSErrorBuilder { vec![strerror.to_pyobject(vm)] }; - let payload = PyOSError::py_new(&exc_type, args.clone().into(), vm) - .expect("new_os_error usage error"); - let os_error = payload - .into_ref_with_type_lazy_dict(vm, exc_type) - .expect("new_os_error usage error"); - PyOSError::slot_init(os_error.as_object().to_owned(), args.into(), vm) - .expect("new_os_error usage error"); - os_error + vm.new_payload_exception::(exc_type, args.into()) + .expect("new_os_error usage error") } } diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 48909c1a41e..65422a64589 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -12,17 +12,18 @@ use rustpython_compiler::{CompileError, ParseError}; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, builtins::{ - PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyOSError, PyStrRef, - PyType, PyTypeRef, + PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyOSError, + PyStopIteration, PyStrRef, PyType, PyTypeRef, builtin_func::PyNativeFunction, descriptor::PyMethodDescriptor, tuple::{IntoPyTuple, PyTupleRef}, }, convert::{ToPyException, ToPyObject}, exceptions::OSErrorBuilder, - function::{IntoPyNativeFn, PyMethodFlags}, + function::{FuncArgs, IntoPyNativeFn, PyMethodFlags}, scope::Scope, set_attrs, + types::{Constructor, Initializer}, vm::VirtualMachine, }; @@ -353,6 +354,19 @@ impl VirtualMachine { .expect("vm.new_exception() called with an invalid exception type") } + /// Construct a built-in exception type that carries a payload, directly + /// (`py_new` + `slot_init`), without routing through `PyType::call`. + /// Only valid for a built-in `T` whose exact type is known at compile time. + pub fn new_payload_exception(&self, cls: PyTypeRef, args: FuncArgs) -> PyResult> + where + T: Constructor + Initializer, + { + let payload = T::py_new(&cls, args.clone(), self)?; + let exc = payload.into_ref_with_type_lazy_dict(self, cls)?; + T::slot_init(exc.as_object().to_owned(), args, self)?; + Ok(exc) + } + pub fn new_os_error(&self, msg: impl ToPyObject) -> PyRef { self.new_os_subtype_error(self.ctx.exceptions.os_error.to_owned(), None, msg) .upcast() @@ -894,15 +908,17 @@ impl VirtualMachine { } pub fn new_stop_iteration(&self, value: Option) -> PyBaseExceptionRef { - let stop_iteration_error = self.ctx.exceptions.stop_iteration; - let args = if let Some(value) = value { - vec![value] - } else { - Vec::new() - }; - let exc = self.invoke_exception(stop_iteration_error, args); - - exc.expect("StopIteration is a BaseException Subclass.") + let args: FuncArgs = value.map(|v| vec![v]).unwrap_or_default().into(); + self.new_payload_exception::( + self.ctx.exceptions.stop_iteration.to_owned(), + args, + ) + .expect("StopIteration is a BaseException Subclass.") + // `PyStopIteration` -> `PyBaseException` needs an owned upcast here. + // `.upcast()` does a runtime downcast; the zero-cost `upcast_ref`/`to_base` + // only return `&Py<_>`, which doesn't fit the owned `PyBaseExceptionRef` + // return. Keeping the downcast for now. + .upcast() } fn new_downcast_error( From 82cb35da8e5fcf04b4c462bb6c502a4bbf2c7ced Mon Sep 17 00:00:00 2001 From: KangDora Date: Mon, 3 Aug 2026 13:31:04 +0900 Subject: [PATCH 2/8] Use new_payload_exception for SystemExit raise sites --- crates/vm/src/stdlib/_thread.rs | 7 ++++--- crates/vm/src/stdlib/builtins.rs | 5 +++-- crates/vm/src/stdlib/sys.rs | 9 +++++---- crates/vm/src/vm/mod.rs | 5 ++++- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/crates/vm/src/stdlib/_thread.rs b/crates/vm/src/stdlib/_thread.rs index a1942adc784..15886824488 100644 --- a/crates/vm/src/stdlib/_thread.rs +++ b/crates/vm/src/stdlib/_thread.rs @@ -21,8 +21,8 @@ pub(crate) mod _thread { use crate::{ AsObject, Py, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{ - PyBaseExceptionRef, PyDictRef, PyIntRef, PyStr, PyTupleRef, PyType, PyTypeRef, - PyUtf8StrRef, + PyBaseExceptionRef, PyDictRef, PyIntRef, PyStr, PySystemExit, PyTupleRef, PyType, + PyTypeRef, PyUtf8StrRef, }, common::{lock::PyMutex, wtf8::Wtf8Buf}, frame::FrameRef, @@ -635,7 +635,8 @@ pub(crate) mod _thread { #[pyfunction] fn exit(vm: &VirtualMachine) -> PyResult { - Err(vm.invoke_exception(vm.ctx.exceptions.system_exit, vec![])?) + Err(vm.new_payload_exception::( + vm.ctx.exceptions.system_exit.to_owned(), vec![].into())?.upcast()) } thread_local!(static SENTINELS: RefCell>> = const { RefCell::new(Vec::new()) }); diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index e329763fa45..71ceb100bde 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -10,7 +10,7 @@ mod builtins { use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ - PyByteArray, PyBytes, PyDictRef, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, + PyByteArray, PyBytes, PyDictRef, PyStr, PyStrRef, PySystemExit, PyTuple, PyTupleRef, PyType, PyUtf8StrRef, enumerate::PyReverseSequenceIterator, function::{PyCell, PyCellRef, PyFunction}, @@ -1041,7 +1041,8 @@ mod builtins { #[pyfunction] pub(super) fn exit(exit_code_arg: OptionalArg, vm: &VirtualMachine) -> PyResult { let code = exit_code_arg.unwrap_or_else(|| vm.ctx.new_int(0).into()); - Err(vm.invoke_exception(vm.ctx.exceptions.system_exit, vec![code])?) + Err(vm.new_payload_exception::( + vm.ctx.exceptions.system_exit.to_owned(), vec![code].into())?.upcast()) } #[derive(Debug, Default, FromArgs)] diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 65917865d07..29749ef7a13 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -36,8 +36,8 @@ pub mod sys { use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult, builtins::{ - PyBaseExceptionRef, PyDictRef, PyFrozenSet, PyNamespace, PyStr, PyStrRef, PyTuple, - PyTupleRef, PyTypeRef, PyUtf8StrRef, + PyBaseExceptionRef, PyDictRef, PyFrozenSet, PyNamespace, PyStr, PyStrRef, PySystemExit, + PyTuple, PyTupleRef, PyTypeRef, PyUtf8StrRef, }, common::{ ascii, @@ -776,8 +776,9 @@ pub mod sys { } else { vec![status] }; - let exc = vm.invoke_exception(vm.ctx.exceptions.system_exit, args)?; - Err(exc) + let exc = vm.new_payload_exception::( + vm.ctx.exceptions.system_exit.to_owned(), args.into())?; + Err(exc.upcast()) } #[pyfunction] diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 3062ea07f98..c31c0655b44 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -43,6 +43,8 @@ use crate::{ stdlib, warn::WarningsState, }; +#[cfg(feature = "threading")] +use crate::builtins::PySystemExit; use alloc::{borrow::Cow, collections::BTreeMap}; #[cfg(all(not(unix), feature = "threading"))] use core::ptr::NonNull; @@ -2167,7 +2169,8 @@ impl VirtualMachine { if self.state.finalizing.load(Ordering::Acquire) && !self.is_main_thread() { // once finalization starts, // non-main Python threads should stop running bytecode. - return Err(self.invoke_exception(self.ctx.exceptions.system_exit, vec![])?); + return Err(self.new_payload_exception::( + self.ctx.exceptions.system_exit.to_owned(), vec![].into())?.upcast()); } // Suspend this thread if stop-the-world is in progress From 3f9df3396c463b1572082fc89397d63a616d3073 Mon Sep 17 00:00:00 2001 From: KangDora Date: Tue, 4 Aug 2026 09:08:23 +0900 Subject: [PATCH 3/8] Use new_payload_exception for BlockingIoError raise sites --- crates/vm/src/stdlib/_io.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/vm/src/stdlib/_io.rs b/crates/vm/src/stdlib/_io.rs index ce41a942891..ab4a1dfd28a 100644 --- a/crates/vm/src/stdlib/_io.rs +++ b/crates/vm/src/stdlib/_io.rs @@ -20,7 +20,8 @@ cfg_select! { } use crate::{ - AsObject, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, builtins::PyModule, + AsObject, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, + builtins::{PyModule, PyOSError}, }; pub use _io::{OpenArgs, io_open as open}; use rustpython_host_env::io as host_io; @@ -943,14 +944,14 @@ mod _io { Some(n) => n, None => { // BlockingIOError(errno, msg, characters_written=0) - return Err(vm.invoke_exception( - vm.ctx.exceptions.blocking_io_error, + return Err(vm.new_payload_exception::( + vm.ctx.exceptions.blocking_io_error.to_owned(), vec![ vm.new_pyobj(EAGAIN), vm.new_pyobj("write could not complete without blocking"), vm.new_pyobj(0), - ], - )?); + ].into(), + )?.upcast()); } }; self.write_pos += n as Offset; @@ -1154,14 +1155,14 @@ mod _io { self.buffer[self.write_end as usize..][..avail].copy_from_slice(&buf[..avail]); self.write_end += avail as Offset; self.pos += avail as Offset; - return Err(vm.invoke_exception( - vm.ctx.exceptions.blocking_io_error, + return Err(vm.new_payload_exception::( + vm.ctx.exceptions.blocking_io_error.to_owned(), vec![ vm.new_pyobj(EAGAIN), vm.new_pyobj("write could not complete without blocking"), vm.new_pyobj(avail), - ], - )?); + ].into(), + )?.upcast()); } Err(e) => return Err(e), } @@ -1200,14 +1201,14 @@ mod _io { self.write_end = buffer_size; // BlockingIOError(errno, msg, characters_written) let chars_written = written + buffer_len; - return Err(vm.invoke_exception( - vm.ctx.exceptions.blocking_io_error, + return Err(vm.new_payload_exception::( + vm.ctx.exceptions.blocking_io_error.to_owned(), vec![ vm.new_pyobj(EAGAIN), vm.new_pyobj("write could not complete without blocking"), vm.new_pyobj(chars_written), - ], - )?); + ].into(), + )?.upcast()); } None => break, } From 689879bc82c309ab1081bdf6728a2734340fc595 Mon Sep 17 00:00:00 2001 From: KangDora Date: Tue, 4 Aug 2026 11:15:56 +0900 Subject: [PATCH 4/8] Use new_payload_exception for OSError errno dispatch in slot_new --- crates/vm/src/exceptions.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index bead07f6d13..dabcae84289 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -2138,7 +2138,9 @@ pub(super) mod types { .downcast_ref::() .and_then(|errno| errno.try_to_primitive::(vm).ok()) .and_then(|errno| super::errno_to_exc_type(errno, vm)) - .and_then(|typ| vm.invoke_exception(typ, args_vec).ok()) + .and_then(|typ| vm.new_payload_exception::( + typ.to_owned(), args_vec.into()).ok() + ) { return error.to_pyresult(vm); } From c373a0217af732704c7ca3793bf1607105c8a841 Mon Sep 17 00:00:00 2001 From: KangDora Date: Tue, 4 Aug 2026 11:24:40 +0900 Subject: [PATCH 5/8] Run rustfmt --- crates/vm/src/exceptions.rs | 7 ++-- crates/vm/src/stdlib/_io.rs | 57 ++++++++++++++++++-------------- crates/vm/src/stdlib/_thread.rs | 8 +++-- crates/vm/src/stdlib/builtins.rs | 12 ++++--- crates/vm/src/stdlib/sys.rs | 4 ++- crates/vm/src/vm/mod.rs | 12 ++++--- 6 files changed, 62 insertions(+), 38 deletions(-) diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index dabcae84289..afda53dfa21 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -2138,9 +2138,10 @@ pub(super) mod types { .downcast_ref::() .and_then(|errno| errno.try_to_primitive::(vm).ok()) .and_then(|errno| super::errno_to_exc_type(errno, vm)) - .and_then(|typ| vm.new_payload_exception::( - typ.to_owned(), args_vec.into()).ok() - ) + .and_then(|typ| { + vm.new_payload_exception::(typ.to_owned(), args_vec.into()) + .ok() + }) { return error.to_pyresult(vm); } diff --git a/crates/vm/src/stdlib/_io.rs b/crates/vm/src/stdlib/_io.rs index ab4a1dfd28a..1bcfa169fc3 100644 --- a/crates/vm/src/stdlib/_io.rs +++ b/crates/vm/src/stdlib/_io.rs @@ -944,14 +944,17 @@ mod _io { Some(n) => n, None => { // BlockingIOError(errno, msg, characters_written=0) - return Err(vm.new_payload_exception::( - vm.ctx.exceptions.blocking_io_error.to_owned(), - vec![ - vm.new_pyobj(EAGAIN), - vm.new_pyobj("write could not complete without blocking"), - vm.new_pyobj(0), - ].into(), - )?.upcast()); + return Err(vm + .new_payload_exception::( + vm.ctx.exceptions.blocking_io_error.to_owned(), + vec![ + vm.new_pyobj(EAGAIN), + vm.new_pyobj("write could not complete without blocking"), + vm.new_pyobj(0), + ] + .into(), + )? + .upcast()); } }; self.write_pos += n as Offset; @@ -1155,14 +1158,17 @@ mod _io { self.buffer[self.write_end as usize..][..avail].copy_from_slice(&buf[..avail]); self.write_end += avail as Offset; self.pos += avail as Offset; - return Err(vm.new_payload_exception::( - vm.ctx.exceptions.blocking_io_error.to_owned(), - vec![ - vm.new_pyobj(EAGAIN), - vm.new_pyobj("write could not complete without blocking"), - vm.new_pyobj(avail), - ].into(), - )?.upcast()); + return Err(vm + .new_payload_exception::( + vm.ctx.exceptions.blocking_io_error.to_owned(), + vec![ + vm.new_pyobj(EAGAIN), + vm.new_pyobj("write could not complete without blocking"), + vm.new_pyobj(avail), + ] + .into(), + )? + .upcast()); } Err(e) => return Err(e), } @@ -1201,14 +1207,17 @@ mod _io { self.write_end = buffer_size; // BlockingIOError(errno, msg, characters_written) let chars_written = written + buffer_len; - return Err(vm.new_payload_exception::( - vm.ctx.exceptions.blocking_io_error.to_owned(), - vec![ - vm.new_pyobj(EAGAIN), - vm.new_pyobj("write could not complete without blocking"), - vm.new_pyobj(chars_written), - ].into(), - )?.upcast()); + return Err(vm + .new_payload_exception::( + vm.ctx.exceptions.blocking_io_error.to_owned(), + vec![ + vm.new_pyobj(EAGAIN), + vm.new_pyobj("write could not complete without blocking"), + vm.new_pyobj(chars_written), + ] + .into(), + )? + .upcast()); } None => break, } diff --git a/crates/vm/src/stdlib/_thread.rs b/crates/vm/src/stdlib/_thread.rs index 15886824488..dbc1c7aa270 100644 --- a/crates/vm/src/stdlib/_thread.rs +++ b/crates/vm/src/stdlib/_thread.rs @@ -635,8 +635,12 @@ pub(crate) mod _thread { #[pyfunction] fn exit(vm: &VirtualMachine) -> PyResult { - Err(vm.new_payload_exception::( - vm.ctx.exceptions.system_exit.to_owned(), vec![].into())?.upcast()) + Err(vm + .new_payload_exception::( + vm.ctx.exceptions.system_exit.to_owned(), + vec![].into(), + )? + .upcast()) } thread_local!(static SENTINELS: RefCell>> = const { RefCell::new(Vec::new()) }); diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 71ceb100bde..f8bfcf7a16f 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -10,8 +10,8 @@ mod builtins { use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ - PyByteArray, PyBytes, PyDictRef, PyStr, PyStrRef, PySystemExit, PyTuple, PyTupleRef, PyType, - PyUtf8StrRef, + PyByteArray, PyBytes, PyDictRef, PyStr, PyStrRef, PySystemExit, PyTuple, PyTupleRef, + PyType, PyUtf8StrRef, enumerate::PyReverseSequenceIterator, function::{PyCell, PyCellRef, PyFunction}, int::PyIntRef, @@ -1041,8 +1041,12 @@ mod builtins { #[pyfunction] pub(super) fn exit(exit_code_arg: OptionalArg, vm: &VirtualMachine) -> PyResult { let code = exit_code_arg.unwrap_or_else(|| vm.ctx.new_int(0).into()); - Err(vm.new_payload_exception::( - vm.ctx.exceptions.system_exit.to_owned(), vec![code].into())?.upcast()) + Err(vm + .new_payload_exception::( + vm.ctx.exceptions.system_exit.to_owned(), + vec![code].into(), + )? + .upcast()) } #[derive(Debug, Default, FromArgs)] diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 29749ef7a13..122c2370a21 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -777,7 +777,9 @@ pub mod sys { vec![status] }; let exc = vm.new_payload_exception::( - vm.ctx.exceptions.system_exit.to_owned(), args.into())?; + vm.ctx.exceptions.system_exit.to_owned(), + args.into(), + )?; Err(exc.upcast()) } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index c31c0655b44..bc635cfa82f 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -19,6 +19,8 @@ mod vm_new; mod vm_object; mod vm_ops; +#[cfg(feature = "threading")] +use crate::builtins::PySystemExit; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, builtins::{ @@ -43,8 +45,6 @@ use crate::{ stdlib, warn::WarningsState, }; -#[cfg(feature = "threading")] -use crate::builtins::PySystemExit; use alloc::{borrow::Cow, collections::BTreeMap}; #[cfg(all(not(unix), feature = "threading"))] use core::ptr::NonNull; @@ -2169,8 +2169,12 @@ impl VirtualMachine { if self.state.finalizing.load(Ordering::Acquire) && !self.is_main_thread() { // once finalization starts, // non-main Python threads should stop running bytecode. - return Err(self.new_payload_exception::( - self.ctx.exceptions.system_exit.to_owned(), vec![].into())?.upcast()); + return Err(self + .new_payload_exception::( + self.ctx.exceptions.system_exit.to_owned(), + vec![].into(), + )? + .upcast()); } // Suspend this thread if stop-the-world is in progress From 6e2802986fcc6918bd49440b7d96304f926856b1 Mon Sep 17 00:00:00 2001 From: KangDora Date: Tue, 4 Aug 2026 11:29:56 +0900 Subject: [PATCH 6/8] Run clippy --- crates/vm/src/exceptions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index afda53dfa21..da86cafc78b 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -2139,7 +2139,7 @@ pub(super) mod types { .and_then(|errno| errno.try_to_primitive::(vm).ok()) .and_then(|errno| super::errno_to_exc_type(errno, vm)) .and_then(|typ| { - vm.new_payload_exception::(typ.to_owned(), args_vec.into()) + vm.new_payload_exception::(typ.to_owned(), args_vec.into()) .ok() }) { From abad71822ca39580a6e4c26a0df198e9665c29be Mon Sep 17 00:00:00 2001 From: KangDora Date: Tue, 4 Aug 2026 14:31:54 +0900 Subject: [PATCH 7/8] Add type guard to new_payload_exception and extract new_system_exit --- crates/vm/src/stdlib/_thread.rs | 3 ++- crates/vm/src/stdlib/builtins.rs | 3 ++- crates/vm/src/stdlib/sys.rs | 10 ++++++---- crates/vm/src/vm/mod.rs | 3 ++- crates/vm/src/vm/vm_new.rs | 7 +++++++ 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/crates/vm/src/stdlib/_thread.rs b/crates/vm/src/stdlib/_thread.rs index dbc1c7aa270..f66313aab8b 100644 --- a/crates/vm/src/stdlib/_thread.rs +++ b/crates/vm/src/stdlib/_thread.rs @@ -639,7 +639,8 @@ pub(crate) mod _thread { .new_payload_exception::( vm.ctx.exceptions.system_exit.to_owned(), vec![].into(), - )? + ) + .expect("SystemExit is a BaseException Subclass.") .upcast()) } diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index f8bfcf7a16f..b232fdf3f63 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -1045,7 +1045,8 @@ mod builtins { .new_payload_exception::( vm.ctx.exceptions.system_exit.to_owned(), vec![code].into(), - )? + ) + .expect("SystemExit is a BaseException Subclass.") .upcast()) } diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 122c2370a21..4548ec122f1 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -776,10 +776,12 @@ pub mod sys { } else { vec![status] }; - let exc = vm.new_payload_exception::( - vm.ctx.exceptions.system_exit.to_owned(), - args.into(), - )?; + let exc = vm + .new_payload_exception::( + vm.ctx.exceptions.system_exit.to_owned(), + args.into(), + ) + .expect("SystemExit is a BaseException Subclass."); Err(exc.upcast()) } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index bc635cfa82f..8261d9a591d 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -2173,7 +2173,8 @@ impl VirtualMachine { .new_payload_exception::( self.ctx.exceptions.system_exit.to_owned(), vec![].into(), - )? + ) + .expect("SystemExit is a BaseException Subclass.") .upcast()); } diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 65422a64589..acd9f00134f 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -361,6 +361,13 @@ impl VirtualMachine { where T: Constructor + Initializer, { + debug_assert_eq!( + cls.slots.basicsize, + size_of::(), + "vm.new_payload_exception::<{}>() called with mismatched type '{}'", + core::any::type_name::(), + cls.name() + ); let payload = T::py_new(&cls, args.clone(), self)?; let exc = payload.into_ref_with_type_lazy_dict(self, cls)?; T::slot_init(exc.as_object().to_owned(), args, self)?; From 50e160e64a80374e662a66b77b72913a6aaa41fa Mon Sep 17 00:00:00 2001 From: KangDora Date: Fri, 7 Aug 2026 11:39:11 +0900 Subject: [PATCH 8/8] Extract new_system_exit and fix stale expect messages The four SystemExit raise sites repeated the same construction, so route them through a new_system_exit helper next to new_stop_iteration. The expect messages still described invoke_exception's downcast failure, which no longer applies after moving to new_payload_exception. --- crates/vm/src/stdlib/_thread.rs | 12 +++--------- crates/vm/src/stdlib/builtins.rs | 12 +++--------- crates/vm/src/stdlib/sys.rs | 12 +++--------- crates/vm/src/vm/mod.rs | 10 +--------- crates/vm/src/vm/vm_new.rs | 10 ++++++++-- 5 files changed, 18 insertions(+), 38 deletions(-) diff --git a/crates/vm/src/stdlib/_thread.rs b/crates/vm/src/stdlib/_thread.rs index f66313aab8b..2169f2357de 100644 --- a/crates/vm/src/stdlib/_thread.rs +++ b/crates/vm/src/stdlib/_thread.rs @@ -21,8 +21,8 @@ pub(crate) mod _thread { use crate::{ AsObject, Py, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{ - PyBaseExceptionRef, PyDictRef, PyIntRef, PyStr, PySystemExit, PyTupleRef, PyType, - PyTypeRef, PyUtf8StrRef, + PyBaseExceptionRef, PyDictRef, PyIntRef, PyStr, PyTupleRef, PyType, PyTypeRef, + PyUtf8StrRef, }, common::{lock::PyMutex, wtf8::Wtf8Buf}, frame::FrameRef, @@ -635,13 +635,7 @@ pub(crate) mod _thread { #[pyfunction] fn exit(vm: &VirtualMachine) -> PyResult { - Err(vm - .new_payload_exception::( - vm.ctx.exceptions.system_exit.to_owned(), - vec![].into(), - ) - .expect("SystemExit is a BaseException Subclass.") - .upcast()) + Err(vm.new_system_exit(vec![].into())) } thread_local!(static SENTINELS: RefCell>> = const { RefCell::new(Vec::new()) }); diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index b232fdf3f63..8fca87c8cca 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -10,8 +10,8 @@ mod builtins { use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ - PyByteArray, PyBytes, PyDictRef, PyStr, PyStrRef, PySystemExit, PyTuple, PyTupleRef, - PyType, PyUtf8StrRef, + PyByteArray, PyBytes, PyDictRef, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, + PyUtf8StrRef, enumerate::PyReverseSequenceIterator, function::{PyCell, PyCellRef, PyFunction}, int::PyIntRef, @@ -1041,13 +1041,7 @@ mod builtins { #[pyfunction] pub(super) fn exit(exit_code_arg: OptionalArg, vm: &VirtualMachine) -> PyResult { let code = exit_code_arg.unwrap_or_else(|| vm.ctx.new_int(0).into()); - Err(vm - .new_payload_exception::( - vm.ctx.exceptions.system_exit.to_owned(), - vec![code].into(), - ) - .expect("SystemExit is a BaseException Subclass.") - .upcast()) + Err(vm.new_system_exit(vec![code].into())) } #[derive(Debug, Default, FromArgs)] diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 4548ec122f1..fc01ab49f0d 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -36,8 +36,8 @@ pub mod sys { use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult, builtins::{ - PyBaseExceptionRef, PyDictRef, PyFrozenSet, PyNamespace, PyStr, PyStrRef, PySystemExit, - PyTuple, PyTupleRef, PyTypeRef, PyUtf8StrRef, + PyBaseExceptionRef, PyDictRef, PyFrozenSet, PyNamespace, PyStr, PyStrRef, PyTuple, + PyTupleRef, PyTypeRef, PyUtf8StrRef, }, common::{ ascii, @@ -776,13 +776,7 @@ pub mod sys { } else { vec![status] }; - let exc = vm - .new_payload_exception::( - vm.ctx.exceptions.system_exit.to_owned(), - args.into(), - ) - .expect("SystemExit is a BaseException Subclass."); - Err(exc.upcast()) + Err(vm.new_system_exit(args.into())) } #[pyfunction] diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 8261d9a591d..bc8b5f18612 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -19,8 +19,6 @@ mod vm_new; mod vm_object; mod vm_ops; -#[cfg(feature = "threading")] -use crate::builtins::PySystemExit; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, builtins::{ @@ -2169,13 +2167,7 @@ impl VirtualMachine { if self.state.finalizing.load(Ordering::Acquire) && !self.is_main_thread() { // once finalization starts, // non-main Python threads should stop running bytecode. - return Err(self - .new_payload_exception::( - self.ctx.exceptions.system_exit.to_owned(), - vec![].into(), - ) - .expect("SystemExit is a BaseException Subclass.") - .upcast()); + return Err(self.new_system_exit(vec![].into())); } // Suspend this thread if stop-the-world is in progress diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index acd9f00134f..509b6495b3c 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -13,7 +13,7 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, builtins::{ PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyOSError, - PyStopIteration, PyStrRef, PyType, PyTypeRef, + PyStopIteration, PyStrRef, PySystemExit, PyType, PyTypeRef, builtin_func::PyNativeFunction, descriptor::PyMethodDescriptor, tuple::{IntoPyTuple, PyTupleRef}, @@ -914,13 +914,19 @@ impl VirtualMachine { exc } + pub fn new_system_exit(&self, args: FuncArgs) -> PyBaseExceptionRef { + self.new_payload_exception::(self.ctx.exceptions.system_exit.to_owned(), args) + .expect("SystemExit construction from internal args is infallible") + .upcast() + } + pub fn new_stop_iteration(&self, value: Option) -> PyBaseExceptionRef { let args: FuncArgs = value.map(|v| vec![v]).unwrap_or_default().into(); self.new_payload_exception::( self.ctx.exceptions.stop_iteration.to_owned(), args, ) - .expect("StopIteration is a BaseException Subclass.") + .expect("StopIteration construction from internal args is infallible") // `PyStopIteration` -> `PyBaseException` needs an owned upcast here. // `.upcast()` does a runtime downcast; the zero-cost `upcast_ref`/`to_base` // only return `&Py<_>`, which doesn't fit the owned `PyBaseExceptionRef`