🌐 AI搜索 & 代理 主页
Skip to content
Draft
Prev Previous commit
Next Next commit
Allow with() on pyexception
  • Loading branch information
youknowone committed Dec 11, 2025
commit 73de1624e451f622969542d5fc4f7fdcd8880e08
37 changes: 30 additions & 7 deletions crates/derive-impl/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,14 +629,30 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R
return Ok(item.into_token_stream());
};

if !attr.is_empty() {
return Err(syn::Error::new_spanned(
&attr[0],
"#[pyexception] impl doesn't allow attrs. Use #[pyclass] instead.",
));
// Check if with(Constructor) is specified
let mut has_constructor_trait = false;
let mut extra_attrs = Vec::new();
for nested in &attr {
if let NestedMeta::Meta(Meta::List(MetaList { path, nested, .. })) = nested {
if path.is_ident("with") {
// Check if Constructor is in the list
for meta in nested {
if let NestedMeta::Meta(Meta::Path(p)) = meta
&& p.is_ident("Constructor")
{
has_constructor_trait = true;
}
}
}
extra_attrs.push(NestedMeta::Meta(Meta::List(MetaList {
path: path.clone(),
paren_token: Default::default(),
nested: nested.clone(),
})));
}
}

let mut has_slot_new = false;
let mut has_slot_new = has_constructor_trait; // If Constructor trait is used, don't generate slot_new
let mut has_slot_init = false;
let syn::ItemImpl {
generics,
Expand Down Expand Up @@ -696,8 +712,15 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R
}
}
};

let extra_attrs_tokens = if extra_attrs.is_empty() {
quote!()
} else {
quote!(, #(#extra_attrs),*)
};

Ok(quote! {
#[pyclass(flags(BASETYPE, HAS_DICT))]
#[pyclass(flags(BASETYPE, HAS_DICT) #extra_attrs_tokens)]
impl #generics #self_ty {
#(#items)*

Expand Down
65 changes: 42 additions & 23 deletions crates/vm/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1192,9 +1192,11 @@ pub(super) mod types {
use crate::object::{Traverse, TraverseFn};
#[cfg_attr(target_arch = "wasm32", allow(unused_imports))]
use crate::{
AsObject, Py, PyAtomicRef, PyObject, PyObjectRef, PyRef, PyResult, VirtualMachine,
AsObject, Py, PyAtomicRef, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
VirtualMachine,
builtins::{
PyInt, PyStrRef, PyTupleRef, PyTypeRef, traceback::PyTracebackRef, tuple::IntoPyTuple,
PyInt, PyStrRef, PyTupleRef, PyType, PyTypeRef, traceback::PyTracebackRef,
tuple::IntoPyTuple,
},
convert::ToPyResult,
function::{ArgBytesLike, FuncArgs},
Expand Down Expand Up @@ -1451,7 +1453,44 @@ pub(super) mod types {
}

// OS Errors:
#[pyexception]
impl Constructor for PyOSError {
type Args = FuncArgs;

fn py_new(_cls: &Py<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> {
let base_exception = PyBaseException::new(args.args.to_vec(), vm);
Ok(Self {
base: PyException(base_exception),
myerrno: None.into(),
strerror: None.into(),
filename: None.into(),
filename2: None.into(),
#[cfg(windows)]
winerror: None.into(),
})
}

#[cfg(not(target_arch = "wasm32"))]
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
// We need this method, because of how `CPython` copies `init`
// from `BaseException` in `SimpleExtendsException` macro.
// See: `BaseException_new`
if *cls.name() == *vm.ctx.exceptions.os_error.name() {
if let Some(error) = Self::optional_new(args.args.to_vec(), vm) {
return error.to_pyresult(vm);
}
}
let payload = Self::py_new(&cls, args, vm)?;
payload.into_ref_with_type(vm, cls).map(Into::into)
}

#[cfg(target_arch = "wasm32")]
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
let payload = Self::py_new(&cls, args, vm)?;
payload.into_ref_with_type(vm, cls).map(Into::into)
}
}

#[pyexception(with(Constructor))]
impl PyOSError {
#[cfg(not(target_arch = "wasm32"))]
fn optional_new(args: Vec<PyObjectRef>, vm: &VirtualMachine) -> Option<PyBaseExceptionRef> {
Expand All @@ -1467,26 +1506,6 @@ pub(super) mod types {
None
}
}
#[cfg(not(target_arch = "wasm32"))]
#[pyslot]
pub fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
// We need this method, because of how `CPython` copies `init`
// from `BaseException` in `SimpleExtendsException` macro.
// See: `BaseException_new`
if *cls.name() == *vm.ctx.exceptions.os_error.name() {
match Self::optional_new(args.args.to_vec(), vm) {
Some(error) => error.to_pyresult(vm),
None => PyBaseException::slot_new(cls, args, vm),
}
} else {
PyBaseException::slot_new(cls, args, vm)
}
}
#[cfg(target_arch = "wasm32")]
#[pyslot]
pub fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
PyBaseException::slot_new(cls, args, vm)
}
#[pyslot]
#[pymethod(name = "__init__")]
pub fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
Expand Down