pyo3/
pyclass_init.rs

1//! Contains initialization utilities for `#[pyclass]`.
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::impl_::callback::IntoPyCallbackOutput;
4use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef};
5use crate::impl_::pyclass_init::{PyNativeTypeInitializer, PyObjectInit};
6use crate::types::PyAnyMethods;
7use crate::{ffi, Bound, Py, PyClass, PyResult, Python};
8use crate::{
9    ffi::PyTypeObject,
10    pycell::impl_::{PyClassBorrowChecker, PyClassMutability, PyClassObjectContents},
11};
12use std::{
13    cell::UnsafeCell,
14    marker::PhantomData,
15    mem::{ManuallyDrop, MaybeUninit},
16};
17
18/// Initializer for our `#[pyclass]` system.
19///
20/// You can use this type to initialize complicatedly nested `#[pyclass]`.
21///
22/// # Examples
23///
24/// ```
25/// # use pyo3::prelude::*;
26/// # use pyo3::py_run;
27/// #[pyclass(subclass)]
28/// struct BaseClass {
29///     #[pyo3(get)]
30///     basename: &'static str,
31/// }
32/// #[pyclass(extends=BaseClass, subclass)]
33/// struct SubClass {
34///     #[pyo3(get)]
35///     subname: &'static str,
36/// }
37/// #[pyclass(extends=SubClass)]
38/// struct SubSubClass {
39///     #[pyo3(get)]
40///     subsubname: &'static str,
41/// }
42///
43/// #[pymethods]
44/// impl SubSubClass {
45///     #[new]
46///     fn new() -> PyClassInitializer<Self> {
47///         PyClassInitializer::from(BaseClass { basename: "base" })
48///             .add_subclass(SubClass { subname: "sub" })
49///             .add_subclass(SubSubClass {
50///                 subsubname: "subsub",
51///             })
52///     }
53/// }
54/// Python::with_gil(|py| {
55///     let typeobj = py.get_type::<SubSubClass>();
56///     let sub_sub_class = typeobj.call((), None).unwrap();
57///     py_run!(
58///         py,
59///         sub_sub_class,
60///         r#"
61///  assert sub_sub_class.basename == 'base'
62///  assert sub_sub_class.subname == 'sub'
63///  assert sub_sub_class.subsubname == 'subsub'"#
64///     );
65/// });
66/// ```
67pub struct PyClassInitializer<T: PyClass>(PyClassInitializerImpl<T>);
68
69enum PyClassInitializerImpl<T: PyClass> {
70    Existing(Py<T>),
71    New {
72        init: T,
73        super_init: <T::BaseType as PyClassBaseType>::Initializer,
74    },
75}
76
77impl<T: PyClass> PyClassInitializer<T> {
78    /// Constructs a new initializer from value `T` and base class' initializer.
79    ///
80    /// It is recommended to use `add_subclass` instead of this method for most usage.
81    #[track_caller]
82    #[inline]
83    pub fn new(init: T, super_init: <T::BaseType as PyClassBaseType>::Initializer) -> Self {
84        // This is unsound; see https://github.com/PyO3/pyo3/issues/4452.
85        assert!(
86            super_init.can_be_subclassed(),
87            "you cannot add a subclass to an existing value",
88        );
89        Self(PyClassInitializerImpl::New { init, super_init })
90    }
91
92    /// Constructs a new initializer from an initializer for the base class.
93    ///
94    /// # Examples
95    /// ```
96    /// use pyo3::prelude::*;
97    ///
98    /// #[pyclass(subclass)]
99    /// struct BaseClass {
100    ///     #[pyo3(get)]
101    ///     value: i32,
102    /// }
103    ///
104    /// impl BaseClass {
105    ///     fn new(value: i32) -> PyResult<Self> {
106    ///         Ok(Self { value })
107    ///     }
108    /// }
109    ///
110    /// #[pyclass(extends=BaseClass)]
111    /// struct SubClass {}
112    ///
113    /// #[pymethods]
114    /// impl SubClass {
115    ///     #[new]
116    ///     fn new(value: i32) -> PyResult<PyClassInitializer<Self>> {
117    ///         let base_init = PyClassInitializer::from(BaseClass::new(value)?);
118    ///         Ok(base_init.add_subclass(SubClass {}))
119    ///     }
120    /// }
121    ///
122    /// fn main() -> PyResult<()> {
123    ///     Python::with_gil(|py| {
124    ///         let m = PyModule::new(py, "example")?;
125    ///         m.add_class::<SubClass>()?;
126    ///         m.add_class::<BaseClass>()?;
127    ///
128    ///         let instance = m.getattr("SubClass")?.call1((92,))?;
129    ///
130    ///         // `SubClass` does not have a `value` attribute, but `BaseClass` does.
131    ///         let n = instance.getattr("value")?.extract::<i32>()?;
132    ///         assert_eq!(n, 92);
133    ///
134    ///         Ok(())
135    ///     })
136    /// }
137    /// ```
138    #[track_caller]
139    #[inline]
140    pub fn add_subclass<S>(self, subclass_value: S) -> PyClassInitializer<S>
141    where
142        S: PyClass<BaseType = T>,
143        S::BaseType: PyClassBaseType<Initializer = Self>,
144    {
145        PyClassInitializer::new(subclass_value, self)
146    }
147
148    /// Creates a new PyCell and initializes it.
149    pub(crate) fn create_class_object(self, py: Python<'_>) -> PyResult<Bound<'_, T>>
150    where
151        T: PyClass,
152    {
153        unsafe { self.create_class_object_of_type(py, T::type_object_raw(py)) }
154    }
155
156    /// Creates a new class object and initializes it given a typeobject `subtype`.
157    ///
158    /// # Safety
159    /// `subtype` must be a valid pointer to the type object of T or a subclass.
160    pub(crate) unsafe fn create_class_object_of_type(
161        self,
162        py: Python<'_>,
163        target_type: *mut crate::ffi::PyTypeObject,
164    ) -> PyResult<Bound<'_, T>>
165    where
166        T: PyClass,
167    {
168        /// Layout of a PyClassObject after base new has been called, but the contents have not yet been
169        /// written.
170        #[repr(C)]
171        struct PartiallyInitializedClassObject<T: PyClass> {
172            _ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
173            contents: MaybeUninit<PyClassObjectContents<T>>,
174        }
175
176        let (init, super_init) = match self.0 {
177            PyClassInitializerImpl::Existing(value) => return Ok(value.into_bound(py)),
178            PyClassInitializerImpl::New { init, super_init } => (init, super_init),
179        };
180
181        let obj = super_init.into_new_object(py, target_type)?;
182
183        let part_init: *mut PartiallyInitializedClassObject<T> = obj.cast();
184        std::ptr::write(
185            (*part_init).contents.as_mut_ptr(),
186            PyClassObjectContents {
187                value: ManuallyDrop::new(UnsafeCell::new(init)),
188                borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage::new(),
189                thread_checker: T::ThreadChecker::new(),
190                dict: T::Dict::INIT,
191                weakref: T::WeakRef::INIT,
192            },
193        );
194
195        // Safety: obj is a valid pointer to an object of type `target_type`, which` is a known
196        // subclass of `T`
197        Ok(obj.assume_owned(py).downcast_into_unchecked())
198    }
199}
200
201impl<T: PyClass> PyObjectInit<T> for PyClassInitializer<T> {
202    unsafe fn into_new_object(
203        self,
204        py: Python<'_>,
205        subtype: *mut PyTypeObject,
206    ) -> PyResult<*mut ffi::PyObject> {
207        self.create_class_object_of_type(py, subtype)
208            .map(Bound::into_ptr)
209    }
210
211    #[inline]
212    fn can_be_subclassed(&self) -> bool {
213        !matches!(self.0, PyClassInitializerImpl::Existing(..))
214    }
215}
216
217impl<T> From<T> for PyClassInitializer<T>
218where
219    T: PyClass,
220    T::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<T::BaseType>>,
221{
222    #[inline]
223    fn from(value: T) -> PyClassInitializer<T> {
224        Self::new(value, PyNativeTypeInitializer(PhantomData))
225    }
226}
227
228impl<S, B> From<(S, B)> for PyClassInitializer<S>
229where
230    S: PyClass<BaseType = B>,
231    B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
232    B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
233{
234    #[track_caller]
235    #[inline]
236    fn from(sub_and_base: (S, B)) -> PyClassInitializer<S> {
237        let (sub, base) = sub_and_base;
238        PyClassInitializer::from(base).add_subclass(sub)
239    }
240}
241
242impl<T: PyClass> From<Py<T>> for PyClassInitializer<T> {
243    #[inline]
244    fn from(value: Py<T>) -> PyClassInitializer<T> {
245        PyClassInitializer(PyClassInitializerImpl::Existing(value))
246    }
247}
248
249impl<'py, T: PyClass> From<Bound<'py, T>> for PyClassInitializer<T> {
250    #[inline]
251    fn from(value: Bound<'py, T>) -> PyClassInitializer<T> {
252        PyClassInitializer::from(value.unbind())
253    }
254}
255
256// Implementation used by proc macros to allow anything convertible to PyClassInitializer<T> to be
257// the return value of pyclass #[new] method (optionally wrapped in `Result<U, E>`).
258impl<T, U> IntoPyCallbackOutput<'_, PyClassInitializer<T>> for U
259where
260    T: PyClass,
261    U: Into<PyClassInitializer<T>>,
262{
263    #[inline]
264    fn convert(self, _py: Python<'_>) -> PyResult<PyClassInitializer<T>> {
265        Ok(self.into())
266    }
267}
268
269#[cfg(all(test, feature = "macros"))]
270mod tests {
271    //! See https://github.com/PyO3/pyo3/issues/4452.
272
273    use crate::prelude::*;
274
275    #[pyclass(crate = "crate", subclass)]
276    struct BaseClass {}
277
278    #[pyclass(crate = "crate", extends=BaseClass)]
279    struct SubClass {
280        _data: i32,
281    }
282
283    #[test]
284    #[should_panic]
285    fn add_subclass_to_py_is_unsound() {
286        Python::with_gil(|py| {
287            let base = Py::new(py, BaseClass {}).unwrap();
288            let _subclass = PyClassInitializer::from(base).add_subclass(SubClass { _data: 42 });
289        });
290    }
291}