From 364756e17497b8f79bd1a676f2d08c1c5905018e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 16 Dec 2025 14:25:45 +0900 Subject: [PATCH 1/3] disallow __new__, __init__ --- crates/derive-impl/src/pyclass.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index f784a2e2a7..2d878b3b1c 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -63,6 +63,7 @@ impl FromStr for AttrName { #[derive(Default)] struct ImplContext { + is_trait: bool, attribute_items: ItemNursery, method_items: MethodNursery, getset_items: GetSetNursery, @@ -232,7 +233,10 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul } } Item::Trait(mut trai) => { - let mut context = ImplContext::default(); + let mut context = ImplContext { + is_trait: true, + ..Default::default() + }; let mut has_extend_slots = false; for item in &trai.items { let has = match item { @@ -892,6 +896,23 @@ where let item_meta = MethodItemMeta::from_attr(ident.clone(), &item_attr)?; let py_name = item_meta.method_name()?; + + // Disallow __new__ and __init__ as pymethod in impl blocks (not in traits) + if !args.context.is_trait { + if py_name == "__new__" { + return Err(syn::Error::new( + ident.span(), + "#[pymethod] cannot define '__new__'. Use #[pyclass(with(Constructor))] instead.", + )); + } + if py_name == "__init__" { + return Err(syn::Error::new( + ident.span(), + "#[pymethod] cannot define '__init__'. Use #[pyclass(with(Initializer))] instead.", + )); + } + } + let raw = item_meta.raw()?; let sig_doc = text_signature(func.sig(), &py_name); From 0d791f913e0e7cc5d7b4f6e6e0a84af323dd1c17 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 16 Dec 2025 23:09:50 +0900 Subject: [PATCH 2/3] migrate to Initializer --- crates/derive-impl/src/pyclass.rs | 107 +++++----- crates/stdlib/src/sqlite.rs | 41 ++-- crates/stdlib/src/ssl/error.rs | 2 +- crates/vm/src/builtins/object.rs | 32 +-- crates/vm/src/exception_group.rs | 242 ++++++++++++----------- crates/vm/src/exceptions.rs | 302 ++++++++++++++++------------- crates/vm/src/stdlib/ast/python.rs | 78 ++++---- 7 files changed, 437 insertions(+), 367 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 2d878b3b1c..dcf3378fa8 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -714,21 +714,16 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R }; // Check if with(Constructor) is specified. If Constructor trait is used, don't generate slot_new - let mut has_slot_new = false; - let mut extra_attrs = Vec::new(); + let mut with_items = vec![]; for nested in &attr { if let NestedMeta::Meta(Meta::List(MetaList { path, nested, .. })) = nested { // If we already found the constructor trait, no need to keep looking for it - if !has_slot_new && path.is_ident("with") { - // Check if Constructor is in the list + if path.is_ident("with") { for meta in nested { - if let NestedMeta::Meta(Meta::Path(p)) = meta - && p.is_ident("Constructor") - { - has_slot_new = true; - } + with_items.push(meta.get_ident().expect("with() has non-ident item").clone()); } + continue; } extra_attrs.push(NestedMeta::Meta(Meta::List(MetaList { path: path.clone(), @@ -738,43 +733,45 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R } } - let mut has_slot_init = false; + let with_contains = |with_items: &[Ident], s: &str| { + // Check if Constructor is in the list + for ident in with_items { + if ident.to_string().as_str() == s { + return true; + } + } + false + }; + let syn::ItemImpl { generics, self_ty, items, .. } = &imp; - for item in items { - // FIXME: better detection or correct wrapper implementation - let Some(ident) = item.get_ident() else { - continue; - }; - let item_name = ident.to_string(); - match item_name.as_str() { - "slot_new" => { - has_slot_new = true; - } - "slot_init" => { - has_slot_init = true; - } - _ => continue, - } - } - // TODO: slot_new, slot_init must be Constructor or Initializer later - - let slot_new = if has_slot_new { + let slot_new = if with_contains(&with_items, "Constructor") { quote!() } else { + with_items.push(Ident::new("Constructor", Span::call_site())); quote! { - #[pyslot] - pub fn slot_new( - cls: ::rustpython_vm::builtins::PyTypeRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult { - ::Base::slot_new(cls, args, vm) + impl ::rustpython_vm::types::Constructor for #self_ty { + type Args = ::rustpython_vm::function::FuncArgs; + + fn slot_new( + cls: ::rustpython_vm::builtins::PyTypeRef, + args: ::rustpython_vm::function::FuncArgs, + vm: &::rustpython_vm::VirtualMachine, + ) -> ::rustpython_vm::PyResult { + ::Base::slot_new(cls, args, vm) + } + fn py_new( + _cls: &::rustpython_vm::Py<::rustpython_vm::builtins::PyType>, + _args: Self::Args, + _vm: &VirtualMachine + ) -> PyResult { + unreachable!("slot_new is defined") + } } } }; @@ -783,19 +780,29 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R // from `BaseException` in `SimpleExtendsException` macro. // See: `(initproc)BaseException_init` // spell-checker:ignore initproc - let slot_init = if has_slot_init { + let slot_init = if with_contains(&with_items, "Initializer") { quote!() } else { - // FIXME: this is a generic logic for types not only for exceptions + with_items.push(Ident::new("Initializer", Span::call_site())); quote! { - #[pyslot] - #[pymethod(name="__init__")] - pub fn slot_init( - zelf: ::rustpython_vm::PyObjectRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult<()> { - ::Base::slot_init(zelf, args, vm) + impl ::rustpython_vm::types::Initializer for #self_ty { + type Args = ::rustpython_vm::function::FuncArgs; + + fn slot_init( + zelf: ::rustpython_vm::PyObjectRef, + args: ::rustpython_vm::function::FuncArgs, + vm: &::rustpython_vm::VirtualMachine, + ) -> PyResult<()> { + ::Base::slot_init(zelf, args, vm) + } + + fn init( + _zelf: ::rustpython_vm::PyRef, + _args: Self::Args, + _vm: &VirtualMachine + ) -> PyResult<()> { + unreachable!("slot_init is defined") + } } } }; @@ -807,13 +814,13 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R }; Ok(quote! { - #[pyclass(flags(BASETYPE, HAS_DICT) #extra_attrs_tokens)] + #[pyclass(flags(BASETYPE, HAS_DICT), with(#(#with_items),*) #extra_attrs_tokens)] impl #generics #self_ty { #(#items)* - - #slot_new - #slot_init } + + #slot_new + #slot_init }) } diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index bc84cffbf8..f2a8ea69c3 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -1540,7 +1540,7 @@ mod _sqlite { size: Option, } - #[pyclass(with(Constructor, IterNext, Iterable), flags(BASETYPE))] + #[pyclass(with(Constructor, Initializer, IterNext, Iterable), flags(BASETYPE))] impl Cursor { fn new( connection: PyRef, @@ -1571,24 +1571,6 @@ mod _sqlite { } } - #[pymethod] - fn __init__(&self, _connection: PyRef, _vm: &VirtualMachine) -> PyResult<()> { - let mut guard = self.inner.lock(); - if guard.is_some() { - // Already initialized (e.g., from a call to super().__init__) - return Ok(()); - } - *guard = Some(CursorInner { - description: None, - row_cast_map: vec![], - lastrowid: -1, - rowcount: -1, - statement: None, - closed: false, - }); - Ok(()) - } - fn check_cursor_state(inner: Option<&CursorInner>, vm: &VirtualMachine) -> PyResult<()> { match inner { Some(inner) if inner.closed => Err(new_programming_error( @@ -1949,6 +1931,27 @@ mod _sqlite { } } + impl Initializer for Cursor { + type Args = FuncArgs; + + fn init(zelf: PyRef, _connection: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { + let mut guard = zelf.inner.lock(); + if guard.is_some() { + // Already initialized (e.g., from a call to super().__init__) + return Ok(()); + } + *guard = Some(CursorInner { + description: None, + row_cast_map: vec![], + lastrowid: -1, + rowcount: -1, + statement: None, + closed: false, + }); + Ok(()) + } + } + impl SelfIter for Cursor {} impl IterNext for Cursor { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { diff --git a/crates/stdlib/src/ssl/error.rs b/crates/stdlib/src/ssl/error.rs index bef9ba513d..879275228e 100644 --- a/crates/stdlib/src/ssl/error.rs +++ b/crates/stdlib/src/ssl/error.rs @@ -7,7 +7,7 @@ pub(crate) mod ssl_error { use crate::vm::{ PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyBaseExceptionRef, PyOSError, PyStrRef}, - types::Constructor, + types::{Constructor, Initializer}, }; // Error type constants - exposed as pyattr and available for internal use diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index cb95652f93..0970496c7b 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -2,11 +2,11 @@ use super::{PyDictRef, PyList, PyStr, PyStrRef, PyType, PyTypeRef}; use crate::common::hash::PyHash; use crate::types::PyTypeFlags; use crate::{ - AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, + AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, convert::ToPyResult, function::{Either, FuncArgs, PyArithmeticValue, PyComparisonValue, PySetterValue}, - types::{Constructor, PyComparisonOp}, + types::{Constructor, Initializer, PyComparisonOp}, }; use itertools::Itertools; @@ -115,6 +115,18 @@ impl Constructor for PyBaseObject { } } +impl Initializer for PyBaseObject { + type Args = FuncArgs; + + fn slot_init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { + Ok(()) + } + + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } +} + // TODO: implement _PyType_GetSlotNames properly fn type_slot_names(typ: &Py, vm: &VirtualMachine) -> PyResult> { // let attributes = typ.attributes.read(); @@ -235,7 +247,7 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine) // getstate.call((), vm) // } -#[pyclass(with(Constructor), flags(BASETYPE))] +#[pyclass(with(Constructor, Initializer), flags(BASETYPE))] impl PyBaseObject { #[pymethod(raw)] fn __getstate__(vm: &VirtualMachine, args: FuncArgs) -> PyResult { @@ -444,19 +456,17 @@ impl PyBaseObject { obj.str(vm) } - #[pyslot] - #[pymethod] - fn __init__(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { - Ok(()) - } - #[pygetset] fn __class__(obj: PyObjectRef) -> PyTypeRef { obj.class().to_owned() } - #[pygetset(name = "__class__", setter)] - fn set_class(instance: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + #[pygetset(setter)] + fn set___class__( + instance: PyObjectRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { match value.downcast::() { Ok(cls) => { let both_module = instance.class().fast_issubclass(vm.ctx.types.module_type) diff --git a/crates/vm/src/exception_group.rs b/crates/vm/src/exception_group.rs index cd943ae1bd..61b67d79e9 100644 --- a/crates/vm/src/exception_group.rs +++ b/crates/vm/src/exception_group.rs @@ -43,13 +43,14 @@ pub(super) mod types { use super::*; use crate::PyPayload; use crate::builtins::PyGenericAlias; + use crate::types::{Constructor, Initializer}; #[pyexception(name, base = PyBaseException, ctx = "base_exception_group")] #[derive(Debug)] #[repr(transparent)] pub struct PyBaseExceptionGroup(PyBaseException); - #[pyexception] + #[pyexception(with(Constructor, Initializer))] impl PyBaseExceptionGroup { #[pyclassmethod] fn __class_getitem__( @@ -60,117 +61,6 @@ pub(super) mod types { PyGenericAlias::from_args(cls, args, vm) } - #[pyslot] - fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // Validate exactly 2 positional arguments - if args.args.len() != 2 { - return Err(vm.new_type_error(format!( - "BaseExceptionGroup.__new__() takes exactly 2 positional arguments ({} given)", - args.args.len() - ))); - } - - // Validate message is str - let message = args.args[0].clone(); - if !message.fast_isinstance(vm.ctx.types.str_type) { - return Err(vm.new_type_error(format!( - "argument 1 must be str, not {}", - message.class().name() - ))); - } - - // Validate exceptions is a sequence (not set or None) - let exceptions_arg = &args.args[1]; - - // Check for set/frozenset (not a sequence - unordered) - if exceptions_arg.fast_isinstance(vm.ctx.types.set_type) - || exceptions_arg.fast_isinstance(vm.ctx.types.frozenset_type) - { - return Err(vm.new_type_error("second argument (exceptions) must be a sequence")); - } - - // Check for None - if exceptions_arg.is(&vm.ctx.none) { - return Err(vm.new_type_error("second argument (exceptions) must be a sequence")); - } - - let exceptions: Vec = exceptions_arg.try_to_value(vm).map_err(|_| { - vm.new_type_error("second argument (exceptions) must be a sequence") - })?; - - // Validate non-empty - if exceptions.is_empty() { - return Err(vm.new_value_error( - "second argument (exceptions) must be a non-empty sequence".to_owned(), - )); - } - - // Validate all items are BaseException instances - let mut has_non_exception = false; - for (i, exc) in exceptions.iter().enumerate() { - if !exc.fast_isinstance(vm.ctx.exceptions.base_exception_type) { - return Err(vm.new_value_error(format!( - "Item {} of second argument (exceptions) is not an exception", - i - ))); - } - // Check if any exception is not an Exception subclass - // With dynamic ExceptionGroup (inherits from both BaseExceptionGroup and Exception), - // ExceptionGroup instances are automatically instances of Exception - if !exc.fast_isinstance(vm.ctx.exceptions.exception_type) { - has_non_exception = true; - } - } - - // Get the dynamic ExceptionGroup type - let exception_group_type = crate::exception_group::exception_group(); - - // Determine the actual class to use - let actual_cls = if cls.is(exception_group_type) { - // ExceptionGroup cannot contain BaseExceptions that are not Exception - if has_non_exception { - return Err( - vm.new_type_error("Cannot nest BaseExceptions in an ExceptionGroup") - ); - } - cls - } else if cls.is(vm.ctx.exceptions.base_exception_group) { - // Auto-convert to ExceptionGroup if all are Exception subclasses - if !has_non_exception { - exception_group_type.to_owned() - } else { - cls - } - } else { - // User-defined subclass - if has_non_exception && cls.fast_issubclass(vm.ctx.exceptions.exception_type) { - return Err(vm.new_type_error(format!( - "Cannot nest BaseExceptions in '{}'", - cls.name() - ))); - } - cls - }; - - // Create the exception with (message, exceptions_tuple) as args - let exceptions_tuple = vm.ctx.new_tuple(exceptions); - let init_args = vec![message, exceptions_tuple.into()]; - PyBaseException::new(init_args, vm) - .into_ref_with_type(vm, actual_cls) - .map(Into::into) - } - - #[pyslot] - #[pymethod(name = "__init__")] - fn slot_init(_zelf: PyObjectRef, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { - // CPython's BaseExceptionGroup.__init__ just calls BaseException.__init__ - // which stores args as-is. Since __new__ already set up the correct args - // (message, exceptions_tuple), we don't need to do anything here. - // This also allows subclasses to pass extra arguments to __new__ without - // __init__ complaining about argument count. - Ok(()) - } - #[pymethod] fn derive( zelf: PyRef, @@ -351,6 +241,134 @@ pub(super) mod types { } } + impl Constructor for PyBaseExceptionGroup { + type Args = FuncArgs; + + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // Validate exactly 2 positional arguments + if args.args.len() != 2 { + return Err(vm.new_type_error(format!( + "BaseExceptionGroup.__new__() takes exactly 2 positional arguments ({} given)", + args.args.len() + ))); + } + + // Validate message is str + let message = args.args[0].clone(); + if !message.fast_isinstance(vm.ctx.types.str_type) { + return Err(vm.new_type_error(format!( + "argument 1 must be str, not {}", + message.class().name() + ))); + } + + // Validate exceptions is a sequence (not set or None) + let exceptions_arg = &args.args[1]; + + // Check for set/frozenset (not a sequence - unordered) + if exceptions_arg.fast_isinstance(vm.ctx.types.set_type) + || exceptions_arg.fast_isinstance(vm.ctx.types.frozenset_type) + { + return Err(vm.new_type_error("second argument (exceptions) must be a sequence")); + } + + // Check for None + if exceptions_arg.is(&vm.ctx.none) { + return Err(vm.new_type_error("second argument (exceptions) must be a sequence")); + } + + let exceptions: Vec = exceptions_arg.try_to_value(vm).map_err(|_| { + vm.new_type_error("second argument (exceptions) must be a sequence") + })?; + + // Validate non-empty + if exceptions.is_empty() { + return Err(vm.new_value_error( + "second argument (exceptions) must be a non-empty sequence".to_owned(), + )); + } + + // Validate all items are BaseException instances + let mut has_non_exception = false; + for (i, exc) in exceptions.iter().enumerate() { + if !exc.fast_isinstance(vm.ctx.exceptions.base_exception_type) { + return Err(vm.new_value_error(format!( + "Item {} of second argument (exceptions) is not an exception", + i + ))); + } + // Check if any exception is not an Exception subclass + // With dynamic ExceptionGroup (inherits from both BaseExceptionGroup and Exception), + // ExceptionGroup instances are automatically instances of Exception + if !exc.fast_isinstance(vm.ctx.exceptions.exception_type) { + has_non_exception = true; + } + } + + // Get the dynamic ExceptionGroup type + let exception_group_type = crate::exception_group::exception_group(); + + // Determine the actual class to use + let actual_cls = if cls.is(exception_group_type) { + // ExceptionGroup cannot contain BaseExceptions that are not Exception + if has_non_exception { + return Err( + vm.new_type_error("Cannot nest BaseExceptions in an ExceptionGroup") + ); + } + cls + } else if cls.is(vm.ctx.exceptions.base_exception_group) { + // Auto-convert to ExceptionGroup if all are Exception subclasses + if !has_non_exception { + exception_group_type.to_owned() + } else { + cls + } + } else { + // User-defined subclass + if has_non_exception && cls.fast_issubclass(vm.ctx.exceptions.exception_type) { + return Err(vm.new_type_error(format!( + "Cannot nest BaseExceptions in '{}'", + cls.name() + ))); + } + cls + }; + + // Create the exception with (message, exceptions_tuple) as args + let exceptions_tuple = vm.ctx.new_tuple(exceptions); + let init_args = vec![message, exceptions_tuple.into()]; + PyBaseException::new(init_args, vm) + .into_ref_with_type(vm, actual_cls) + .map(Into::into) + } + + fn py_new(_cls: &Py, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult { + unimplemented!("use slot_new") + } + } + + impl Initializer for PyBaseExceptionGroup { + type Args = FuncArgs; + + fn slot_init( + _zelf: PyObjectRef, + _args: ::rustpython_vm::function::FuncArgs, + _vm: &::rustpython_vm::VirtualMachine, + ) -> ::rustpython_vm::PyResult<()> { + // CPython's BaseExceptionGroup.__init__ just calls BaseException.__init__ + // which stores args as-is. Since __new__ already set up the correct args + // (message, exceptions_tuple), we don't need to do anything here. + // This also allows subclasses to pass extra arguments to __new__ without + // __init__ complaining about argument count. + Ok(()) + } + + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + // Helper functions for ExceptionGroup fn is_base_exception_group(obj: &PyObject, vm: &VirtualMachine) -> bool { obj.fast_isinstance(vm.ctx.exceptions.base_exception_group) diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index bb10ca02c2..9486337785 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1371,18 +1371,19 @@ pub(super) mod types { #[repr(transparent)] pub struct PyStopIteration(PyException); - #[pyexception] - impl PyStopIteration { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult<()> { + #[pyexception(with(Initializer))] + impl PyStopIteration {} + + impl Initializer for PyStopIteration { + type Args = FuncArgs; + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { zelf.set_attr("value", vm.unwrap_or_none(args.args.first().cloned()), vm)?; Ok(()) } + + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } } #[pyexception(name, base = PyException, ctx = "stop_async_iteration", impl)] @@ -1419,15 +1420,13 @@ pub(super) mod types { #[repr(transparent)] pub struct PyAttributeError(PyException); - #[pyexception] - impl PyAttributeError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult<()> { + #[pyexception(with(Initializer))] + impl PyAttributeError {} + + impl Initializer for PyAttributeError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { zelf.set_attr( "name", vm.unwrap_or_none(args.kwargs.get("name").cloned()), @@ -1440,6 +1439,10 @@ pub(super) mod types { )?; Ok(()) } + + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } } #[pyexception(name, base = PyException, ctx = "buffer_error", impl)] @@ -1457,15 +1460,28 @@ pub(super) mod types { #[repr(transparent)] pub struct PyImportError(PyException); - #[pyexception] + #[pyexception(with(Initializer))] impl PyImportError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult<()> { + #[pymethod] + fn __reduce__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyTupleRef { + let obj = exc.as_object().to_owned(); + let mut result: Vec = vec![ + obj.class().to_owned().into(), + vm.new_tuple((exc.get_arg(0).unwrap(),)).into(), + ]; + + if let Some(dict) = obj.dict().filter(|x| !x.is_empty()) { + result.push(dict.into()); + } + + result.into_pytuple(vm) + } + } + + impl Initializer for PyImportError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { let mut kwargs = args.kwargs.clone(); let name = kwargs.swap_remove("name"); let path = kwargs.swap_remove("path"); @@ -1482,19 +1498,9 @@ pub(super) mod types { dict.set_item("path", vm.unwrap_or_none(path), vm)?; PyBaseException::slot_init(zelf, args, vm) } - #[pymethod] - fn __reduce__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyTupleRef { - let obj = exc.as_object().to_owned(); - let mut result: Vec = vec![ - obj.class().to_owned().into(), - vm.new_tuple((exc.get_arg(0).unwrap(),)).into(), - ]; - if let Some(dict) = obj.dict().filter(|x| !x.is_empty()) { - result.push(dict.into()); - } - - result.into_pytuple(vm) + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") } } @@ -1660,11 +1666,10 @@ pub(super) mod types { } } - #[pyexception(with(Constructor))] - impl PyOSError { - #[pyslot] - #[pymethod(name = "__init__")] - pub fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + impl Initializer for PyOSError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { let len = args.args.len(); let mut new_args = args; @@ -1718,6 +1723,13 @@ pub(super) mod types { PyBaseException::slot_init(zelf, new_args, vm) } + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + + #[pyexception(with(Constructor, Initializer))] + impl PyOSError { #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult { let obj = exc.as_object().to_owned(); @@ -2011,44 +2023,8 @@ pub(super) mod types { #[repr(transparent)] pub struct PySyntaxError(PyException); - #[pyexception] + #[pyexception(with(Initializer))] impl PySyntaxError { - #[pyslot] - #[pymethod(name = "__init__")] - fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - let len = args.args.len(); - let new_args = args; - - zelf.set_attr("print_file_and_line", vm.ctx.none(), vm)?; - - if len == 2 - && let Ok(location_tuple) = new_args.args[1] - .clone() - .downcast::() - { - let location_tup_len = location_tuple.len(); - for (i, &attr) in [ - "filename", - "lineno", - "offset", - "text", - "end_lineno", - "end_offset", - ] - .iter() - .enumerate() - { - if location_tup_len > i { - zelf.set_attr(attr, location_tuple[i].to_owned(), vm)?; - } else { - break; - } - } - } - - PyBaseException::slot_init(zelf, new_args, vm) - } - #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyStrRef { fn basename(filename: &str) -> &str { @@ -2097,6 +2073,48 @@ pub(super) mod types { } } + impl Initializer for PySyntaxError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + let len = args.args.len(); + let new_args = args; + + zelf.set_attr("print_file_and_line", vm.ctx.none(), vm)?; + + if len == 2 + && let Ok(location_tuple) = new_args.args[1] + .clone() + .downcast::() + { + let location_tup_len = location_tuple.len(); + for (i, &attr) in [ + "filename", + "lineno", + "offset", + "text", + "end_lineno", + "end_offset", + ] + .iter() + .enumerate() + { + if location_tup_len > i { + zelf.set_attr(attr, location_tuple[i].to_owned(), vm)?; + } else { + break; + } + } + } + + PyBaseException::slot_init(zelf, new_args, vm) + } + + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + #[pyexception( name = "_IncompleteInputError", base = PySyntaxError, @@ -2106,18 +2124,20 @@ pub(super) mod types { #[repr(transparent)] pub struct PyIncompleteInputError(PySyntaxError); - #[pyexception] - impl PyIncompleteInputError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - _args: FuncArgs, - vm: &VirtualMachine, - ) -> PyResult<()> { + #[pyexception(with(Initializer))] + impl PyIncompleteInputError {} + + impl Initializer for PyIncompleteInputError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { zelf.set_attr("name", vm.ctx.new_str("SyntaxError"), vm)?; Ok(()) } + + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } } #[pyexception(name, base = PySyntaxError, ctx = "indentation_error", impl)] @@ -2155,26 +2175,8 @@ pub(super) mod types { #[repr(transparent)] pub struct PyUnicodeDecodeError(PyUnicodeError); - #[pyexception] + #[pyexception(with(Initializer))] impl PyUnicodeDecodeError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: FuncArgs, - vm: &VirtualMachine, - ) -> PyResult<()> { - type Args = (PyStrRef, ArgBytesLike, isize, isize, PyStrRef); - let (encoding, object, start, end, reason): Args = args.bind(vm)?; - zelf.set_attr("encoding", encoding, vm)?; - let object_as_bytes = vm.ctx.new_bytes(object.borrow_buf().to_vec()); - zelf.set_attr("object", object_as_bytes, vm)?; - zelf.set_attr("start", vm.ctx.new_int(start), vm)?; - zelf.set_attr("end", vm.ctx.new_int(end), vm)?; - zelf.set_attr("reason", reason, vm)?; - Ok(()) - } - #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult { let Ok(object) = exc.as_object().get_attr("object", vm) else { @@ -2202,30 +2204,33 @@ pub(super) mod types { } } - #[pyexception(name, base = PyUnicodeError, ctx = "unicode_encode_error")] - #[derive(Debug)] - #[repr(transparent)] - pub struct PyUnicodeEncodeError(PyUnicodeError); + impl Initializer for PyUnicodeDecodeError { + type Args = FuncArgs; - #[pyexception] - impl PyUnicodeEncodeError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: FuncArgs, - vm: &VirtualMachine, - ) -> PyResult<()> { - type Args = (PyStrRef, PyStrRef, isize, isize, PyStrRef); + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + type Args = (PyStrRef, ArgBytesLike, isize, isize, PyStrRef); let (encoding, object, start, end, reason): Args = args.bind(vm)?; zelf.set_attr("encoding", encoding, vm)?; - zelf.set_attr("object", object, vm)?; + let object_as_bytes = vm.ctx.new_bytes(object.borrow_buf().to_vec()); + zelf.set_attr("object", object_as_bytes, vm)?; zelf.set_attr("start", vm.ctx.new_int(start), vm)?; zelf.set_attr("end", vm.ctx.new_int(end), vm)?; zelf.set_attr("reason", reason, vm)?; Ok(()) } + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + + #[pyexception(name, base = PyUnicodeError, ctx = "unicode_encode_error")] + #[derive(Debug)] + #[repr(transparent)] + pub struct PyUnicodeEncodeError(PyUnicodeError); + + #[pyexception(with(Initializer))] + impl PyUnicodeEncodeError { #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult { let Ok(object) = exc.as_object().get_attr("object", vm) else { @@ -2254,22 +2259,13 @@ pub(super) mod types { } } - #[pyexception(name, base = PyUnicodeError, ctx = "unicode_translate_error")] - #[derive(Debug)] - #[repr(transparent)] - pub struct PyUnicodeTranslateError(PyUnicodeError); + impl Initializer for PyUnicodeEncodeError { + type Args = FuncArgs; - #[pyexception] - impl PyUnicodeTranslateError { - #[pyslot] - #[pymethod(name = "__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: FuncArgs, - vm: &VirtualMachine, - ) -> PyResult<()> { - type Args = (PyStrRef, isize, isize, PyStrRef); - let (object, start, end, reason): Args = args.bind(vm)?; + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + type Args = (PyStrRef, PyStrRef, isize, isize, PyStrRef); + let (encoding, object, start, end, reason): Args = args.bind(vm)?; + zelf.set_attr("encoding", encoding, vm)?; zelf.set_attr("object", object, vm)?; zelf.set_attr("start", vm.ctx.new_int(start), vm)?; zelf.set_attr("end", vm.ctx.new_int(end), vm)?; @@ -2277,6 +2273,18 @@ pub(super) mod types { Ok(()) } + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + + #[pyexception(name, base = PyUnicodeError, ctx = "unicode_translate_error")] + #[derive(Debug)] + #[repr(transparent)] + pub struct PyUnicodeTranslateError(PyUnicodeError); + + #[pyexception(with(Initializer))] + impl PyUnicodeTranslateError { #[pymethod] fn __str__(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult { let Ok(object) = exc.as_object().get_attr("object", vm) else { @@ -2301,6 +2309,24 @@ pub(super) mod types { } } + impl Initializer for PyUnicodeTranslateError { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + type Args = (PyStrRef, isize, isize, PyStrRef); + let (object, start, end, reason): Args = args.bind(vm)?; + zelf.set_attr("object", object, vm)?; + zelf.set_attr("start", vm.ctx.new_int(start), vm)?; + zelf.set_attr("end", vm.ctx.new_int(end), vm)?; + zelf.set_attr("reason", reason, vm)?; + Ok(()) + } + + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + /// JIT error. #[cfg(feature = "jit")] #[pyexception(name, base = PyException, ctx = "jit_error", impl)] diff --git a/crates/vm/src/stdlib/ast/python.rs b/crates/vm/src/stdlib/ast/python.rs index aa21d8b034..3e5af2a911 100644 --- a/crates/vm/src/stdlib/ast/python.rs +++ b/crates/vm/src/stdlib/ast/python.rs @@ -3,50 +3,18 @@ use super::{PY_CF_OPTIMIZED_AST, PY_CF_TYPE_COMMENTS, PY_COMPILE_FLAG_AST_ONLY}; #[pymodule] pub(crate) mod _ast { use crate::{ - AsObject, Context, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, + AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef}, function::FuncArgs, - types::Constructor, + types::{Constructor, Initializer}, }; #[pyattr] #[pyclass(module = "_ast", name = "AST")] #[derive(Debug, PyPayload)] pub(crate) struct NodeAst; - #[pyclass(with(Constructor), flags(BASETYPE, HAS_DICT))] + #[pyclass(with(Constructor, Initializer), flags(BASETYPE, HAS_DICT))] impl NodeAst { - #[pyslot] - #[pymethod] - fn __init__(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - let fields = zelf.get_attr("_fields", vm)?; - let fields: Vec = fields.try_to_value(vm)?; - let n_args = args.args.len(); - if n_args > fields.len() { - return Err(vm.new_type_error(format!( - "{} constructor takes at most {} positional argument{}", - zelf.class().name(), - fields.len(), - if fields.len() == 1 { "" } else { "s" }, - ))); - } - for (name, arg) in fields.iter().zip(args.args) { - zelf.set_attr(name, arg, vm)?; - } - for (key, value) in args.kwargs { - if let Some(pos) = fields.iter().position(|f| f.as_str() == key) - && pos < n_args - { - return Err(vm.new_type_error(format!( - "{} got multiple values for argument '{}'", - zelf.class().name(), - key - ))); - } - zelf.set_attr(vm.ctx.intern_str(key), value, vm)?; - } - Ok(()) - } - #[pyattr] fn _fields(ctx: &Context) -> PyTupleRef { ctx.empty_tuple.clone() @@ -71,7 +39,7 @@ pub(crate) mod _ast { let zelf = vm.ctx.new_base_object(cls, dict); // Initialize the instance with the provided arguments - Self::__init__(zelf.clone(), args, vm)?; + Self::slot_init(zelf.clone(), args, vm)?; Ok(zelf) } @@ -81,6 +49,44 @@ pub(crate) mod _ast { } } + impl Initializer for NodeAst { + type Args = FuncArgs; + + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + let fields = zelf.get_attr("_fields", vm)?; + let fields: Vec = fields.try_to_value(vm)?; + let n_args = args.args.len(); + if n_args > fields.len() { + return Err(vm.new_type_error(format!( + "{} constructor takes at most {} positional argument{}", + zelf.class().name(), + fields.len(), + if fields.len() == 1 { "" } else { "s" }, + ))); + } + for (name, arg) in fields.iter().zip(args.args) { + zelf.set_attr(name, arg, vm)?; + } + for (key, value) in args.kwargs { + if let Some(pos) = fields.iter().position(|f| f.as_str() == key) + && pos < n_args + { + return Err(vm.new_type_error(format!( + "{} got multiple values for argument '{}'", + zelf.class().name(), + key + ))); + } + zelf.set_attr(vm.ctx.intern_str(key), value, vm)?; + } + Ok(()) + } + + fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { + unreachable!("slot_init is defined") + } + } + #[pyattr(name = "PyCF_ONLY_AST")] use super::PY_COMPILE_FLAG_AST_ONLY; From 78c23c5a1afc33b39a073ef547576828cfd9c904 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 22 Dec 2025 20:18:09 +0900 Subject: [PATCH 3/3] apply review --- crates/derive-impl/src/pyclass.rs | 17 ++++++----------- crates/stdlib/src/sqlite.rs | 4 ++-- crates/vm/src/exception_group.rs | 14 ++++++++------ crates/vm/src/exceptions.rs | 4 ++-- crates/vm/src/stdlib/ast/python.rs | 1 + 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index dcf3378fa8..d1fe539863 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -735,12 +735,7 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R let with_contains = |with_items: &[Ident], s: &str| { // Check if Constructor is in the list - for ident in with_items { - if ident.to_string().as_str() == s { - return true; - } - } - false + with_items.iter().any(|ident| ident == s) }; let syn::ItemImpl { @@ -768,8 +763,8 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R fn py_new( _cls: &::rustpython_vm::Py<::rustpython_vm::builtins::PyType>, _args: Self::Args, - _vm: &VirtualMachine - ) -> PyResult { + _vm: &::rustpython_vm::VirtualMachine + ) -> ::rustpython_vm::PyResult { unreachable!("slot_new is defined") } } @@ -792,15 +787,15 @@ pub(crate) fn impl_pyexception_impl(attr: PunctuatedNestedMeta, item: Item) -> R zelf: ::rustpython_vm::PyObjectRef, args: ::rustpython_vm::function::FuncArgs, vm: &::rustpython_vm::VirtualMachine, - ) -> PyResult<()> { + ) -> ::rustpython_vm::PyResult<()> { ::Base::slot_init(zelf, args, vm) } fn init( _zelf: ::rustpython_vm::PyRef, _args: Self::Args, - _vm: &VirtualMachine - ) -> PyResult<()> { + _vm: &::rustpython_vm::VirtualMachine + ) -> ::rustpython_vm::PyResult<()> { unreachable!("slot_init is defined") } } diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index f2a8ea69c3..ffdc9eb383 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -1932,9 +1932,9 @@ mod _sqlite { } impl Initializer for Cursor { - type Args = FuncArgs; + type Args = PyRef; - fn init(zelf: PyRef, _connection: FuncArgs, _vm: &VirtualMachine) -> PyResult<()> { + fn init(zelf: PyRef, _connection: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { let mut guard = zelf.inner.lock(); if guard.is_some() { // Already initialized (e.g., from a call to super().__init__) diff --git a/crates/vm/src/exception_group.rs b/crates/vm/src/exception_group.rs index 61b67d79e9..e19dbceb8d 100644 --- a/crates/vm/src/exception_group.rs +++ b/crates/vm/src/exception_group.rs @@ -242,19 +242,21 @@ pub(super) mod types { } impl Constructor for PyBaseExceptionGroup { - type Args = FuncArgs; + type Args = crate::function::PosArgs; fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + let args: Self::Args = args.bind(vm)?; + let args = args.into_vec(); // Validate exactly 2 positional arguments - if args.args.len() != 2 { + if args.len() != 2 { return Err(vm.new_type_error(format!( "BaseExceptionGroup.__new__() takes exactly 2 positional arguments ({} given)", - args.args.len() + args.len() ))); } // Validate message is str - let message = args.args[0].clone(); + let message = args[0].clone(); if !message.fast_isinstance(vm.ctx.types.str_type) { return Err(vm.new_type_error(format!( "argument 1 must be str, not {}", @@ -263,7 +265,7 @@ pub(super) mod types { } // Validate exceptions is a sequence (not set or None) - let exceptions_arg = &args.args[1]; + let exceptions_arg = &args[1]; // Check for set/frozenset (not a sequence - unordered) if exceptions_arg.fast_isinstance(vm.ctx.types.set_type) @@ -343,7 +345,7 @@ pub(super) mod types { .map(Into::into) } - fn py_new(_cls: &Py, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult { + fn py_new(_cls: &Py, _args: Self::Args, _vm: &VirtualMachine) -> PyResult { unimplemented!("use slot_new") } } diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 9486337785..2c36aa13bd 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -2130,9 +2130,9 @@ pub(super) mod types { impl Initializer for PyIncompleteInputError { type Args = FuncArgs; - fn slot_init(zelf: PyObjectRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { zelf.set_attr("name", vm.ctx.new_str("SyntaxError"), vm)?; - Ok(()) + PySyntaxError::slot_init(zelf, args, vm) } fn init(_zelf: PyRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> { diff --git a/crates/vm/src/stdlib/ast/python.rs b/crates/vm/src/stdlib/ast/python.rs index 3e5af2a911..35fe561527 100644 --- a/crates/vm/src/stdlib/ast/python.rs +++ b/crates/vm/src/stdlib/ast/python.rs @@ -39,6 +39,7 @@ pub(crate) mod _ast { let zelf = vm.ctx.new_base_object(cls, dict); // Initialize the instance with the provided arguments + // FIXME: This is probably incorrect. Please check if init should be called outside of __new__ Self::slot_init(zelf.clone(), args, vm)?; Ok(zelf)