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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion crates/derive-impl/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,9 @@ fn generate_class_def(
let subclass_impl = if !is_pystruct && is_repr_transparent {
base.as_ref().map(|typ| {
quote! {
impl ::rustpython_vm::class::PySubclass for #ident {
// SAFETY: the transparent base field has the same layout as Self;
// pyclass also checks the object payload offsets and alignment.
unsafe impl ::rustpython_vm::class::PySubclass for #ident {
type Base = #typ;

#[inline]
Expand Down
3 changes: 2 additions & 1 deletion crates/derive-impl/src/pystructseq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,8 @@ pub(crate) fn impl_pystruct_sequence(
}

// PySubclass for proper inheritance
impl ::rustpython_vm::class::PySubclass for #pytype_ident {
// SAFETY: the generated payload is a transparent wrapper around PyTuple.
unsafe impl ::rustpython_vm::class::PySubclass for #pytype_ident {
type Base = ::rustpython_vm::builtins::PyTuple;

#[inline]
Expand Down
37 changes: 31 additions & 6 deletions crates/vm/src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,38 @@ pub trait PyClassImpl: PyClassDef {

/// 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 provides safe reference access to the
/// base type's payload.
/// The `#[pyclass]` macro implements this trait for `#[repr(transparent)]`
/// subclasses with `base = SomeType`. Other native subclasses can implement
/// it manually when their base is a compatible physical prefix.
///
/// For subclasses with `#[repr(transparent)]`
/// which enables ownership transfer via `into_base()`.
pub trait PySubclass: crate::PyPayload {
/// # Safety
///
/// Every valid `Self` must contain a valid `Base` at offset zero, returned by
/// `as_base`. Their payload offsets in `Py<Self>` and `Py<Base>` must match,
/// and `Py<Self>` must meet `Py<Base>`'s alignment requirement. The base must
/// remain valid for the lifetime of `Self`, independently of the object's
/// Python class or MRO. These guarantees allow safe borrowing and ownership
/// transfer of the same allocation as `Py<Base>`.
///
/// A manual implementation must explicitly acknowledge this contract:
///
/// ```compile_fail,E0200
/// use rustpython_vm::{builtins::PyInt, class::PySubclass, pyclass, PyPayload};
///
/// #[pyclass(module = false, name = "IntWrapper")]
/// #[derive(Debug, PyPayload)]
/// #[repr(transparent)]
/// struct IntWrapper(PyInt);
///
/// #[pyclass]
/// impl IntWrapper {}
///
/// impl PySubclass for IntWrapper {
/// type Base = PyInt;
/// fn as_base(&self) -> &PyInt { &self.0 }
/// }
/// ```
pub unsafe trait PySubclass: crate::PyPayload {
type Base: crate::PyPayload;

/// Returns a reference to the base type's payload.
Expand Down
3 changes: 2 additions & 1 deletion crates/vm/src/exception_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ pub(super) mod types {
excs_str: PyAtomicRef<Option<PyObject>>,
}

impl crate::class::PySubclass for PyBaseExceptionGroup {
// SAFETY: pyexception checks the base field offset, payload offset, and alignment.
unsafe impl crate::class::PySubclass for PyBaseExceptionGroup {
type Base = PyBaseException;
fn as_base(&self) -> &Self::Base {
&self.base
Expand Down
24 changes: 16 additions & 8 deletions crates/vm/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1750,7 +1750,8 @@ pub(super) mod types {
code: PyAtomicRef<Option<PyObject>>,
}

impl crate::class::PySubclass for PySystemExit {
// SAFETY: pyexception checks the base field offset, payload offset, and alignment.
unsafe impl crate::class::PySubclass for PySystemExit {
type Base = PyBaseException;
fn as_base(&self) -> &Self::Base {
&self.base
Expand Down Expand Up @@ -1830,7 +1831,8 @@ pub(super) mod types {
value: PyAtomicRef<Option<PyObject>>,
}

impl crate::class::PySubclass for PyStopIteration {
// SAFETY: pyexception checks the base field offset, payload offset, and alignment.
unsafe impl crate::class::PySubclass for PyStopIteration {
type Base = PyException;
fn as_base(&self) -> &Self::Base {
&self.base
Expand Down Expand Up @@ -1925,7 +1927,8 @@ pub(super) mod types {
obj: PyAtomicRef<Option<PyObject>>,
}

impl crate::class::PySubclass for PyAttributeError {
// SAFETY: pyexception checks the base field offset, payload offset, and alignment.
unsafe impl crate::class::PySubclass for PyAttributeError {
type Base = PyException;
fn as_base(&self) -> &Self::Base {
&self.base
Expand Down Expand Up @@ -2051,7 +2054,8 @@ pub(super) mod types {
name_from: PyAtomicRef<Option<PyObject>>,
}

impl crate::class::PySubclass for PyImportError {
// SAFETY: pyexception checks the base field offset, payload offset, and alignment.
unsafe impl crate::class::PySubclass for PyImportError {
type Base = PyException;
fn as_base(&self) -> &Self::Base {
&self.base
Expand Down Expand Up @@ -2263,7 +2267,8 @@ pub(super) mod types {
name: PyAtomicRef<Option<PyObject>>,
}

impl crate::class::PySubclass for PyNameError {
// SAFETY: pyexception checks the base field offset, payload offset, and alignment.
unsafe impl crate::class::PySubclass for PyNameError {
type Base = PyException;
fn as_base(&self) -> &Self::Base {
&self.base
Expand Down Expand Up @@ -2357,7 +2362,8 @@ pub(super) mod types {
written: AtomicCell<isize>,
}

impl crate::class::PySubclass for PyOSError {
// SAFETY: pyexception checks the base field offset, payload offset, and alignment.
unsafe impl crate::class::PySubclass for PyOSError {
type Base = PyException;
fn as_base(&self) -> &Self::Base {
&self.base
Expand Down Expand Up @@ -2927,7 +2933,8 @@ pub(super) mod types {
metadata: PyAtomicRef<Option<PyObject>>,
}

impl crate::class::PySubclass for PySyntaxError {
// SAFETY: pyexception checks the base field offset, payload offset, and alignment.
unsafe impl crate::class::PySubclass for PySyntaxError {
type Base = PyException;
fn as_base(&self) -> &Self::Base {
&self.base
Expand Down Expand Up @@ -3230,7 +3237,8 @@ pub(super) mod types {
reason: PyAtomicRef<Option<PyObject>>,
}

impl crate::class::PySubclass for PyUnicodeError {
// SAFETY: pyexception checks the base field offset, payload offset, and alignment.
unsafe impl crate::class::PySubclass for PyUnicodeError {
type Base = PyValueError;
fn as_base(&self) -> &Self::Base {
&self.base
Expand Down
Loading