From 9f6d0952d4e80e43d9aba74d7e776df1489bcc63 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 2 Dec 2025 09:21:16 +0900 Subject: [PATCH 01/14] bool contains PyInt --- crates/vm/src/builtins/bool.rs | 55 ++++++++++++++++++++++++++++++---- crates/vm/src/builtins/int.rs | 5 +++- crates/vm/src/stdlib/io.rs | 4 +-- crates/vm/src/vm/context.rs | 20 ++++++------- 4 files changed, 65 insertions(+), 19 deletions(-) diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 9b519dbbde..309e9d046b 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -1,8 +1,9 @@ use super::{PyInt, PyStrRef, PyType, PyTypeRef}; use crate::common::format::FormatSpec; use crate::{ - AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, - VirtualMachine, + AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, + TryFromBorrowedObject, VirtualMachine, + builtins::PyBaseExceptionRef, class::PyClassImpl, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{FuncArgs, OptionalArg}, @@ -82,18 +83,42 @@ impl PyObjectRef { } #[pyclass(name = "bool", module = false, base = PyInt)] -pub struct PyBool; +#[repr(transparent)] +pub struct PyBool(pub PyInt); impl PyPayload for PyBool { #[inline] fn class(ctx: &Context) -> &'static Py { ctx.types.bool_type } + + /// PyBool reuses PyInt's TypeId + #[inline] + fn payload_type_id() -> std::any::TypeId { + std::any::TypeId::of::() + } + + fn try_downcast_from(obj: &PyObject, vm: &VirtualMachine) -> PyResult<()> { + if obj.class().is(vm.ctx.types.bool_type) { + return Ok(()); + } + + #[cold] + fn raise_downcast_type_error( + vm: &VirtualMachine, + class: &Py, + obj: &PyObject, + ) -> PyBaseExceptionRef { + vm.new_downcast_type_error(class, obj) + } + Err(raise_downcast_type_error(vm, Self::class(&vm.ctx), obj)) + } } impl Debug for PyBool { - fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { - todo!() + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let value = !self.0.as_bigint().is_zero(); + write!(f, "PyBool({})", value) } } @@ -221,5 +246,23 @@ pub(crate) fn init(context: &Context) { // Retrieve inner int value: pub(crate) fn get_value(obj: &PyObject) -> bool { - !obj.downcast_ref::().unwrap().as_bigint().is_zero() + !obj.downcast_ref::() + .unwrap() + .0 + .as_bigint() + .is_zero() +} + +impl PyRef { + #[inline] + pub fn into_base(self) -> PyRef { + // SAFETY: PyBool's payload is PyInt + unsafe { std::mem::transmute(self) } + } + + #[inline] + pub fn as_base(&self) -> &PyRef { + // SAFETY: PyBool's payload is PyInt + unsafe { std::mem::transmute(self) } + } } diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index b210d41823..25e60a041e 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -256,7 +256,10 @@ impl PyInt { if cls.is(vm.ctx.types.int_type) { Ok(vm.ctx.new_int(value)) } else if cls.is(vm.ctx.types.bool_type) { - Ok(vm.ctx.new_bool(!value.into().eq(&BigInt::zero()))) + Ok(vm + .ctx + .new_bool(!value.into().eq(&BigInt::zero())) + .into_base()) } else { Self::from(value).into_ref_with_type(vm, cls) } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index c54f3b853d..0568ee8194 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -119,7 +119,7 @@ mod _io { AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromBorrowedObject, TryFromObject, builtins::{ - PyBaseExceptionRef, PyByteArray, PyBytes, PyBytesRef, PyIntRef, PyMemoryView, PyStr, + PyBaseExceptionRef, PyBool, PyByteArray, PyBytes, PyBytesRef, PyMemoryView, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, PyUtf8StrRef, }, class::StaticType, @@ -425,7 +425,7 @@ mod _io { } #[pyattr] - fn __closed(ctx: &Context) -> PyIntRef { + fn __closed(ctx: &Context) -> PyRef { ctx.new_bool(false) } diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index 191c090f12..17142c0218 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -4,6 +4,7 @@ use crate::{ PyBaseException, PyByteArray, PyBytes, PyComplex, PyDict, PyDictRef, PyEllipsis, PyFloat, PyFrozenSet, PyInt, PyIntRef, PyList, PyListRef, PyNone, PyNotImplemented, PyStr, PyStrInterned, PyTuple, PyTupleRef, PyType, PyTypeRef, + bool_::PyBool, code::{self, PyCode}, descriptor::{ MemberGetter, MemberKind, MemberSetter, MemberSetterFunc, PyDescriptorOwned, @@ -31,8 +32,8 @@ use rustpython_common::lock::PyRwLock; #[derive(Debug)] pub struct Context { - pub true_value: PyIntRef, - pub false_value: PyIntRef, + pub true_value: PyRef, + pub false_value: PyRef, pub none: PyRef, pub empty_tuple: PyTupleRef, pub empty_frozenset: PyRef, @@ -305,8 +306,8 @@ impl Context { }) .collect(); - let true_value = create_object(PyInt::from(1), types.bool_type); - let false_value = create_object(PyInt::from(0), types.bool_type); + let true_value = create_object(PyBool(PyInt::from(1)), types.bool_type); + let false_value = create_object(PyBool(PyInt::from(0)), types.bool_type); let empty_tuple = create_object( PyTuple::new_unchecked(Vec::new().into_boxed_slice()), @@ -449,13 +450,12 @@ impl Context { } #[inline(always)] - pub fn new_bool(&self, b: bool) -> PyIntRef { - let value = if b { - &self.true_value + pub fn new_bool(&self, b: bool) -> PyRef { + if b { + self.true_value.clone() } else { - &self.false_value - }; - value.clone() + self.false_value.clone() + } } #[inline(always)] From 00318441e73e7c844a79054eb234636badd9f4c8 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 2 Dec 2025 10:13:56 +0900 Subject: [PATCH 02/14] subclass trial 1 --- crates/derive-impl/src/pyclass.rs | 32 ++++++++++++++++++++++++++++++- crates/vm/src/builtins/bool.rs | 18 ++--------------- crates/vm/src/class.rs | 15 +++++++++++++++ crates/vm/src/object/core.rs | 18 +++++++++++++++++ 4 files changed, 66 insertions(+), 17 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 84559e3574..caef601cc3 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -350,6 +350,18 @@ fn generate_class_def( false } }); + // Check if the type has #[repr(transparent)] - only then we can safely + // generate PySubclass impl (requires same memory layout as base type) + let is_repr_transparent = attrs.iter().any(|attr| { + attr.path().is_ident("repr") + && if let Ok(Meta::List(l)) = attr.parse_meta() { + l.nested + .into_iter() + .any(|n| n.get_ident().is_some_and(|p| p == "transparent")) + } else { + false + } + }); if base.is_some() && is_pystruct { bail_span!(ident, "PyStructSequence cannot have `base` class attr",); } @@ -379,12 +391,28 @@ fn generate_class_def( } }); - let base_or_object = if let Some(base) = base { + let base_or_object = if let Some(ref base) = base { quote! { #base } } else { quote! { ::rustpython_vm::builtins::PyBaseObject } }; + // Generate PySubclass impl for types with: + // - base class specified + // - #[repr(transparent)] (required for safe transmute) + // - not PyStructSequence + let subclass_impl = if !is_pystruct && is_repr_transparent { + base.as_ref().map(|typ| { + quote! { + impl ::rustpython_vm::class::PySubclass for #ident { + type Base = #typ; + } + } + }) + } else { + None + }; + let tokens = quote! { impl ::rustpython_vm::class::PyClassDef for #ident { const NAME: &'static str = #name; @@ -409,6 +437,8 @@ fn generate_class_def( #base_class } + + #subclass_impl }; Ok(tokens) } diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 309e9d046b..dbc4edf385 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -1,8 +1,8 @@ use super::{PyInt, PyStrRef, PyType, PyTypeRef}; use crate::common::format::FormatSpec; use crate::{ - AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, - TryFromBorrowedObject, VirtualMachine, + AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, + VirtualMachine, builtins::PyBaseExceptionRef, class::PyClassImpl, convert::{IntoPyException, ToPyObject, ToPyResult}, @@ -252,17 +252,3 @@ pub(crate) fn get_value(obj: &PyObject) -> bool { .as_bigint() .is_zero() } - -impl PyRef { - #[inline] - pub fn into_base(self) -> PyRef { - // SAFETY: PyBool's payload is PyInt - unsafe { std::mem::transmute(self) } - } - - #[inline] - pub fn as_base(&self) -> &PyRef { - // SAFETY: PyBool's payload is PyInt - unsafe { std::mem::transmute(self) } - } -} diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 8850de3615..6bb2781152 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -171,3 +171,18 @@ pub trait PyClassImpl: PyClassDef { slots } } + +/// Marker trait for Python subclasses with `#[repr(transparent)]` newtype pattern. +/// +/// This trait is automatically implemented by the `#[pyclass]` macro when +/// `base = SomeType` is specified. It enables type-safe conversion between +/// `PyRef` and `PyRef`. +/// +/// # Safety +/// +/// Implementors must ensure: +/// - The type uses `#[repr(transparent)]` with the Base type as the only field +/// - Memory layout is identical to the Base type +pub trait PySubclass: crate::PyPayload { + type Base: crate::PyPayload; +} diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 6530abdbab..4ebdd32e71 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -1070,6 +1070,24 @@ impl PyRef { } } +impl PyRef { + /// Converts this reference to a reference of the base type. + /// + /// This is safe because T has `#[repr(transparent)]` layout with T::Base. + #[inline] + pub fn into_base(self) -> PyRef { + // SAFETY: #[repr(transparent)] guarantees same memory layout + unsafe { std::mem::transmute(self) } + } + + /// Returns a reference to the base type. + #[inline] + pub fn as_base(&self) -> &PyRef { + // SAFETY: #[repr(transparent)] guarantees same memory layout + unsafe { std::mem::transmute(self) } + } +} + impl Borrow for PyRef where T: PyPayload, From e132d7e18dc1642cd453484b48be7c3a940128cf Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 2 Dec 2025 10:32:14 +0900 Subject: [PATCH 03/14] subclass trial 2 --- crates/derive-impl/src/pyclass.rs | 21 +++++++++++++++++---- crates/vm/src/builtins/bool.rs | 15 +++++---------- crates/vm/src/builtins/int.rs | 2 +- crates/vm/src/class.rs | 25 +++++++++++++++++++------ crates/vm/src/object/core.rs | 21 ++++++++++++++++----- crates/vm/src/object/mod.rs | 2 ++ crates/vm/src/object/payload.rs | 20 ++++++++++---------- 7 files changed, 70 insertions(+), 36 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index caef601cc3..01fed3dc3a 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -397,15 +397,18 @@ fn generate_class_def( quote! { ::rustpython_vm::builtins::PyBaseObject } }; - // Generate PySubclass impl for types with: - // - base class specified - // - #[repr(transparent)] (required for safe transmute) - // - not PyStructSequence + // Generate PySubclass impl for #[repr(transparent)] types with base class + // (tuple struct assumed, so &self.0 works) let subclass_impl = if !is_pystruct && is_repr_transparent { base.as_ref().map(|typ| { quote! { impl ::rustpython_vm::class::PySubclass for #ident { type Base = #typ; + + #[inline] + fn as_base(&self) -> &Self::Base { + &self.0 + } } } }) @@ -413,6 +416,15 @@ fn generate_class_def( None }; + // Generate PySubclassTransparent marker for #[repr(transparent)] types + let transparent_impl = if !is_pystruct && is_repr_transparent && base.is_some() { + Some(quote! { + impl ::rustpython_vm::class::PySubclassTransparent for #ident {} + }) + } else { + None + }; + let tokens = quote! { impl ::rustpython_vm::class::PyClassDef for #ident { const NAME: &'static str = #name; @@ -439,6 +451,7 @@ fn generate_class_def( } #subclass_impl + #transparent_impl }; Ok(tokens) } diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index dbc4edf385..36c39d4d6e 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -3,7 +3,6 @@ use crate::common::format::FormatSpec; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, VirtualMachine, - builtins::PyBaseExceptionRef, class::PyClassImpl, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{FuncArgs, OptionalArg}, @@ -103,15 +102,11 @@ impl PyPayload for PyBool { return Ok(()); } - #[cold] - fn raise_downcast_type_error( - vm: &VirtualMachine, - class: &Py, - obj: &PyObject, - ) -> PyBaseExceptionRef { - vm.new_downcast_type_error(class, obj) - } - Err(raise_downcast_type_error(vm, Self::class(&vm.ctx), obj)) + Err(crate::object::cold_downcast_type_error( + vm, + Self::class(&vm.ctx), + obj, + )) } } diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 25e60a041e..ab79490d0c 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -259,7 +259,7 @@ impl PyInt { Ok(vm .ctx .new_bool(!value.into().eq(&BigInt::zero())) - .into_base()) + .into_base_ref()) } else { Self::from(value).into_ref_with_type(vm, cls) } diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 6bb2781152..2438d647c6 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -172,17 +172,30 @@ pub trait PyClassImpl: PyClassDef { } } -/// Marker trait for Python subclasses with `#[repr(transparent)]` newtype pattern. +/// Trait for Python subclasses that can provide a reference to their base type. /// /// This trait is automatically implemented by the `#[pyclass]` macro when -/// `base = SomeType` is specified. It enables type-safe conversion between -/// `PyRef` and `PyRef`. +/// `base = SomeType` is specified. It provides safe reference access to the +/// base type's payload. +/// +/// For subclasses with `#[repr(transparent)]`, see also [`PySubclassTransparent`] +/// which enables ownership transfer via `into_base()`. +pub trait PySubclass: crate::PyPayload { + type Base: crate::PyPayload; + + /// Returns a reference to the base type's payload. + fn as_base(&self) -> &Self::Base; +} + +/// Marker trait for `#[repr(transparent)]` subclasses. +/// +/// This trait enables ownership transfer from `PyRef` to `PyRef` +/// via the `into_base_ref()` method. Only types with identical memory layout to their +/// base type (i.e., `#[repr(transparent)]` newtypes) should implement this trait. /// /// # Safety /// /// Implementors must ensure: /// - The type uses `#[repr(transparent)]` with the Base type as the only field /// - Memory layout is identical to the Base type -pub trait PySubclass: crate::PyPayload { - type Base: crate::PyPayload; -} +pub trait PySubclassTransparent: PySubclass {} diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 4ebdd32e71..1100f83e9b 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -1071,18 +1071,29 @@ impl PyRef { } impl PyRef { - /// Converts this reference to a reference of the base type. + /// Returns a reference to the base type's payload. + #[inline] + pub fn as_base(&self) -> &T::Base { + (**self).as_base() + } +} + +impl PyRef { + /// Converts this reference to the base type (ownership transfer). /// - /// This is safe because T has `#[repr(transparent)]` layout with T::Base. + /// Only available for `#[repr(transparent)]` types where memory layout + /// is identical to the base type. #[inline] - pub fn into_base(self) -> PyRef { + pub fn into_base_ref(self) -> PyRef { // SAFETY: #[repr(transparent)] guarantees same memory layout unsafe { std::mem::transmute(self) } } - /// Returns a reference to the base type. + /// Returns a reference to this as a PyRef of the base type. + /// + /// Only available for `#[repr(transparent)]` types. #[inline] - pub fn as_base(&self) -> &PyRef { + pub fn as_base_ref(&self) -> &PyRef { // SAFETY: #[repr(transparent)] guarantees same memory layout unsafe { std::mem::transmute(self) } } diff --git a/crates/vm/src/object/mod.rs b/crates/vm/src/object/mod.rs index 034523afe5..d0412abdb9 100644 --- a/crates/vm/src/object/mod.rs +++ b/crates/vm/src/object/mod.rs @@ -8,3 +8,5 @@ pub use self::core::*; pub use self::ext::*; pub use self::payload::*; pub use traverse::{MaybeTraverse, Traverse, TraverseFn}; + +pub(crate) use self::payload::cold_downcast_type_error; diff --git a/crates/vm/src/object/payload.rs b/crates/vm/src/object/payload.rs index b221176149..f5debf8509 100644 --- a/crates/vm/src/object/payload.rs +++ b/crates/vm/src/object/payload.rs @@ -16,6 +16,15 @@ cfg_if::cfg_if! { } } +#[cold] +pub(crate) fn cold_downcast_type_error( + vm: &VirtualMachine, + class: &Py, + obj: &PyObject, +) -> PyBaseExceptionRef { + vm.new_downcast_type_error(class, obj) +} + pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { #[inline] fn payload_type_id() -> std::any::TypeId { @@ -38,17 +47,8 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { return Ok(()); } - #[cold] - fn raise_downcast_type_error( - vm: &VirtualMachine, - class: &Py, - obj: &PyObject, - ) -> PyBaseExceptionRef { - vm.new_downcast_type_error(class, obj) - } - let class = Self::class(&vm.ctx); - Err(raise_downcast_type_error(vm, class, obj)) + Err(cold_downcast_type_error(vm, class, obj)) } fn class(ctx: &Context) -> &'static Py; From dea66eb2650c937f513f4526704b08029597c04b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 2 Dec 2025 14:20:27 +0900 Subject: [PATCH 04/14] PyBool downcastable_from --- crates/vm/src/builtins/bool.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 36c39d4d6e..9528fe34a2 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -3,7 +3,7 @@ use crate::common::format::FormatSpec; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, VirtualMachine, - class::PyClassImpl, + class::{PyClassImpl, StaticType}, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{FuncArgs, OptionalArg}, protocol::PyNumberMethods, @@ -21,10 +21,15 @@ impl ToPyObject for bool { impl<'a> TryFromBorrowedObject<'a> for bool { fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult { - if obj.fast_isinstance(vm.ctx.types.int_type) { - Ok(get_value(obj)) - } else { - Err(vm.new_type_error(format!("Expected type bool, not {}", obj.class().name()))) + // Python takes integers as a legit bool value + match obj.downcast_ref::() { + Some(int_obj) => { + let int_val = int_obj.as_bigint(); + Ok(!int_val.is_zero()) + } + None => { + Err(vm.new_type_error(format!("Expected type bool, not {}", obj.class().name()))) + } } } } @@ -97,6 +102,10 @@ impl PyPayload for PyBool { std::any::TypeId::of::() } + fn downcastable_from(obj: &PyObject) -> bool { + obj.class().is(PyBool::static_type()) + } + fn try_downcast_from(obj: &PyObject, vm: &VirtualMachine) -> PyResult<()> { if obj.class().is(vm.ctx.types.bool_type) { return Ok(()); From ad5464a164ac1e9120c5c0d13e579874b07c7664 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 2 Dec 2025 21:15:00 +0900 Subject: [PATCH 05/14] Revise downcastable --- crates/derive-impl/src/pyclass.rs | 60 +++++++++++++----------- crates/vm/src/builtins/bool.rs | 36 ++------------ crates/vm/src/object/core.rs | 26 ++-------- crates/vm/src/object/mod.rs | 2 - crates/vm/src/stdlib/ast/pyast.rs | 2 +- crates/vm/src/stdlib/ctypes/array.rs | 3 +- crates/vm/src/stdlib/ctypes/base.rs | 3 +- crates/vm/src/stdlib/ctypes/field.rs | 2 +- crates/vm/src/stdlib/ctypes/function.rs | 1 - crates/vm/src/stdlib/ctypes/pointer.rs | 4 +- crates/vm/src/stdlib/ctypes/structure.rs | 3 +- crates/vm/src/stdlib/ctypes/union.rs | 3 +- crates/vm/src/stdlib/io.rs | 18 +++---- crates/vm/src/vm/context.rs | 5 +- 14 files changed, 59 insertions(+), 109 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 01fed3dc3a..2db0b587ff 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -473,7 +473,7 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result Result::static_type() } + }; - // We need this to make extend mechanism work: quote! { impl ::rustpython_vm::PyPayload for #ident { + #[inline] + fn payload_type_id() -> ::std::any::TypeId { + <#base_type as ::rustpython_vm::PyPayload>::payload_type_id() + } + + #[inline] + fn validate_downcastable_from(obj: &::rustpython_vm::PyObject) -> bool { + ::BASICSIZE <= obj.class().slots.basicsize && obj.class().fast_issubclass(::static_type()) + } + fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { - ctx.types.#ctx_type_ident + #class_fn } } } } else { - quote! {} + if let Some(ctx_type_name) = class_meta.ctx_name()? { + let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); + quote! { + impl ::rustpython_vm::PyPayload for #ident { + fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { + ctx.types.#ctx_type_ident + } + } + } + } else { + quote! {} + } }; let empty_impl = if let Some(attrs) = class_meta.impl_attrs()? { @@ -579,26 +606,6 @@ pub(crate) fn impl_pyexception(attr: PunctuatedNestedMeta, item: Item) -> Result let class_name = class_meta.class_name()?; let base_class_name = class_meta.base()?; - let impl_payload = if let Some(ctx_type_name) = class_meta.ctx_name()? { - let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); // FIXME span - - // We need this to make extend mechanism work: - quote! { - impl ::rustpython_vm::PyPayload for #ident { - fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { - ctx.exceptions.#ctx_type_ident - } - } - } - } else { - quote! { - impl ::rustpython_vm::PyPayload for #ident { - fn class(_ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { - ::static_type() - } - } - } - }; let impl_pyclass = if class_meta.has_impl()? { quote! { #[pyexception] @@ -611,7 +618,6 @@ pub(crate) fn impl_pyexception(attr: PunctuatedNestedMeta, item: Item) -> Result let ret = quote! { #[pyclass(module = false, name = #class_name, base = #base_class_name)] #item - #impl_payload #impl_pyclass }; Ok(ret) diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 9528fe34a2..6b3ddd8241 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -1,9 +1,8 @@ use super::{PyInt, PyStrRef, PyType, PyTypeRef}; use crate::common::format::FormatSpec; use crate::{ - AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, - VirtualMachine, - class::{PyClassImpl, StaticType}, + AsObject, Context, Py, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, VirtualMachine, + class::PyClassImpl, convert::{IntoPyException, ToPyObject, ToPyResult}, function::{FuncArgs, OptionalArg}, protocol::PyNumberMethods, @@ -86,39 +85,10 @@ impl PyObjectRef { } } -#[pyclass(name = "bool", module = false, base = PyInt)] +#[pyclass(name = "bool", module = false, base = PyInt, ctx = "bool_type")] #[repr(transparent)] pub struct PyBool(pub PyInt); -impl PyPayload for PyBool { - #[inline] - fn class(ctx: &Context) -> &'static Py { - ctx.types.bool_type - } - - /// PyBool reuses PyInt's TypeId - #[inline] - fn payload_type_id() -> std::any::TypeId { - std::any::TypeId::of::() - } - - fn downcastable_from(obj: &PyObject) -> bool { - obj.class().is(PyBool::static_type()) - } - - fn try_downcast_from(obj: &PyObject, vm: &VirtualMachine) -> PyResult<()> { - if obj.class().is(vm.ctx.types.bool_type) { - return Ok(()); - } - - Err(crate::object::cold_downcast_type_error( - vm, - Self::class(&vm.ctx), - obj, - )) - } -} - impl Debug for PyBool { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let value = !self.0.as_bigint().is_zero(); diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 1100f83e9b..ad1649c0c7 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -1070,30 +1070,14 @@ impl PyRef { } } -impl PyRef { - /// Returns a reference to the base type's payload. - #[inline] - pub fn as_base(&self) -> &T::Base { - (**self).as_base() - } -} - -impl PyRef { +impl PyRef +where + T::Base: std::fmt::Debug, +{ /// Converts this reference to the base type (ownership transfer). - /// - /// Only available for `#[repr(transparent)]` types where memory layout - /// is identical to the base type. - #[inline] - pub fn into_base_ref(self) -> PyRef { - // SAFETY: #[repr(transparent)] guarantees same memory layout - unsafe { std::mem::transmute(self) } - } - - /// Returns a reference to this as a PyRef of the base type. - /// /// Only available for `#[repr(transparent)]` types. #[inline] - pub fn as_base_ref(&self) -> &PyRef { + pub fn into_base_ref(self) -> PyRef { // SAFETY: #[repr(transparent)] guarantees same memory layout unsafe { std::mem::transmute(self) } } diff --git a/crates/vm/src/object/mod.rs b/crates/vm/src/object/mod.rs index d0412abdb9..034523afe5 100644 --- a/crates/vm/src/object/mod.rs +++ b/crates/vm/src/object/mod.rs @@ -8,5 +8,3 @@ pub use self::core::*; pub use self::ext::*; pub use self::payload::*; pub use traverse::{MaybeTraverse, Traverse, TraverseFn}; - -pub(crate) use self::payload::cold_downcast_type_error; diff --git a/crates/vm/src/stdlib/ast/pyast.rs b/crates/vm/src/stdlib/ast/pyast.rs index b891a605dc..a4dd5f0385 100644 --- a/crates/vm/src/stdlib/ast/pyast.rs +++ b/crates/vm/src/stdlib/ast/pyast.rs @@ -78,7 +78,7 @@ macro_rules! impl_node { } #[pyclass(module = "_ast", name = "mod", base = NodeAst)] -pub(crate) struct NodeMod; +pub(crate) struct NodeMod(NodeAst); #[pyclass(flags(HAS_DICT, BASETYPE))] impl NodeMod {} diff --git a/crates/vm/src/stdlib/ctypes/array.rs b/crates/vm/src/stdlib/ctypes/array.rs index 98274e388b..b95884032c 100644 --- a/crates/vm/src/stdlib/ctypes/array.rs +++ b/crates/vm/src/stdlib/ctypes/array.rs @@ -23,7 +23,7 @@ use rustpython_vm::stdlib::ctypes::base::PyCData; /// PyCArrayType - metatype for Array types /// CPython stores array info (type, length) in StgInfo via type_data #[pyclass(name = "PyCArrayType", base = PyType, module = "_ctypes")] -#[derive(Debug, Default, PyPayload)] +#[derive(Debug, Default)] pub struct PyCArrayType {} /// Create a new Array type with StgInfo stored in type_data (CPython style) @@ -235,7 +235,6 @@ impl AsNumber for PyCArrayType { metaclass = "PyCArrayType", module = "_ctypes" )] -#[derive(PyPayload)] pub struct PyCArray { /// Element type - can be a simple type (c_int) or an array type (c_int * 5) pub(super) typ: PyRwLock, diff --git a/crates/vm/src/stdlib/ctypes/base.rs b/crates/vm/src/stdlib/ctypes/base.rs index 80d42fba3c..25507067ca 100644 --- a/crates/vm/src/stdlib/ctypes/base.rs +++ b/crates/vm/src/stdlib/ctypes/base.rs @@ -228,7 +228,7 @@ impl PyCData { } #[pyclass(module = "_ctypes", name = "PyCSimpleType", base = PyType)] -#[derive(Debug, PyPayload, Default)] +#[derive(Debug, Default)] pub struct PyCSimpleType {} #[pyclass(flags(BASETYPE), with(AsNumber))] @@ -411,7 +411,6 @@ impl AsNumber for PyCSimpleType { base = PyCData, metaclass = "PyCSimpleType" )] -#[derive(PyPayload)] pub struct PyCSimple { pub _type_: String, pub value: AtomicCell, diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index 8d6da5808a..7205c430b5 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -8,7 +8,7 @@ use super::structure::PyCStructure; use super::union::PyCUnion; #[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] -#[derive(PyPayload, Debug)] +#[derive(Debug)] pub struct PyCFieldType { #[allow(dead_code)] pub(super) inner: PyCField, diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index d202410b14..f36c56851b 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -162,7 +162,6 @@ impl ReturnType for PyNone { } #[pyclass(module = "_ctypes", name = "CFuncPtr", base = PyCData)] -#[derive(PyPayload)] pub struct PyCFuncPtr { pub name: PyRwLock>, pub ptr: PyRwLock>, diff --git a/crates/vm/src/stdlib/ctypes/pointer.rs b/crates/vm/src/stdlib/ctypes/pointer.rs index 156c4e54ee..beccf63e2a 100644 --- a/crates/vm/src/stdlib/ctypes/pointer.rs +++ b/crates/vm/src/stdlib/ctypes/pointer.rs @@ -9,7 +9,7 @@ use crate::types::{AsNumber, Constructor}; use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine}; #[pyclass(name = "PyCPointerType", base = PyType, module = "_ctypes")] -#[derive(PyPayload, Debug, Default)] +#[derive(Debug, Default)] pub struct PyCPointerType {} #[pyclass(flags(IMMUTABLETYPE), with(AsNumber))] @@ -60,7 +60,7 @@ impl AsNumber for PyCPointerType { metaclass = "PyCPointerType", module = "_ctypes" )] -#[derive(Debug, PyPayload)] +#[derive(Debug)] pub struct PyCPointer { contents: PyRwLock, } diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index d3cdea69c7..566703fb3a 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -15,7 +15,7 @@ use std::fmt::Debug; /// PyCStructType - metaclass for Structure #[pyclass(name = "PyCStructType", base = PyType, module = "_ctypes")] -#[derive(Debug, PyPayload, Default)] +#[derive(Debug, Default)] pub struct PyCStructType {} impl Constructor for PyCStructType { @@ -218,7 +218,6 @@ pub struct FieldInfo { base = PyCData, metaclass = "PyCStructType" )] -#[derive(PyPayload)] pub struct PyCStructure { /// Common CDataObject for memory buffer pub(super) cdata: PyRwLock, diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 37d8e4f688..71c76e291a 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -13,7 +13,7 @@ use rustpython_common::lock::PyRwLock; /// PyCUnionType - metaclass for Union #[pyclass(name = "UnionType", base = PyType, module = "_ctypes")] -#[derive(Debug, PyPayload, Default)] +#[derive(Debug, Default)] pub struct PyCUnionType {} impl Constructor for PyCUnionType { @@ -121,7 +121,6 @@ impl PyCUnionType {} /// PyCUnion - base class for Union #[pyclass(module = "_ctypes", name = "Union", base = PyCData, metaclass = "PyCUnionType")] -#[derive(PyPayload)] pub struct PyCUnion { /// Common CDataObject for memory buffer pub(super) cdata: PyRwLock, diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 0568ee8194..7dc0cd4ac9 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -729,7 +729,7 @@ mod _io { // TextIO Base has no public constructor #[pyattr] #[pyclass(name = "_TextIOBase", base = _IOBase)] - #[derive(Debug, PyPayload)] + #[derive(Debug)] struct _TextIOBase; #[pyclass(flags(BASETYPE))] @@ -1729,7 +1729,7 @@ mod _io { #[pyattr] #[pyclass(name = "BufferedReader", base = _BufferedIOBase)] - #[derive(Debug, Default, PyPayload)] + #[derive(Debug, Default)] struct BufferedReader { data: PyThreadMutex, } @@ -1798,7 +1798,7 @@ mod _io { #[pyattr] #[pyclass(name = "BufferedWriter", base = _BufferedIOBase)] - #[derive(Debug, Default, PyPayload)] + #[derive(Debug, Default)] struct BufferedWriter { data: PyThreadMutex, } @@ -1843,7 +1843,7 @@ mod _io { #[pyattr] #[pyclass(name = "BufferedRandom", base = _BufferedIOBase)] - #[derive(Debug, Default, PyPayload)] + #[derive(Debug, Default)] struct BufferedRandom { data: PyThreadMutex, } @@ -1903,7 +1903,7 @@ mod _io { #[pyattr] #[pyclass(name = "BufferedRWPair", base = _BufferedIOBase)] - #[derive(Debug, Default, PyPayload)] + #[derive(Debug, Default)] struct BufferedRWPair { read: BufferedReader, write: BufferedWriter, @@ -2335,7 +2335,7 @@ mod _io { #[pyattr] #[pyclass(name = "TextIOWrapper", base = _TextIOBase)] - #[derive(Debug, Default, PyPayload)] + #[derive(Debug, Default)] struct TextIOWrapper { data: PyThreadMutex>, } @@ -3615,7 +3615,7 @@ mod _io { #[pyattr] #[pyclass(name = "StringIO", base = _TextIOBase)] - #[derive(Debug, PyPayload)] + #[derive(Debug)] struct StringIO { buffer: PyRwLock, closed: AtomicCell, @@ -3758,7 +3758,7 @@ mod _io { #[pyattr] #[pyclass(name = "BytesIO", base = _BufferedIOBase)] - #[derive(Debug, PyPayload)] + #[derive(Debug)] struct BytesIO { buffer: PyRwLock, closed: AtomicCell, @@ -4392,7 +4392,7 @@ mod fileio { #[pyattr] #[pyclass(module = "io", name, base = _RawIOBase)] - #[derive(Debug, PyPayload)] + #[derive(Debug)] pub(super) struct FileIO { fd: AtomicCell, closefd: AtomicCell, diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index 17142c0218..5136b1cd95 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -280,10 +280,7 @@ impl Context { let exceptions = exceptions::ExceptionZoo::init(); #[inline] - fn create_object( - payload: T, - cls: &'static Py, - ) -> PyRef { + fn create_object(payload: T, cls: &'static Py) -> PyRef { PyRef::new_ref(payload, cls.to_owned(), None) } From 5f6345d649e6b45689fa930e24b57c57d8bc765f Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 5 Dec 2025 10:23:51 +0900 Subject: [PATCH 06/14] Add base payload to exception --- crates/derive-impl/src/pyclass.rs | 6 +- crates/stdlib/src/csv.rs | 6 +- crates/vm/src/exception_group.rs | 2 +- crates/vm/src/exceptions.rs | 319 ++++++++++++++++++++---------- crates/vm/src/object/payload.rs | 16 ++ 5 files changed, 237 insertions(+), 112 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 2db0b587ff..c41ed2345f 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -539,6 +539,7 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result() <= std::mem::size_of::<#ident>()); impl ::rustpython_vm::PyPayload for #ident { #[inline] fn payload_type_id() -> ::std::any::TypeId { @@ -1102,9 +1103,8 @@ impl GetSetNursery { item_ident: Ident, ) -> Result<()> { assert!(!self.validated, "new item is not allowed after validation"); - if !matches!(kind, GetSetItemKind::Get) && !cfgs.is_empty() { - bail_span!(item_ident, "Only the getter can have #[cfg]",); - } + // Note: Both getter and setter can have #[cfg], but they must have matching cfgs + // since the map key is (name, cfgs). This ensures getter and setter are paired correctly. let entry = self.map.entry((name.clone(), cfgs)).or_default(); let func = match kind { GetSetItemKind::Get => &mut entry.0, diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index 39ca70640d..341bba59e3 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -442,8 +442,8 @@ mod _csv { } } impl TryFrom for QuoteStyle { - type Error = PyTypeError; - fn try_from(num: isize) -> Result { + type Error = (); + fn try_from(num: isize) -> Result { match num { 0 => Ok(Self::Minimal), 1 => Ok(Self::All), @@ -451,7 +451,7 @@ mod _csv { 3 => Ok(Self::None), 4 => Ok(Self::Strings), 5 => Ok(Self::Notnull), - _ => Err(PyTypeError {}), + _ => Err(()), } } } diff --git a/crates/vm/src/exception_group.rs b/crates/vm/src/exception_group.rs index 5eb011960e..3a12fee114 100644 --- a/crates/vm/src/exception_group.rs +++ b/crates/vm/src/exception_group.rs @@ -46,7 +46,7 @@ pub(super) mod types { #[pyexception(name, base = PyBaseException, ctx = "base_exception_group")] #[derive(Debug)] - pub struct PyBaseExceptionGroup {} + pub struct PyBaseExceptionGroup(PyBaseException); #[pyexception] impl PyBaseExceptionGroup { diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 4a8bea2355..820ddc1257 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -964,32 +964,8 @@ impl ExceptionZoo { extend_exception!(PyUnboundLocalError, ctx, excs.unbound_local_error); // os errors: - let errno_getter = - ctx.new_readonly_getset("errno", excs.os_error, |exc: PyBaseExceptionRef| { - let args = exc.args(); - args.first() - .filter(|_| args.len() > 1 && args.len() <= 5) - .cloned() - }); - let strerror_getter = - ctx.new_readonly_getset("strerror", excs.os_error, |exc: PyBaseExceptionRef| { - let args = exc.args(); - args.get(1) - .filter(|_| args.len() >= 2 && args.len() <= 5) - .cloned() - }); - extend_exception!(PyOSError, ctx, excs.os_error, { - // POSIX exception code - "errno" => errno_getter.clone(), - // exception strerror - "strerror" => strerror_getter.clone(), - // exception filename - "filename" => ctx.none(), - // second exception filename - "filename2" => ctx.none(), - }); - #[cfg(windows)] - excs.os_error.set_str_attr("winerror", ctx.none(), ctx); + // PyOSError now uses struct fields with pygetset, no need for dynamic attributes + extend_exception!(PyOSError, ctx, excs.os_error); extend_exception!(PyBlockingIOError, ctx, excs.blocking_io_error); extend_exception!(PyChildProcessError, ctx, excs.child_process_error); @@ -1219,9 +1195,10 @@ pub(crate) fn errno_to_exc_type(_errno: i32, _vm: &VirtualMachine) -> Option<&'s pub(super) mod types { use crate::common::lock::PyRwLock; + use crate::object::{Traverse, TraverseFn}; #[cfg_attr(target_arch = "wasm32", allow(unused_imports))] use crate::{ - AsObject, PyObjectRef, PyRef, PyResult, VirtualMachine, + AsObject, Py, PyAtomicRef, PyObject, PyObjectRef, PyRef, PyResult, VirtualMachine, builtins::{ PyInt, PyStrRef, PyTupleRef, PyTypeRef, traceback::PyTracebackRef, tuple::IntoPyTuple, }, @@ -1255,23 +1232,23 @@ pub(super) mod types { #[pyexception(name, base = PyBaseException, ctx = "system_exit", impl)] #[derive(Debug)] - pub struct PySystemExit {} + pub struct PySystemExit(PyBaseException); #[pyexception(name, base = PyBaseException, ctx = "generator_exit", impl)] #[derive(Debug)] - pub struct PyGeneratorExit {} + pub struct PyGeneratorExit(PyBaseException); #[pyexception(name, base = PyBaseException, ctx = "keyboard_interrupt", impl)] #[derive(Debug)] - pub struct PyKeyboardInterrupt {} + pub struct PyKeyboardInterrupt(PyBaseException); #[pyexception(name, base = PyBaseException, ctx = "exception_type", impl)] #[derive(Debug)] - pub struct PyException {} + pub struct PyException(PyBaseException); #[pyexception(name, base = PyException, ctx = "stop_iteration")] #[derive(Debug)] - pub struct PyStopIteration {} + pub struct PyStopIteration(PyException); #[pyexception] impl PyStopIteration { @@ -1289,31 +1266,30 @@ pub(super) mod types { #[pyexception(name, base = PyException, ctx = "stop_async_iteration", impl)] #[derive(Debug)] - pub struct PyStopAsyncIteration {} + pub struct PyStopAsyncIteration(PyException); #[pyexception(name, base = PyException, ctx = "arithmetic_error", impl)] #[derive(Debug)] - pub struct PyArithmeticError {} + pub struct PyArithmeticError(PyException); #[pyexception(name, base = PyArithmeticError, ctx = "floating_point_error", impl)] #[derive(Debug)] - pub struct PyFloatingPointError {} - + pub struct PyFloatingPointError(PyArithmeticError); #[pyexception(name, base = PyArithmeticError, ctx = "overflow_error", impl)] #[derive(Debug)] - pub struct PyOverflowError {} + pub struct PyOverflowError(PyArithmeticError); #[pyexception(name, base = PyArithmeticError, ctx = "zero_division_error", impl)] #[derive(Debug)] - pub struct PyZeroDivisionError {} + pub struct PyZeroDivisionError(PyArithmeticError); #[pyexception(name, base = PyException, ctx = "assertion_error", impl)] #[derive(Debug)] - pub struct PyAssertionError {} + pub struct PyAssertionError(PyException); #[pyexception(name, base = PyException, ctx = "attribute_error")] #[derive(Debug)] - pub struct PyAttributeError {} + pub struct PyAttributeError(PyException); #[pyexception] impl PyAttributeError { @@ -1340,15 +1316,15 @@ pub(super) mod types { #[pyexception(name, base = PyException, ctx = "buffer_error", impl)] #[derive(Debug)] - pub struct PyBufferError {} + pub struct PyBufferError(PyException); #[pyexception(name, base = PyException, ctx = "eof_error", impl)] #[derive(Debug)] - pub struct PyEOFError {} + pub struct PyEOFError(PyException); #[pyexception(name, base = PyException, ctx = "import_error")] #[derive(Debug)] - pub struct PyImportError {} + pub struct PyImportError(PyException); #[pyexception] impl PyImportError { @@ -1393,19 +1369,19 @@ pub(super) mod types { #[pyexception(name, base = PyImportError, ctx = "module_not_found_error", impl)] #[derive(Debug)] - pub struct PyModuleNotFoundError {} + pub struct PyModuleNotFoundError(PyImportError); #[pyexception(name, base = PyException, ctx = "lookup_error", impl)] #[derive(Debug)] - pub struct PyLookupError {} + pub struct PyLookupError(PyException); #[pyexception(name, base = PyLookupError, ctx = "index_error", impl)] #[derive(Debug)] - pub struct PyIndexError {} + pub struct PyIndexError(PyLookupError); #[pyexception(name, base = PyLookupError, ctx = "key_error")] #[derive(Debug)] - pub struct PyKeyError {} + pub struct PyKeyError(PyLookupError); #[pyexception] impl PyKeyError { @@ -1425,19 +1401,60 @@ pub(super) mod types { #[pyexception(name, base = PyException, ctx = "memory_error", impl)] #[derive(Debug)] - pub struct PyMemoryError {} + pub struct PyMemoryError(PyException); #[pyexception(name, base = PyException, ctx = "name_error", impl)] #[derive(Debug)] - pub struct PyNameError {} + pub struct PyNameError(PyException); #[pyexception(name, base = PyNameError, ctx = "unbound_local_error", impl)] #[derive(Debug)] - pub struct PyUnboundLocalError {} + pub struct PyUnboundLocalError(PyNameError); #[pyexception(name, base = PyException, ctx = "os_error")] - #[derive(Debug)] - pub struct PyOSError {} + pub struct PyOSError { + base: PyException, + myerrno: PyAtomicRef>, + strerror: PyAtomicRef>, + filename: PyAtomicRef>, + filename2: PyAtomicRef>, + #[cfg(windows)] + winerror: PyAtomicRef>, + } + + impl crate::class::PySubclass for PyOSError { + type Base = PyException; + fn as_base(&self) -> &Self::Base { + &self.base + } + } + + impl std::fmt::Debug for PyOSError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyOSError").finish_non_exhaustive() + } + } + + unsafe impl Traverse for PyOSError { + fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { + if let Some(obj) = self.myerrno.deref() { + tracer_fn(obj); + } + if let Some(obj) = self.strerror.deref() { + tracer_fn(obj); + } + if let Some(obj) = self.filename.deref() { + tracer_fn(obj); + } + if let Some(obj) = self.filename2.deref() { + tracer_fn(obj); + } + #[cfg(windows)] + if let Some(obj) = self.winerror.deref() { + tracer_fn(obj); + } + } + } // OS Errors: #[pyexception] @@ -1481,24 +1498,48 @@ pub(super) mod types { pub fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { let len = args.args.len(); let mut new_args = args; - if (3..=5).contains(&len) { - zelf.set_attr("filename", new_args.args[2].clone(), vm)?; + + // All OSError subclasses use #[repr(transparent)] wrapping PyOSError, + // so we can safely access the PyOSError fields through pointer cast + // SAFETY: All OSError subclasses (FileNotFoundError, etc.) are + // #[repr(transparent)] wrappers around PyOSError with identical memory layout + #[allow(deprecated)] + let exc: &Py = zelf.downcast_ref::().unwrap(); + + // SAFETY: slot_init is called during object initialization, + // so fields are None and swap result can be safely ignored + unsafe { + if len >= 1 { + let _ = exc.myerrno.swap(Some(new_args.args[0].clone())); + } + if len >= 2 { + let _ = exc.strerror.swap(Some(new_args.args[1].clone())); + } + if len >= 3 { + let _ = exc.filename.swap(Some(new_args.args[2].clone())); + } #[cfg(windows)] - if let Some(winerror) = new_args.args.get(3) { - zelf.set_attr("winerror", winerror.clone(), vm)?; - // Convert winerror to errno and replace args[0] (CPython behavior) - if let Some(winerror_int) = winerror + if len >= 4 { + let winerror = &new_args.args.get(3).cloned(); + let arg = if let Some(winerror_int) = winerror .downcast_ref::() .and_then(|w| w.try_to_primitive::(vm).ok()) { let errno = crate::common::os::winerror_to_errno(winerror_int); - new_args.args[0] = vm.new_pyobj(errno); - } + arg = vm.new_pyobj(errno); + } else { + winerror + }; + + let _ = exc.winerror.swap(arg); } - if let Some(filename2) = new_args.args.get(4) { - zelf.set_attr("filename2", filename2.clone(), vm)?; + if len >= 5 { + let _ = exc.filename2.swap(new_args.args.get(4).cloned()); } + } + // args are truncated to 2 for compatibility + if len > 2 { new_args.args.truncate(2); } PyBaseException::slot_init(zelf, new_args, vm) @@ -1582,23 +1623,80 @@ pub(super) mod types { } result.into_pytuple(vm) } + + // Getters and setters for OSError fields + #[pygetset] + fn errno(&self) -> Option { + self.myerrno.to_owned() + } + + #[pygetset(setter)] + fn set_errno(&self, value: Option, vm: &VirtualMachine) { + self.myerrno.swap_to_temporary_refs(value, vm); + } + + #[pygetset(name = "strerror")] + fn get_strerror(&self) -> Option { + self.strerror.to_owned() + } + + #[pygetset(setter, name = "strerror")] + fn set_strerror(&self, value: Option, vm: &VirtualMachine) { + self.strerror.swap_to_temporary_refs(value, vm); + } + + #[pygetset] + fn filename(&self) -> Option { + self.filename.to_owned() + } + + #[pygetset(setter)] + fn set_filename(&self, value: Option, vm: &VirtualMachine) { + self.filename.swap_to_temporary_refs(value, vm); + } + + #[pygetset] + fn filename2(&self) -> Option { + self.filename2.to_owned() + } + + #[pygetset(setter)] + fn set_filename2(&self, value: Option, vm: &VirtualMachine) { + self.filename2.swap_to_temporary_refs(value, vm); + } + + #[cfg(windows)] + #[pygetset] + fn winerror(&self) -> Option { + self.winerror.to_owned() + } + + #[cfg(windows)] + #[pygetset(setter)] + fn set_winerror(&self, value: Option, vm: &VirtualMachine) { + self.winerror.swap_to_temporary_refs(value, vm); + } } #[pyexception(name, base = PyOSError, ctx = "blocking_io_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyBlockingIOError {} + pub struct PyBlockingIOError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "child_process_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyChildProcessError {} + pub struct PyChildProcessError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "connection_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyConnectionError {} + pub struct PyConnectionError(PyOSError); #[pyexception(name, base = PyConnectionError, ctx = "broken_pipe_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyBrokenPipeError {} + pub struct PyBrokenPipeError(PyConnectionError); #[pyexception( name, @@ -1606,8 +1704,9 @@ pub(super) mod types { ctx = "connection_aborted_error", impl )] + #[repr(transparent)] #[derive(Debug)] - pub struct PyConnectionAbortedError {} + pub struct PyConnectionAbortedError(PyConnectionError); #[pyexception( name, @@ -1615,64 +1714,74 @@ pub(super) mod types { ctx = "connection_refused_error", impl )] + #[repr(transparent)] #[derive(Debug)] - pub struct PyConnectionRefusedError {} + pub struct PyConnectionRefusedError(PyConnectionError); #[pyexception(name, base = PyConnectionError, ctx = "connection_reset_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyConnectionResetError {} + pub struct PyConnectionResetError(PyConnectionError); #[pyexception(name, base = PyOSError, ctx = "file_exists_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyFileExistsError {} + pub struct PyFileExistsError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "file_not_found_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyFileNotFoundError {} + pub struct PyFileNotFoundError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "interrupted_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyInterruptedError {} + pub struct PyInterruptedError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "is_a_directory_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyIsADirectoryError {} + pub struct PyIsADirectoryError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "not_a_directory_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyNotADirectoryError {} + pub struct PyNotADirectoryError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "permission_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyPermissionError {} + pub struct PyPermissionError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "process_lookup_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyProcessLookupError {} + pub struct PyProcessLookupError(PyOSError); #[pyexception(name, base = PyOSError, ctx = "timeout_error", impl)] + #[repr(transparent)] #[derive(Debug)] - pub struct PyTimeoutError {} + pub struct PyTimeoutError(PyOSError); #[pyexception(name, base = PyException, ctx = "reference_error", impl)] #[derive(Debug)] - pub struct PyReferenceError {} + pub struct PyReferenceError(PyException); #[pyexception(name, base = PyException, ctx = "runtime_error", impl)] #[derive(Debug)] - pub struct PyRuntimeError {} + pub struct PyRuntimeError(PyException); #[pyexception(name, base = PyRuntimeError, ctx = "not_implemented_error", impl)] #[derive(Debug)] - pub struct PyNotImplementedError {} + pub struct PyNotImplementedError(PyRuntimeError); #[pyexception(name, base = PyRuntimeError, ctx = "recursion_error", impl)] #[derive(Debug)] - pub struct PyRecursionError {} + pub struct PyRecursionError(PyRuntimeError); #[pyexception(name, base = PyException, ctx = "syntax_error")] #[derive(Debug)] - pub struct PySyntaxError {} + pub struct PySyntaxError(PyException); #[pyexception] impl PySyntaxError { @@ -1766,7 +1875,7 @@ pub(super) mod types { ctx = "incomplete_input_error" )] #[derive(Debug)] - pub struct PyIncompleteInputError {} + pub struct PyIncompleteInputError(PySyntaxError); #[pyexception] impl PyIncompleteInputError { @@ -1784,31 +1893,31 @@ pub(super) mod types { #[pyexception(name, base = PySyntaxError, ctx = "indentation_error", impl)] #[derive(Debug)] - pub struct PyIndentationError {} + pub struct PyIndentationError(PySyntaxError); #[pyexception(name, base = PyIndentationError, ctx = "tab_error", impl)] #[derive(Debug)] - pub struct PyTabError {} + pub struct PyTabError(PyIndentationError); #[pyexception(name, base = PyException, ctx = "system_error", impl)] #[derive(Debug)] - pub struct PySystemError {} + pub struct PySystemError(PyException); #[pyexception(name, base = PyException, ctx = "type_error", impl)] #[derive(Debug)] - pub struct PyTypeError {} + pub struct PyTypeError(PyException); #[pyexception(name, base = PyException, ctx = "value_error", impl)] #[derive(Debug)] - pub struct PyValueError {} + pub struct PyValueError(PyException); #[pyexception(name, base = PyValueError, ctx = "unicode_error", impl)] #[derive(Debug)] - pub struct PyUnicodeError {} + pub struct PyUnicodeError(PyValueError); #[pyexception(name, base = PyUnicodeError, ctx = "unicode_decode_error")] #[derive(Debug)] - pub struct PyUnicodeDecodeError {} + pub struct PyUnicodeDecodeError(PyUnicodeError); #[pyexception] impl PyUnicodeDecodeError { @@ -1859,7 +1968,7 @@ pub(super) mod types { #[pyexception(name, base = PyUnicodeError, ctx = "unicode_encode_error")] #[derive(Debug)] - pub struct PyUnicodeEncodeError {} + pub struct PyUnicodeEncodeError(PyUnicodeError); #[pyexception] impl PyUnicodeEncodeError { @@ -1910,7 +2019,7 @@ pub(super) mod types { #[pyexception(name, base = PyUnicodeError, ctx = "unicode_translate_error")] #[derive(Debug)] - pub struct PyUnicodeTranslateError {} + pub struct PyUnicodeTranslateError(PyUnicodeError); #[pyexception] impl PyUnicodeTranslateError { @@ -1958,54 +2067,54 @@ pub(super) mod types { #[cfg(feature = "jit")] #[pyexception(name, base = PyException, ctx = "jit_error", impl)] #[derive(Debug)] - pub struct PyJitError {} + pub struct PyJitError(PyException); // Warnings #[pyexception(name, base = PyException, ctx = "warning", impl)] #[derive(Debug)] - pub struct PyWarning {} + pub struct PyWarning(PyException); #[pyexception(name, base = PyWarning, ctx = "deprecation_warning", impl)] #[derive(Debug)] - pub struct PyDeprecationWarning {} + pub struct PyDeprecationWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "pending_deprecation_warning", impl)] #[derive(Debug)] - pub struct PyPendingDeprecationWarning {} + pub struct PyPendingDeprecationWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "runtime_warning", impl)] #[derive(Debug)] - pub struct PyRuntimeWarning {} + pub struct PyRuntimeWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "syntax_warning", impl)] #[derive(Debug)] - pub struct PySyntaxWarning {} + pub struct PySyntaxWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "user_warning", impl)] #[derive(Debug)] - pub struct PyUserWarning {} + pub struct PyUserWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "future_warning", impl)] #[derive(Debug)] - pub struct PyFutureWarning {} + pub struct PyFutureWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "import_warning", impl)] #[derive(Debug)] - pub struct PyImportWarning {} + pub struct PyImportWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "unicode_warning", impl)] #[derive(Debug)] - pub struct PyUnicodeWarning {} + pub struct PyUnicodeWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "bytes_warning", impl)] #[derive(Debug)] - pub struct PyBytesWarning {} + pub struct PyBytesWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "resource_warning", impl)] #[derive(Debug)] - pub struct PyResourceWarning {} + pub struct PyResourceWarning(PyWarning); #[pyexception(name, base = PyWarning, ctx = "encoding_warning", impl)] #[derive(Debug)] - pub struct PyEncodingWarning {} + pub struct PyEncodingWarning(PyWarning); } diff --git a/crates/vm/src/object/payload.rs b/crates/vm/src/object/payload.rs index f5debf8509..48c0b586de 100644 --- a/crates/vm/src/object/payload.rs +++ b/crates/vm/src/object/payload.rs @@ -101,6 +101,22 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { { let exact_class = Self::class(&vm.ctx); if cls.fast_issubclass(exact_class) { + if exact_class.slots.basicsize != cls.slots.basicsize { + #[cold] + #[inline(never)] + fn _into_ref_size_error( + vm: &VirtualMachine, + cls: &PyTypeRef, + exact_class: &Py, + ) -> PyBaseExceptionRef { + vm.new_type_error(format!( + "cannot create '{}' from a subclass with a different size '{}'", + cls.name(), + exact_class.name() + )) + } + return Err(_into_ref_size_error(vm, &cls, exact_class)); + } Ok(self._into_ref(cls, &vm.ctx)) } else { #[cold] From 24954242e09285c40f3a9055bebe06bea6a42262 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 6 Dec 2025 10:42:34 +0900 Subject: [PATCH 07/14] PyStructSequecne base --- crates/derive-impl/src/pystructseq.rs | 50 ++++++++++++++++++++++++--- crates/stdlib/src/csv.rs | 2 +- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/crates/derive-impl/src/pystructseq.rs b/crates/derive-impl/src/pystructseq.rs index c43673fe97..9908726151 100644 --- a/crates/derive-impl/src/pystructseq.rs +++ b/crates/derive-impl/src/pystructseq.rs @@ -446,8 +446,9 @@ pub(crate) fn impl_pystruct_sequence( }; let output = quote! { - // The Python type struct (user-defined, possibly empty) - #pytype_vis struct #pytype_ident; + // The Python type struct - newtype wrapping PyTuple + #[repr(transparent)] + #pytype_vis struct #pytype_ident(pub ::rustpython_vm::builtins::PyTuple); // PyClassDef for Python type impl ::rustpython_vm::class::PyClassDef for #pytype_ident { @@ -476,13 +477,52 @@ pub(crate) fn impl_pystruct_sequence( } } - // MaybeTraverse (empty - no GC fields in empty struct) + // PyPayload - following PyBool pattern (use base type's payload_type_id) + impl ::rustpython_vm::PyPayload for #pytype_ident { + #[inline] + fn payload_type_id() -> ::std::any::TypeId { + <::rustpython_vm::builtins::PyTuple as ::rustpython_vm::PyPayload>::payload_type_id() + } + + #[inline] + fn validate_downcastable_from(obj: &::rustpython_vm::PyObject) -> bool { + obj.class().fast_issubclass(::static_type()) + } + + fn class(_ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { + ::static_type() + } + } + + // MaybeTraverse - delegate to inner PyTuple impl ::rustpython_vm::object::MaybeTraverse for #pytype_ident { - fn try_traverse(&self, _traverse_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) { - // Empty struct has no fields to traverse + fn try_traverse(&self, traverse_fn: &mut ::rustpython_vm::object::TraverseFn<'_>) { + self.0.try_traverse(traverse_fn) } } + // Deref to access inner PyTuple + impl ::std::ops::Deref for #pytype_ident { + type Target = ::rustpython_vm::builtins::PyTuple; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + // PySubclass for proper inheritance + impl ::rustpython_vm::class::PySubclass for #pytype_ident { + type Base = ::rustpython_vm::builtins::PyTuple; + + #[inline] + fn as_base(&self) -> &Self::Base { + &self.0 + } + } + + impl ::rustpython_vm::class::PySubclassTransparent for #pytype_ident {} + // PyStructSequence trait for Python type impl ::rustpython_vm::types::PyStructSequence for #pytype_ident { type Data = #data_ident; diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index 341bba59e3..3c7cc2ff80 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -5,7 +5,7 @@ mod _csv { use crate::common::lock::PyMutex; use crate::vm::{ AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyInt, PyNone, PyStr, PyType, PyTypeError, PyTypeRef}, + builtins::{PyBaseExceptionRef, PyInt, PyNone, PyStr, PyType, PyTypeRef}, function::{ArgIterable, ArgumentError, FromArgs, FuncArgs, OptionalArg}, protocol::{PyIter, PyIterReturn}, raise_if_stop, From 2819ebf1060a3b5b2851d322ac6481c726c88438 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 6 Dec 2025 17:29:48 +0900 Subject: [PATCH 08/14] derive from_data? --- crates/derive-impl/src/pystructseq.rs | 14 ++++++++++++++ crates/vm/src/types/structseq.rs | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/derive-impl/src/pystructseq.rs b/crates/derive-impl/src/pystructseq.rs index 9908726151..37e09649a1 100644 --- a/crates/derive-impl/src/pystructseq.rs +++ b/crates/derive-impl/src/pystructseq.rs @@ -447,6 +447,7 @@ pub(crate) fn impl_pystruct_sequence( let output = quote! { // The Python type struct - newtype wrapping PyTuple + #[derive(Debug)] #[repr(transparent)] #pytype_vis struct #pytype_ident(pub ::rustpython_vm::builtins::PyTuple); @@ -526,6 +527,19 @@ pub(crate) fn impl_pystruct_sequence( // PyStructSequence trait for Python type impl ::rustpython_vm::types::PyStructSequence for #pytype_ident { type Data = #data_ident; + + // fn from_data(data: Self::Data, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTupleRef { + // let tuple = ::into_tuple(data, vm); + // // Create Self(tuple) as the payload + // let wrapped = #pytype_ident(tuple); + // let result = ::rustpython_vm::PyRef::new_ref( + // wrapped, + // ::static_type().to_owned(), + // None + // ); + // // SAFETY: PyRef has same layout as PyRef due to #[repr(transparent)] + // unsafe { ::std::mem::transmute(result) } + // } } // ToPyObject for Data struct - uses PyStructSequence::from_data diff --git a/crates/vm/src/types/structseq.rs b/crates/vm/src/types/structseq.rs index b2ff5868d4..90b4c92ff8 100644 --- a/crates/vm/src/types/structseq.rs +++ b/crates/vm/src/types/structseq.rs @@ -184,10 +184,10 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { /// Convert a Data struct into a PyStructSequence instance. fn from_data(data: Self::Data, vm: &VirtualMachine) -> PyTupleRef { + let tuple = + ::into_tuple(data, vm); let typ = Self::static_type(); - data.into_tuple(vm) - .into_ref_with_type(vm, typ.to_owned()) - .unwrap() + tuple.into_ref_with_type(vm, typ.to_owned()).unwrap() } #[pyslot] From 2fb78978cd517d0ebe95a134181420d2fa267391 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 8 Dec 2025 10:09:49 +0900 Subject: [PATCH 09/14] Allow with() on pyexception --- crates/derive-impl/src/pyclass.rs | 37 ++++++++++++++---- crates/vm/src/exceptions.rs | 65 ++++++++++++++++++++----------- 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index c41ed2345f..3cfb7702d3 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -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, @@ -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)* diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 820ddc1257..f146eecd40 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1198,9 +1198,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}, @@ -1457,7 +1459,44 @@ pub(super) mod types { } // OS Errors: - #[pyexception] + impl Constructor for PyOSError { + type Args = FuncArgs; + + fn py_new(_cls: &Py, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + 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, vm: &VirtualMachine) -> Option { @@ -1473,26 +1512,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<()> { From 85294c23b6516927cc6920d715e5ecd8adf571e1 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 10 Dec 2025 10:21:18 +0900 Subject: [PATCH 10/14] new_simple_os_error --- crates/stdlib/src/bz2.rs | 2 +- crates/stdlib/src/faulthandler.rs | 2 +- crates/stdlib/src/lzma.rs | 2 +- crates/stdlib/src/openssl.rs | 14 +++++--- crates/stdlib/src/select.rs | 2 +- crates/stdlib/src/socket.rs | 49 ++++++++++++++++------------ crates/stdlib/src/ssl.rs | 30 +++++++++-------- crates/vm/src/exceptions.rs | 35 +++++++------------- crates/vm/src/stdlib/builtins.rs | 4 +-- crates/vm/src/stdlib/ctypes.rs | 4 +-- crates/vm/src/stdlib/io.rs | 29 ++++++++-------- crates/vm/src/stdlib/nt.rs | 2 +- crates/vm/src/stdlib/os.rs | 10 +++--- crates/vm/src/stdlib/posix.rs | 8 ++--- crates/vm/src/stdlib/posix_compat.rs | 2 +- crates/vm/src/stdlib/signal.rs | 2 +- crates/vm/src/stdlib/sys.rs | 11 ++++--- crates/vm/src/stdlib/time.rs | 8 ++--- crates/vm/src/stdlib/winreg.rs | 48 ++++++++++++++------------- crates/vm/src/vm/vm_new.rs | 33 +++++++++++++------ 20 files changed, 162 insertions(+), 135 deletions(-) diff --git a/crates/stdlib/src/bz2.rs b/crates/stdlib/src/bz2.rs index a2a40953cf..0e2f5c4d5e 100644 --- a/crates/stdlib/src/bz2.rs +++ b/crates/stdlib/src/bz2.rs @@ -79,7 +79,7 @@ mod _bz2 { state .decompress(data, max_length, BUFSIZ, vm) .map_err(|e| match e { - DecompressError::Decompress(err) => vm.new_os_error(err.to_string()), + DecompressError::Decompress(err) => vm.new_simple_os_error(err.to_string()), DecompressError::Eof(err) => err.to_pyexception(vm), }) } diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index a06b063d30..67ed3f9f53 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -999,7 +999,7 @@ mod decl { libc::signal(args.signum, faulthandler_user_signal as libc::sighandler_t) }; if prev == libc::SIG_ERR { - return Err(vm.new_os_error(format!( + return Err(vm.new_simple_os_error(format!( "Failed to register signal handler for signal {}", args.signum ))); diff --git a/crates/stdlib/src/lzma.rs b/crates/stdlib/src/lzma.rs index 855a5eae56..5c4b74c526 100644 --- a/crates/stdlib/src/lzma.rs +++ b/crates/stdlib/src/lzma.rs @@ -182,7 +182,7 @@ mod _lzma { state .decompress(data, max_length, BUFSIZ, vm) .map_err(|e| match e { - DecompressError::Decompress(err) => vm.new_os_error(err.to_string()), + DecompressError::Decompress(err) => vm.new_simple_os_error(err.to_string()), DecompressError::Eof(err) => err.to_pyexception(vm), }) } diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index ea67d605f7..1bf5c96a92 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -1480,7 +1480,7 @@ mod _ssl { vm.ctx.exceptions.file_not_found_error.to_owned(), e.to_string(), ), - _ => vm.new_os_error(e.to_string()), + _ => vm.new_simple_os_error(e.to_string()), } })?; @@ -3550,10 +3550,10 @@ mod _ssl { let mut combined_pem = String::new(); let entries = read_dir(root) - .map_err(|err| vm.new_os_error(format!("read cert root: {}", err)))?; + .map_err(|err| vm.new_simple_os_error(format!("read cert root: {}", err)))?; for entry in entries { - let entry = - entry.map_err(|err| vm.new_os_error(format!("iter cert root: {}", err)))?; + let entry = entry + .map_err(|err| vm.new_simple_os_error(format!("iter cert root: {}", err)))?; let path = entry.path(); if !path.is_file() { @@ -3563,7 +3563,11 @@ mod _ssl { File::open(&path) .and_then(|mut file| file.read_to_string(&mut combined_pem)) .map_err(|err| { - vm.new_os_error(format!("open cert file {}: {}", path.display(), err)) + vm.new_simple_os_error(format!( + "open cert file {}: {}", + path.display(), + err + )) })?; combined_pem.push('\n'); diff --git a/crates/stdlib/src/select.rs b/crates/stdlib/src/select.rs index 5639a66d2c..ef552a3a63 100644 --- a/crates/stdlib/src/select.rs +++ b/crates/stdlib/src/select.rs @@ -581,7 +581,7 @@ mod decl { return Err(vm.new_value_error("negative sizehint")); } if !matches!(args.flags, 0 | libc::EPOLL_CLOEXEC) { - return Err(vm.new_os_error("invalid flags".to_owned())); + return Err(vm.new_simple_os_error("invalid flags".to_owned())); } Self::new().map_err(|e| e.into_pyexception(vm)) } diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index b4e4dac88a..5d21d55f41 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -944,8 +944,10 @@ mod _socket { ArgStrOrBytesLike::Buf(_) => ffi::OsStr::from_bytes(bytes).into(), ArgStrOrBytesLike::Str(s) => vm.fsencode(s)?, }; - socket2::SockAddr::unix(path) - .map_err(|_| vm.new_os_error("AF_UNIX path too long".to_owned()).into()) + socket2::SockAddr::unix(path).map_err(|_| { + vm.new_simple_os_error("AF_UNIX path too long".to_owned()) + .into() + }) } c::AF_INET => { let tuple: PyTupleRef = addr.downcast().map_err(|obj| { @@ -996,7 +998,9 @@ mod _socket { } Ok(addr6.into()) } - _ => Err(vm.new_os_error(format!("{caller}(): bad family")).into()), + _ => Err(vm + .new_simple_os_error(format!("{caller}(): bad family")) + .into()), } } @@ -1420,10 +1424,10 @@ mod _socket { .map(|(_, _, buf)| buf.len()) .try_fold(0, |sum, len| { let space = checked_cmsg_space(len).ok_or_else(|| { - vm.new_os_error("ancillary data item too large".to_owned()) + vm.new_simple_os_error("ancillary data item too large".to_owned()) })?; usize::checked_add(sum, space) - .ok_or_else(|| vm.new_os_error("too much ancillary data".to_owned())) + .ok_or_else(|| vm.new_simple_os_error("too much ancillary data".to_owned())) })?; let mut cmsg_buffer = vec![0u8; capacity]; @@ -1552,7 +1556,7 @@ mod _socket { } else { if buflen <= 0 || buflen > 1024 { return Err(vm - .new_os_error("getsockopt buflen out of range".to_owned()) + .new_simple_os_error("getsockopt buflen out of range".to_owned()) .into()); } let mut buf = vec![0u8; buflen as usize]; @@ -1738,7 +1742,7 @@ mod _socket { gethostname::gethostname() .into_string() .map(|hostname| vm.ctx.new_str(hostname)) - .map_err(|err| vm.new_os_error(err.into_string().unwrap())) + .map_err(|err| vm.new_simple_os_error(err.into_string().unwrap())) } #[cfg(all(unix, not(target_os = "redox")))] @@ -1754,15 +1758,16 @@ mod _socket { .parse::() .map(|ip_addr| Vec::::from(ip_addr.octets())) .map_err(|_| { - vm.new_os_error("illegal IP address string passed to inet_aton".to_owned()) + vm.new_simple_os_error("illegal IP address string passed to inet_aton".to_owned()) }) } #[pyfunction] fn inet_ntoa(packed_ip: ArgBytesLike, vm: &VirtualMachine) -> PyResult { let packed_ip = packed_ip.borrow_buf(); - let packed_ip = <&[u8; 4]>::try_from(&*packed_ip) - .map_err(|_| vm.new_os_error("packed IP wrong length for inet_ntoa".to_owned()))?; + let packed_ip = <&[u8; 4]>::try_from(&*packed_ip).map_err(|_| { + vm.new_simple_os_error("packed IP wrong length for inet_ntoa".to_owned()) + })?; Ok(vm.ctx.new_str(Ipv4Addr::from(*packed_ip).to_string())) } @@ -1784,7 +1789,7 @@ mod _socket { let cstr_proto = cstr_opt_as_ptr(&cstr_proto); let serv = unsafe { c::getservbyname(cstr_name.as_ptr() as _, cstr_proto as _) }; if serv.is_null() { - return Err(vm.new_os_error("service/proto not found".to_owned())); + return Err(vm.new_simple_os_error("service/proto not found".to_owned())); } let port = unsafe { (*serv).s_port }; Ok(u16::from_be(port as u16)) @@ -1806,7 +1811,7 @@ mod _socket { let cstr_proto = cstr_opt_as_ptr(&cstr_proto); let serv = unsafe { c::getservbyport(port.to_be() as _, cstr_proto as _) }; if serv.is_null() { - return Err(vm.new_os_error("port/proto not found".to_owned())); + return Err(vm.new_simple_os_error("port/proto not found".to_owned())); } let s = unsafe { ffi::CStr::from_ptr((*serv).s_name as _) }; Ok(s.to_string_lossy().into_owned()) @@ -2023,16 +2028,20 @@ mod _socket { c::AF_INET => ip_string .as_str() .parse::() - .map_err(|_| vm.new_os_error(ERROR_MSG.to_owned()))? + .map_err(|_| vm.new_simple_os_error(ERROR_MSG.to_owned()))? .octets() .to_vec(), c::AF_INET6 => ip_string .as_str() .parse::() - .map_err(|_| vm.new_os_error(ERROR_MSG.to_owned()))? + .map_err(|_| vm.new_simple_os_error(ERROR_MSG.to_owned()))? .octets() .to_vec(), - _ => return Err(vm.new_os_error("Address family not supported by protocol".to_owned())), + _ => { + return Err( + vm.new_simple_os_error("Address family not supported by protocol".to_owned()) + ); + } }; Ok(ip_addr) } @@ -2062,7 +2071,7 @@ mod _socket { let cstr = name.to_cstring(vm)?; let proto = unsafe { c::getprotobyname(cstr.as_ptr() as _) }; if proto.is_null() { - return Err(vm.new_os_error("protocol not found".to_owned())); + return Err(vm.new_simple_os_error("protocol not found".to_owned())); } let num = unsafe { (*proto).p_proto }; Ok(vm.ctx.new_int(num).into()) @@ -2095,14 +2104,14 @@ mod _socket { let mut ainfo = res.next().unwrap(); if res.next().is_some() { return Err(vm - .new_os_error("sockaddr resolved to multiple addresses".to_owned()) + .new_simple_os_error("sockaddr resolved to multiple addresses".to_owned()) .into()); } match &mut ainfo.sockaddr { SocketAddr::V4(_) => { if address.len() != 2 { return Err(vm - .new_os_error("IPv4 sockaddr must be 2 tuple".to_owned()) + .new_simple_os_error("IPv4 sockaddr must be 2 tuple".to_owned()) .into()); } } @@ -2273,7 +2282,7 @@ mod _socket { let ainfo = res.next().unwrap()?; if res.next().is_some() { return Err(vm - .new_os_error("wildcard resolved to multiple address".to_owned()) + .new_simple_os_error("wildcard resolved to multiple address".to_owned()) .into()); } return Ok(ainfo.sockaddr); @@ -2283,7 +2292,7 @@ mod _socket { c::AF_INET | c::AF_UNSPEC => {} _ => { return Err(vm - .new_os_error("address family mismatched".to_owned()) + .new_simple_os_error("address family mismatched".to_owned()) .into()); } } diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 14e087668b..1735226082 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -1543,7 +1543,7 @@ mod _ssl { // If there were errors but some certs loaded, just continue // If NO certs loaded and there were errors, report the first error if *self.x509_cert_count.read() == 0 && !result.errors.is_empty() { - return Err(vm.new_os_error(format!( + return Err(vm.new_simple_os_error(format!( "Failed to load native certificates: {}", result.errors[0] ))); @@ -1889,8 +1889,8 @@ mod _ssl { // Validate that the file contains DH parameters // Read the file and check for DH PARAMETERS header - let contents = - std::fs::read_to_string(&path_str).map_err(|e| vm.new_os_error(e.to_string()))?; + let contents = std::fs::read_to_string(&path_str) + .map_err(|e| vm.new_simple_os_error(e.to_string()))?; if !contents.contains("BEGIN DH PARAMETERS") && !contents.contains("BEGIN X9.42 DH PARAMETERS") @@ -2258,7 +2258,7 @@ mod _ssl { std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => { e.into_pyexception(vm) } - _ => vm.new_os_error(e.to_string()), + _ => vm.new_simple_os_error(e.to_string()), })?; match self.try_parse_crl(&data) { @@ -2468,7 +2468,7 @@ mod _ssl { // Clear the error so it's only raised once *self.deferred_cert_error.write() = None; // Raise OSError with the stored error message - return Err(vm.new_os_error(error_msg)); + return Err(vm.new_simple_os_error(error_msg)); } Ok(()) } @@ -2683,10 +2683,10 @@ mod _ssl { let py_socket: PyRef = self.sock.clone().try_into_value(vm)?; let socket = py_socket .sock() - .map_err(|e| vm.new_os_error(format!("Failed to get socket: {e}")))?; + .map_err(|e| vm.new_simple_os_error(format!("Failed to get socket: {e}")))?; let timed_out = sock_select(&socket, kind, timeout) - .map_err(|e| vm.new_os_error(format!("select failed: {e}")))?; + .map_err(|e| vm.new_simple_os_error(format!("select failed: {e}")))?; Ok(timed_out) } @@ -3483,7 +3483,7 @@ mod _ssl { use std::io::Write; writer .write_all(chunk) - .map_err(|e| vm.new_os_error(format!("Write failed: {e}")))?; + .map_err(|e| vm.new_simple_os_error(format!("Write failed: {e}")))?; } written = chunk_end; @@ -3497,14 +3497,16 @@ mod _ssl { while conn.wants_write() { let mut buf = Vec::new(); conn.write_tls(&mut buf).map_err(|e| { - vm.new_os_error(format!("TLS write failed: {e}")) + vm.new_simple_os_error(format!("TLS write failed: {e}")) })?; if !buf.is_empty() { let timed_out = self.sock_wait_for_io_impl(SelectKind::Write, vm)?; if timed_out { - return Err(vm.new_os_error("Write operation timed out")); + return Err( + vm.new_simple_os_error("Write operation timed out") + ); } match self.sock_send(buf, vm) { @@ -3923,7 +3925,7 @@ mod _ssl { let mut buf = vec![0u8; SSL3_RT_MAX_PLAIN_LENGTH]; let written = conn .write_tls(&mut buf.as_mut_slice()) - .map_err(|e| vm.new_os_error(format!("TLS write failed: {e}")))?; + .map_err(|e| vm.new_simple_os_error(format!("TLS write failed: {e}")))?; if written == 0 { break; @@ -3976,7 +3978,7 @@ mod _ssl { // Process any remaining packets and check peer_has_closed let io_state = conn .process_new_packets() - .map_err(|e| vm.new_os_error(format!("Failed to process packets: {e}")))?; + .map_err(|e| vm.new_simple_os_error(format!("Failed to process packets: {e}")))?; Ok(io_state.peer_has_closed()) } @@ -4422,7 +4424,7 @@ mod _ssl { let rng = SystemRandom::new(); let mut buf = vec![0u8; n_usize]; rng.fill(&mut buf) - .map_err(|_| vm.new_os_error("Failed to generate random bytes"))?; + .map_err(|_| vm.new_simple_os_error("Failed to generate random bytes"))?; Ok(PyBytesRef::from(vm.ctx.new_bytes(buf))) } @@ -4441,7 +4443,7 @@ mod _ssl { fn _test_decode_cert(path: PyStrRef, vm: &VirtualMachine) -> PyResult { // Read certificate file let cert_data = std::fs::read(path.as_str()).map_err(|e| { - vm.new_os_error(format!( + vm.new_simple_os_error(format!( "Failed to read certificate file {}: {}", path.as_str(), e diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index f146eecd40..4c46faaf44 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1475,43 +1475,32 @@ pub(super) mod types { }) } - #[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 args_vec = args.args.to_vec(); + let len = args_vec.len(); + if (2..=5).contains(&len) { + let errno = &args_vec[0]; + if let Some(error) = errno + .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.to_owned(), args_vec).ok()) + { + 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, vm: &VirtualMachine) -> Option { - let len = args.len(); - if (2..=5).contains(&len) { - let errno = &args[0]; - errno - .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.to_owned(), args.to_vec()).ok()) - } else { - None - } - } #[pyslot] #[pymethod(name = "__init__")] pub fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 5d4a28bf18..eca5a7c4ac 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -488,9 +488,9 @@ mod builtins { ReadlineResult::Interrupt => { Err(vm.new_exception_empty(vm.ctx.exceptions.keyboard_interrupt.to_owned())) } - ReadlineResult::Io(e) => Err(vm.new_os_error(e.to_string())), + ReadlineResult::Io(e) => Err(vm.new_simple_os_error(e.to_string())), #[cfg(unix)] - ReadlineResult::OsError(num) => Err(vm.new_os_error(num.to_string())), + ReadlineResult::OsError(num) => Err(vm.new_simple_os_error(num.to_string())), ReadlineResult::Other(e) => Err(vm.new_runtime_error(e.to_string())), } } else { diff --git a/crates/vm/src/stdlib/ctypes.rs b/crates/vm/src/stdlib/ctypes.rs index 2daaa6f3ab..5bc8051393 100644 --- a/crates/vm/src/stdlib/ctypes.rs +++ b/crates/vm/src/stdlib/ctypes.rs @@ -526,14 +526,14 @@ pub(crate) mod _ctypes { let mut cache_write = cache.write(); let (id, _) = cache_write .get_or_insert_lib(&name, vm) - .map_err(|e| vm.new_os_error(e.to_string()))?; + .map_err(|e| vm.new_simple_os_error(e.to_string()))?; Ok(id) } None => { // If None, call libc::dlopen(null, mode) to get the current process handle let handle = unsafe { libc::dlopen(std::ptr::null(), libc::RTLD_NOW) }; if handle.is_null() { - return Err(vm.new_os_error("dlopen() error")); + return Err(vm.new_simple_os_error("dlopen() error")); } Ok(handle as usize) } diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 7dc0cd4ac9..08cda5d692 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -38,7 +38,8 @@ impl ToPyException for std::io::Error { .set_attr("winerror", vm.new_pyobj(winerror), vm) .unwrap(); } - exc + // FIXME: + unsafe { std::mem::transmute(exc) } } } @@ -219,7 +220,7 @@ mod _io { } #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] { - vm.new_os_error(err.to_string()) + vm.new_simple_os_error(err.to_string()) } } @@ -879,9 +880,9 @@ mod _io { let ret = vm.call_method(self.check_init(vm)?, "seek", (pos, whence))?; let offset = get_offset(ret, vm)?; if offset < 0 { - return Err( - vm.new_os_error(format!("Raw stream returned invalid position {offset}")) - ); + return Err(vm.new_simple_os_error(format!( + "Raw stream returned invalid position {offset}" + ))); } self.abs_pos = offset; Ok(offset) @@ -925,9 +926,9 @@ mod _io { let ret = vm.call_method(raw, "tell", ())?; let offset = get_offset(ret, vm)?; if offset < 0 { - return Err( - vm.new_os_error(format!("Raw stream returned invalid position {offset}")) - ); + return Err(vm.new_simple_os_error(format!( + "Raw stream returned invalid position {offset}" + ))); } self.abs_pos = offset; Ok(offset) @@ -978,7 +979,7 @@ mod _io { } let n = isize::try_from_object(vm, res)?; if n < 0 || n as usize > len { - return Err(vm.new_os_error(format!( + return Err(vm.new_simple_os_error(format!( "raw write() returned invalid length {n} (should have been between 0 and {len})" ))); } @@ -1209,7 +1210,7 @@ mod _io { } let n = isize::try_from_object(vm, res)?; if n < 0 || n as usize > len { - return Err(vm.new_os_error(format!( + return Err(vm.new_simple_os_error(format!( "raw readinto() returned invalid length {n} (should have been between 0 and {len})" ))); } @@ -2698,7 +2699,7 @@ mod _io { .is_code_point_boundary(cookie.bytes_to_skip as usize); textio.set_decoded_chars(Some(decoded)); if !pos_is_valid { - return Err(vm.new_os_error("can't restore logical file position")); + return Err(vm.new_simple_os_error("can't restore logical file position")); } textio.decoded_chars_used = cookie.num_to_skip(); } else { @@ -2721,7 +2722,7 @@ mod _io { )); } if !textio.telling { - return Err(vm.new_os_error("telling position disabled by next() call")); + return Err(vm.new_simple_os_error("telling position disabled by next() call")); } textio.write_pending(vm)?; drop(textio); @@ -2810,7 +2811,9 @@ mod _io { let final_decoded_chars = n_decoded.chars + decoded.char_len(); cookie.need_eof = true; if final_decoded_chars < num_to_skip.chars { - return Err(vm.new_os_error("can't reconstruct logical file position")); + return Err( + vm.new_simple_os_error("can't reconstruct logical file position") + ); } } } diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index ada939b154..1d9f6c9fae 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -1233,7 +1233,7 @@ pub(crate) mod module { let username = std::ffi::OsString::from_wide(&buffer[..(size - 1) as usize]); Ok(username.to_str().unwrap().to_string()) } else { - Err(vm.new_os_error(format!("Error code: {success}"))) + Err(vm.new_simple_os_error(format!("Error code: {success}"))) } } diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index b75f601c8d..160184c71c 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -525,13 +525,15 @@ pub(super) mod _os { return Err(vm.new_value_error("embedded null byte")); } if key.is_empty() || key.contains(&b'=') { - return Err(vm.new_errno_error( + let x = vm.new_errno_error( 22, format!( "Invalid argument: {}", std::str::from_utf8(key).unwrap_or("") ), - )); + ); + + return Err(unsafe { std::mem::transmute(x) }); } let key = super::bytes_as_os_str(key, vm)?; // SAFETY: requirements forwarded from the caller @@ -1473,7 +1475,7 @@ pub(super) mod _os { // XXX: The signedness of `clock_t` varies from platform to platform. if c == (-1i8) as libc::clock_t { - return Err(vm.new_os_error("Fail to get times".to_string())); + return Err(vm.new_simple_os_error("Fail to get times".to_string())); } let times_result = TimesResultData { @@ -1586,7 +1588,7 @@ pub(super) mod _os { // after this function ends. unsafe { if libc::getloadavg(&mut loadavg[0] as *mut f64, 3) != 3 { - return Err(vm.new_os_error("Load averages are unobtainable".to_string())); + return Err(vm.new_simple_os_error("Load averages are unobtainable".to_string())); } } diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 680e9914a0..823e5e6406 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -507,7 +507,7 @@ pub mod module { } else if uid == -1 { None } else { - return Err(vm.new_os_error("Specified uid is not valid.")); + return Err(vm.new_simple_os_error("Specified uid is not valid.")); }; let gid = if gid >= 0 { @@ -515,7 +515,7 @@ pub mod module { } else if gid == -1 { None } else { - return Err(vm.new_os_error("Specified gid is not valid.")); + return Err(vm.new_simple_os_error("Specified gid is not valid.")); }; let flag = if follow_symlinks.0 { @@ -1841,7 +1841,7 @@ pub mod module { // function or to `cuserid()`. See man getlogin(3) for more information. let ptr = unsafe { libc::getlogin() }; if ptr.is_null() { - return Err(vm.new_os_error("unable to determine login name")); + return Err(vm.new_simple_os_error("unable to determine login name")); } let slice = unsafe { CStr::from_ptr(ptr) }; slice @@ -2456,7 +2456,7 @@ pub mod module { #[pyfunction] fn getrandom(size: isize, flags: OptionalArg, vm: &VirtualMachine) -> PyResult> { let size = usize::try_from(size) - .map_err(|_| vm.new_os_error(format!("Invalid argument for size: {size}")))?; + .map_err(|_| vm.new_simple_os_error(format!("Invalid argument for size: {size}")))?; let mut buf = Vec::with_capacity(size); unsafe { let len = sys_getrandom( diff --git a/crates/vm/src/stdlib/posix_compat.rs b/crates/vm/src/stdlib/posix_compat.rs index b2149b4319..c1173e0caa 100644 --- a/crates/vm/src/stdlib/posix_compat.rs +++ b/crates/vm/src/stdlib/posix_compat.rs @@ -57,7 +57,7 @@ pub(crate) mod module { #[allow(dead_code)] fn os_unimpl(func: &str, vm: &VirtualMachine) -> PyResult { - Err(vm.new_os_error(format!("{} is not supported on this platform", func))) + Err(vm.new_simple_os_error(format!("{} is not supported on this platform", func))) } pub(crate) fn support_funcs() -> Vec { diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index 5c3d8825f7..668703d6a7 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -176,7 +176,7 @@ pub(crate) mod _signal { let old = unsafe { libc::signal(signalnum, sig_handler) }; if old == SIG_ERR { - return Err(vm.new_os_error("Failed to set signal".to_owned())); + return Err(vm.new_simple_os_error("Failed to set signal".to_owned())); } #[cfg(all(unix, not(target_os = "redox")))] unsafe { diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 45b1d56605..08929dbbc8 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -320,9 +320,9 @@ mod sys { let mut source = String::new(); handle .read_to_string(&mut source) - .map_err(|e| vm.new_os_error(format!("Error reading from stdin: {e}")))?; + .map_err(|e| vm.new_simple_os_error(format!("Error reading from stdin: {e}")))?; vm.compile(&source, crate::compiler::Mode::Single, "".to_owned()) - .map_err(|e| vm.new_os_error(format!("Error running stdin: {e}")))?; + .map_err(|e| vm.new_simple_os_error(format!("Error running stdin: {e}")))?; Ok(()) } @@ -633,7 +633,7 @@ mod sys { }; if result == 0 { - return Err(vm.new_os_error("failed to get windows version".to_owned())); + return Err(vm.new_simple_os_error("failed to get windows version".to_owned())); } let service_pack = { @@ -646,9 +646,10 @@ mod sys { .unwrap_or((0, &0)); let sp = OsString::from_wide(&version.szCSDVersion[..last]); sp.into_string() - .map_err(|_| vm.new_os_error("service pack is not ASCII".to_owned()))? + .map_err(|_| vm.new_simple_os_error("service pack is not ASCII".to_owned()))? }; - let real_version = get_kernel32_version().map_err(|e| vm.new_os_error(e.to_string()))?; + let real_version = + get_kernel32_version().map_err(|e| vm.new_simple_os_error(e.to_string()))?; let winver = WindowsVersionData { major: real_version.0, minor: real_version.1, diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index 1c6113bad7..e887fcae1f 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -422,7 +422,7 @@ mod decl { let t: libc::tms = unsafe { let mut t = std::mem::MaybeUninit::uninit(); if libc::times(t.as_mut_ptr()) == -1 { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); + return Err(vm.new_simple_os_error("Failed to get clock time".to_owned())); } t.assume_init() }; @@ -439,7 +439,7 @@ mod decl { let time: libc::timespec = unsafe { let mut time = std::mem::MaybeUninit::uninit(); if libc::clock_gettime(libc::CLOCK_PROCESS_CPUTIME_ID, time.as_mut_ptr()) == -1 { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); + return Err(vm.new_simple_os_error("Failed to get clock time".to_owned())); } time.assume_init() }; @@ -933,7 +933,7 @@ mod platform { user_time.as_mut_ptr(), ) == 0 { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); + return Err(vm.new_simple_os_error("Failed to get clock time".to_owned())); } (kernel_time.assume_init(), user_time.assume_init()) }; @@ -958,7 +958,7 @@ mod platform { user_time.as_mut_ptr(), ) == 0 { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); + return Err(vm.new_simple_os_error("Failed to get clock time".to_owned())); } (kernel_time.assume_init(), user_time.assume_init()) }; diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index a761902586..b2e54f8b47 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -201,7 +201,7 @@ mod winreg { if res == 0 { Ok(()) } else { - Err(vm.new_os_error(format!("RegCloseKey failed with error code: {res}"))) + Err(vm.new_simple_os_error(format!("RegCloseKey failed with error code: {res}"))) } } @@ -316,7 +316,7 @@ mod winreg { if res == 0 { Ok(PyHkey::new(ret_key)) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } else { let mut ret_key = std::ptr::null_mut(); @@ -326,7 +326,7 @@ mod winreg { if res == 0 { Ok(PyHkey::new(ret_key)) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } } @@ -341,7 +341,7 @@ mod winreg { if res == 0 { Ok(PyHkey::new(out_key)) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } @@ -381,7 +381,7 @@ mod winreg { hkey: AtomicHKEY::new(res), }) } else { - Err(vm.new_os_error(format!("error code: {err}"))) + Err(vm.new_simple_os_error(format!("error code: {err}"))) } } @@ -397,7 +397,7 @@ mod winreg { if res == 0 { Ok(()) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } @@ -409,7 +409,7 @@ mod winreg { if res == 0 { Ok(()) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } @@ -439,7 +439,7 @@ mod winreg { if res == 0 { Ok(()) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } @@ -466,7 +466,7 @@ mod winreg { ) }; if res != 0 { - return Err(vm.new_os_error(format!("error code: {res}"))); + return Err(vm.new_simple_os_error(format!("error code: {res}"))); } String::from_utf16(&tmpbuf[..len as usize]) .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}"))) @@ -495,7 +495,9 @@ mod winreg { ) }; if rc != 0 { - return Err(vm.new_os_error(format!("RegQueryInfoKeyW failed with error code {rc}"))); + return Err( + vm.new_simple_os_error(format!("RegQueryInfoKeyW failed with error code {rc}")) + ); } // Include room for null terminators. @@ -537,7 +539,9 @@ mod winreg { continue; } if rc != 0 { - return Err(vm.new_os_error(format!("RegEnumValueW failed with error code {rc}"))); + return Err( + vm.new_simple_os_error(format!("RegEnumValueW failed with error code {rc}")) + ); } // Convert the registry value name from UTF‑16. @@ -570,7 +574,7 @@ mod winreg { if res == 0 { Ok(()) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } @@ -588,7 +592,7 @@ mod winreg { if res == 0 { Ok(()) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } @@ -653,7 +657,7 @@ mod winreg { }; if err != 0 { - return Err(vm.new_os_error(format!("error code: {err}"))); + return Err(vm.new_simple_os_error(format!("error code: {err}"))); } let l: u64 = (lpftlastwritetime.dwHighDateTime as u64) << 32 | lpftlastwritetime.dwLowDateTime as u64; @@ -837,7 +841,7 @@ mod winreg { if res == 0 { Ok(()) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } @@ -1101,7 +1105,7 @@ mod winreg { ) }; if res != 0 { - return Err(vm.new_os_error(format!("error code: {res}"))); + return Err(vm.new_simple_os_error(format!("error code: {res}"))); } } Ok(None) => { @@ -1119,7 +1123,7 @@ mod winreg { ) }; if res != 0 { - return Err(vm.new_os_error(format!("error code: {res}"))); + return Err(vm.new_simple_os_error(format!("error code: {res}"))); } } Err(e) => return Err(e), @@ -1133,7 +1137,7 @@ mod winreg { if res == 0 { Ok(()) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } @@ -1143,7 +1147,7 @@ mod winreg { if res == 0 { Ok(()) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } @@ -1154,7 +1158,7 @@ mod winreg { if res == 0 { Ok(result != 0) } else { - Err(vm.new_os_error(format!("error code: {res}"))) + Err(vm.new_simple_os_error(format!("error code: {res}"))) } } @@ -1171,7 +1175,7 @@ mod winreg { ) }; if required_size == 0 { - return Err(vm.new_os_error("ExpandEnvironmentStringsW failed".to_string())); + return Err(vm.new_simple_os_error("ExpandEnvironmentStringsW failed".to_string())); } // Allocate buffer with exact size and expand @@ -1184,7 +1188,7 @@ mod winreg { ) }; if r == 0 { - return Err(vm.new_os_error("ExpandEnvironmentStringsW failed".to_string())); + return Err(vm.new_simple_os_error("ExpandEnvironmentStringsW failed".to_string())); } let len = out.iter().position(|&c| c == 0).unwrap_or(out.len()); diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 1054ba9b31..df00357416 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -1,15 +1,16 @@ use crate::{ - AsObject, Py, PyObject, PyObjectRef, PyRef, + AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, builtins::{ - PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyStrRef, PyType, - PyTypeRef, + PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyOSError, PyStrRef, + PyType, PyTypeRef, builtin_func::PyNativeFunction, descriptor::PyMethodDescriptor, tuple::{IntoPyTuple, PyTupleRef}, }, convert::{ToPyException, ToPyObject}, - function::{IntoPyNativeFn, PyMethodFlags}, + function::{FuncArgs, IntoPyNativeFn, KwArgs, PyMethodFlags}, scope::Scope, + types::Constructor, vm::VirtualMachine, }; use rustpython_compiler_core::SourceLocation; @@ -92,18 +93,30 @@ impl VirtualMachine { /// [`vm.invoke_exception()`][Self::invoke_exception] or /// [`exceptions::ExceptionCtor`][crate::exceptions::ExceptionCtor] instead. pub fn new_exception(&self, exc_type: PyTypeRef, args: Vec) -> PyBaseExceptionRef { + debug_assert_eq!( + exc_type.slots.basicsize, + std::mem::size_of::() + ); // TODO: add repr of args into logging? PyRef::new_ref( - // TODO: this constructor might be invalid, because multiple - // exception (even builtin ones) are using custom constructors, - // see `OSError` as an example: PyBaseException::new(args, self), exc_type, Some(self.ctx.new_dict()), ) } + pub fn new_os_error(&self, exc_type: PyTypeRef, args: Vec) -> PyRef { + debug_assert_eq!(exc_type.slots.basicsize, std::mem::size_of::()); + + let func_args = FuncArgs::new(args, KwArgs::::default()); + let payload = + PyOSError::py_new(&exc_type, func_args, self).expect("new_os_error usage error"); + payload + .into_ref_with_type(self, exc_type) + .expect("new_os_error type error") + } + /// Instantiate an exception with no arguments. /// This function should only be used with builtin exception types; if a user-defined exception /// type is passed in, it may not be fully initialized; try using @@ -220,13 +233,13 @@ impl VirtualMachine { err.to_pyexception(self) } - pub fn new_errno_error(&self, errno: i32, msg: impl Into) -> PyBaseExceptionRef { + pub fn new_errno_error(&self, errno: i32, msg: impl Into) -> PyRef { let vm = self; let exc_type = crate::exceptions::errno_to_exc_type(errno, vm).unwrap_or(vm.ctx.exceptions.os_error); let errno_obj = vm.new_pyobj(errno); - vm.new_exception( + vm.new_os_error( exc_type.to_owned(), vec![errno_obj, vm.new_pyobj(msg.into())], ) @@ -565,7 +578,7 @@ impl VirtualMachine { define_exception_fn!(fn new_eof_error, eof_error, EOFError); define_exception_fn!(fn new_attribute_error, attribute_error, AttributeError); define_exception_fn!(fn new_type_error, type_error, TypeError); - define_exception_fn!(fn new_os_error, os_error, OSError); + define_exception_fn!(fn new_simple_os_error, os_error, OSError); define_exception_fn!(fn new_system_error, system_error, SystemError); // TODO: remove & replace with new_unicode_decode_error_real From e61dce47994bbcfff2c64e881e155d9fe40b9f5a Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 10 Dec 2025 11:21:58 +0900 Subject: [PATCH 11/14] fix --- crates/stdlib/src/ssl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 1735226082..3f24737ca3 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -37,7 +37,7 @@ mod _ssl { vm::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyType, PyTypeRef}, + builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyType}, convert::IntoPyException, function::{ArgBytesLike, ArgMemoryBuffer, FuncArgs, OptionalArg, PyComparisonValue}, stdlib::warnings, From 7af9f88701580b7d57d6be8e46f5924fc99ee213 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 10 Dec 2025 11:19:58 +0900 Subject: [PATCH 12/14] new slot heap new --- crates/vm/src/vm/context.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index 5136b1cd95..ae0a844034 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -507,14 +507,15 @@ impl Context { attrs.insert(identifier!(self, __module__), self.new_str(module).into()); let interned_name = self.intern_str(name); + let mut slots = PyBaseException::make_slots(); + slots.name = interned_name.as_str(); + slots.basicsize = 0; // Inherit from base class + slots.new.store(None); // Inherit __new__ from base class via MRO lookup PyType::new_heap( name, bases, attrs, - PyTypeSlots { - name: interned_name.as_str(), - ..PyBaseException::make_slots() - }, + slots, self.types.type_type.to_owned(), self, ) From 84f4c3708d5e437557e8a7c6aa2fdcfedfaf4ba7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 10 Dec 2025 11:21:28 +0900 Subject: [PATCH 13/14] ctypes --- crates/stdlib/src/mmap.rs | 2 +- crates/stdlib/src/ssl.rs | 10 +++++----- crates/vm/src/stdlib/codecs.rs | 20 ++++++++++---------- crates/vm/src/stdlib/ctypes/field.rs | 1 + crates/vm/src/stdlib/nt.rs | 2 +- crates/vm/src/vm/vm_new.rs | 8 ++------ extra_tests/snippets/stdlib_ctypes.py | 2 ++ 7 files changed, 22 insertions(+), 23 deletions(-) diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index 5309917a99..3f79012b32 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -510,7 +510,7 @@ mod mmap { let handle = unsafe { suppress_iph!(libc::get_osfhandle(fileno)) }; // Check for invalid handle value (-1 on Windows) if handle == -1 || handle == INVALID_HANDLE_VALUE as isize { - return Err(vm.new_os_error(format!("Invalid file descriptor: {}", fileno))); + return Err(vm.new_simple_os_error(format!("Invalid file descriptor: {}", fileno))); } Some(handle as HANDLE) } else { diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 3f24737ca3..be55eaf3b9 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -37,7 +37,7 @@ mod _ssl { vm::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyType}, + builtins::{PyBaseExceptionRef, PyBytesRef, PyListRef, PyStrRef, PyType, PyTypeRef}, convert::IntoPyException, function::{ArgBytesLike, ArgMemoryBuffer, FuncArgs, OptionalArg, PyComparisonValue}, stdlib::warnings, @@ -1517,8 +1517,8 @@ mod _ssl { } if *self.x509_cert_count.read() == 0 { - return Err(vm.new_os_error( - "Failed to load certificates from Windows store".to_owned(), + return Err(vm.new_simple_os_error( + "Failed to load certificates from Windows store", )); } @@ -4513,7 +4513,7 @@ mod _ssl { // If no stores could be opened, raise OSError if stores.is_empty() { - return Err(vm.new_os_error(format!( + return Err(vm.new_simple_os_error(format!( "failed to open certificate store {:?}", store_name.as_str() ))); @@ -4566,7 +4566,7 @@ mod _ssl { let store = unsafe { CertOpenSystemStoreW(0, store_name_wide.as_ptr()) }; if store.is_null() { - return Err(vm.new_os_error(format!( + return Err(vm.new_simple_os_error(format!( "failed to open certificate store {:?}", store_name.as_str() ))); diff --git a/crates/vm/src/stdlib/codecs.rs b/crates/vm/src/stdlib/codecs.rs index 5f1b721dfb..db44992c22 100644 --- a/crates/vm/src/stdlib/codecs.rs +++ b/crates/vm/src/stdlib/codecs.rs @@ -277,7 +277,7 @@ mod _codecs { if size == 0 { let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("mbcs_encode failed: {}", err))); + return Err(vm.new_simple_os_error(format!("mbcs_encode failed: {}", err))); } let mut buffer = vec![0u8; size as usize]; @@ -302,7 +302,7 @@ mod _codecs { if result == 0 { let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("mbcs_encode failed: {err}"))); + return Err(vm.new_simple_os_error(format!("mbcs_encode failed: {err}"))); } if errors == "strict" && used_default_char != 0 { @@ -374,7 +374,7 @@ mod _codecs { }; if size == 0 { let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("mbcs_decode failed: {}", err))); + return Err(vm.new_simple_os_error(format!("mbcs_decode failed: {}", err))); } let mut buffer = vec![0u16; size as usize]; @@ -390,7 +390,7 @@ mod _codecs { }; if result == 0 { let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("mbcs_decode failed: {}", err))); + return Err(vm.new_simple_os_error(format!("mbcs_decode failed: {}", err))); } buffer.truncate(result as usize); let s = String::from_utf16(&buffer) @@ -412,7 +412,7 @@ mod _codecs { }; if result == 0 { let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("mbcs_decode failed: {}", err))); + return Err(vm.new_simple_os_error(format!("mbcs_decode failed: {}", err))); } buffer.truncate(result as usize); let s = String::from_utf16(&buffer) @@ -479,7 +479,7 @@ mod _codecs { if size == 0 { let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("oem_encode failed: {}", err))); + return Err(vm.new_simple_os_error(format!("oem_encode failed: {}", err))); } let mut buffer = vec![0u8; size as usize]; @@ -504,7 +504,7 @@ mod _codecs { if result == 0 { let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("oem_encode failed: {err}"))); + return Err(vm.new_simple_os_error(format!("oem_encode failed: {err}"))); } if errors == "strict" && used_default_char != 0 { @@ -576,7 +576,7 @@ mod _codecs { }; if size == 0 { let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("oem_decode failed: {}", err))); + return Err(vm.new_simple_os_error(format!("oem_decode failed: {}", err))); } let mut buffer = vec![0u16; size as usize]; @@ -592,7 +592,7 @@ mod _codecs { }; if result == 0 { let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("oem_decode failed: {}", err))); + return Err(vm.new_simple_os_error(format!("oem_decode failed: {}", err))); } buffer.truncate(result as usize); let s = String::from_utf16(&buffer) @@ -614,7 +614,7 @@ mod _codecs { }; if result == 0 { let err = std::io::Error::last_os_error(); - return Err(vm.new_os_error(format!("oem_decode failed: {}", err))); + return Err(vm.new_simple_os_error(format!("oem_decode failed: {}", err))); } buffer.truncate(result as usize); let s = String::from_utf16(&buffer) diff --git a/crates/vm/src/stdlib/ctypes/field.rs b/crates/vm/src/stdlib/ctypes/field.rs index 7205c430b5..9401767823 100644 --- a/crates/vm/src/stdlib/ctypes/field.rs +++ b/crates/vm/src/stdlib/ctypes/field.rs @@ -10,6 +10,7 @@ use super::union::PyCUnion; #[pyclass(name = "PyCFieldType", base = PyType, module = "_ctypes")] #[derive(Debug)] pub struct PyCFieldType { + pub base: PyType, #[allow(dead_code)] pub(super) inner: PyCField, } diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 1d9f6c9fae..956e472327 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -214,7 +214,7 @@ pub(crate) mod module { let handle = unsafe { FindFirstFileW(wide_path.as_ptr(), &mut find_data) }; if handle == INVALID_HANDLE_VALUE { - return Err(vm.new_os_error(format!( + return Err(vm.new_simple_os_error(format!( "FindFirstFileW failed for path: {}", path.as_ref().display() ))); diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index df00357416..e1a30b3cd2 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -109,12 +109,8 @@ impl VirtualMachine { pub fn new_os_error(&self, exc_type: PyTypeRef, args: Vec) -> PyRef { debug_assert_eq!(exc_type.slots.basicsize, std::mem::size_of::()); - let func_args = FuncArgs::new(args, KwArgs::::default()); - let payload = - PyOSError::py_new(&exc_type, func_args, self).expect("new_os_error usage error"); - payload - .into_ref_with_type(self, exc_type) - .expect("new_os_error type error") + let payload = PyOSError::py_new(&exc_type, args.into(), self).expect("new_os_error usage error"); + payload.into_ref_with_type(self, exc_type).expect("new_os_error usage error") } /// Instantiate an exception with no arguments. diff --git a/extra_tests/snippets/stdlib_ctypes.py b/extra_tests/snippets/stdlib_ctypes.py index b4bc05dcb4..d735d24165 100644 --- a/extra_tests/snippets/stdlib_ctypes.py +++ b/extra_tests/snippets/stdlib_ctypes.py @@ -395,3 +395,5 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str: return buf.value # print(get_win_folder_via_ctypes("CSIDL_DOWNLOADS")) + +print('done') From 8fdbe4bfd347e495d9fa00a187b87e5fca787b98 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 12 Dec 2025 07:34:28 +0000 Subject: [PATCH 14/14] Auto-format: cargo fmt --all --- crates/stdlib/src/mmap.rs | 4 +++- crates/stdlib/src/ssl.rs | 6 +++--- crates/vm/src/vm/vm_new.rs | 7 +++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index 3f79012b32..841ef552ae 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -510,7 +510,9 @@ mod mmap { let handle = unsafe { suppress_iph!(libc::get_osfhandle(fileno)) }; // Check for invalid handle value (-1 on Windows) if handle == -1 || handle == INVALID_HANDLE_VALUE as isize { - return Err(vm.new_simple_os_error(format!("Invalid file descriptor: {}", fileno))); + return Err( + vm.new_simple_os_error(format!("Invalid file descriptor: {}", fileno)) + ); } Some(handle as HANDLE) } else { diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index be55eaf3b9..9a42347cf8 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -1517,9 +1517,9 @@ mod _ssl { } if *self.x509_cert_count.read() == 0 { - return Err(vm.new_simple_os_error( - "Failed to load certificates from Windows store", - )); + return Err( + vm.new_simple_os_error("Failed to load certificates from Windows store") + ); } Ok(()) diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index e1a30b3cd2..4cccdb2d85 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -109,8 +109,11 @@ impl VirtualMachine { pub fn new_os_error(&self, exc_type: PyTypeRef, args: Vec) -> PyRef { debug_assert_eq!(exc_type.slots.basicsize, std::mem::size_of::()); - let payload = PyOSError::py_new(&exc_type, args.into(), self).expect("new_os_error usage error"); - payload.into_ref_with_type(self, exc_type).expect("new_os_error usage error") + let payload = + PyOSError::py_new(&exc_type, args.into(), self).expect("new_os_error usage error"); + payload + .into_ref_with_type(self, exc_type) + .expect("new_os_error usage error") } /// Instantiate an exception with no arguments.