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 = unsafe { super_init.into_new_object(py, target_type)? };
182
183 let part_init: *mut PartiallyInitializedClassObject<T> = obj.cast();
184 unsafe {
185 std::ptr::write(
186 (*part_init).contents.as_mut_ptr(),
187 PyClassObjectContents {
188 value: ManuallyDrop::new(UnsafeCell::new(init)),
189 borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage::new(),
190 thread_checker: T::ThreadChecker::new(),
191 dict: T::Dict::INIT,
192 weakref: T::WeakRef::INIT,
193 },
194 );
195 }
196
197 // Safety: obj is a valid pointer to an object of type `target_type`, which` is a known
198 // subclass of `T`
199 Ok(unsafe { obj.assume_owned(py).downcast_into_unchecked() })
200 }
201}
202
203impl<T: PyClass> PyObjectInit<T> for PyClassInitializer<T> {
204 unsafe fn into_new_object(
205 self,
206 py: Python<'_>,
207 subtype: *mut PyTypeObject,
208 ) -> PyResult<*mut ffi::PyObject> {
209 unsafe {
210 self.create_class_object_of_type(py, subtype)
211 .map(op:Bound::into_ptr)
212 }
213 }
214
215 #[inline]
216 fn can_be_subclassed(&self) -> bool {
217 !matches!(self.0, PyClassInitializerImpl::Existing(..))
218 }
219}
220
221impl<T> From<T> for PyClassInitializer<T>
222where
223 T: PyClass,
224 T::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<T::BaseType>>,
225{
226 #[inline]
227 fn from(value: T) -> PyClassInitializer<T> {
228 Self::new(init:value, super_init:PyNativeTypeInitializer(PhantomData))
229 }
230}
231
232impl<S, B> From<(S, B)> for PyClassInitializer<S>
233where
234 S: PyClass<BaseType = B>,
235 B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
236 B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
237{
238 #[track_caller]
239 #[inline]
240 fn from(sub_and_base: (S, B)) -> PyClassInitializer<S> {
241 let (sub: S, base: B) = sub_and_base;
242 PyClassInitializer::from(base).add_subclass(subclass_value:sub)
243 }
244}
245
246impl<T: PyClass> From<Py<T>> for PyClassInitializer<T> {
247 #[inline]
248 fn from(value: Py<T>) -> PyClassInitializer<T> {
249 PyClassInitializer(PyClassInitializerImpl::Existing(value))
250 }
251}
252
253impl<'py, T: PyClass> From<Bound<'py, T>> for PyClassInitializer<T> {
254 #[inline]
255 fn from(value: Bound<'py, T>) -> PyClassInitializer<T> {
256 PyClassInitializer::from(value.unbind())
257 }
258}
259
260// Implementation used by proc macros to allow anything convertible to PyClassInitializer<T> to be
261// the return value of pyclass #[new] method (optionally wrapped in `Result<U, E>`).
262impl<T, U> IntoPyCallbackOutput<'_, PyClassInitializer<T>> for U
263where
264 T: PyClass,
265 U: Into<PyClassInitializer<T>>,
266{
267 #[inline]
268 fn convert(self, _py: Python<'_>) -> PyResult<PyClassInitializer<T>> {
269 Ok(self.into())
270 }
271}
272
273#[cfg(all(test, feature = "macros"))]
274mod tests {
275 //! See https://github.com/PyO3/pyo3/issues/4452.
276
277 use crate::prelude::*;
278
279 #[pyclass(crate = "crate", subclass)]
280 struct BaseClass {}
281
282 #[pyclass(crate = "crate", extends=BaseClass)]
283 struct SubClass {
284 _data: i32,
285 }
286
287 #[test]
288 #[should_panic]
289 fn add_subclass_to_py_is_unsound() {
290 Python::with_gil(|py| {
291 let base = Py::new(py, BaseClass {}).unwrap();
292 let _subclass = PyClassInitializer::from(base).add_subclass(SubClass { _data: 42 });
293 });
294 }
295}
296