use PyO3 types within LazyTypeObject

This commit is contained in:
David Hewitt 2023-02-14 08:21:04 +00:00
parent 00ddd21535
commit c7cc48f8e4
6 changed files with 39 additions and 41 deletions

View file

@ -981,6 +981,7 @@ unsafe impl pyo3::type_object::PyTypeInfo for MyClass {
fn type_object_raw(py: pyo3::Python<'_>) -> *mut pyo3::ffi::PyTypeObject { fn type_object_raw(py: pyo3::Python<'_>) -> *mut pyo3::ffi::PyTypeObject {
<Self as pyo3::impl_::pyclass::PyClassImpl>::lazy_type_object() <Self as pyo3::impl_::pyclass::PyClassImpl>::lazy_type_object()
.get_or_init(py) .get_or_init(py)
.as_type_ptr()
} }
} }

View file

@ -758,6 +758,7 @@ fn impl_pytypeinfo(
<#cls as _pyo3::impl_::pyclass::PyClassImpl>::lazy_type_object() <#cls as _pyo3::impl_::pyclass::PyClassImpl>::lazy_type_object()
.get_or_init(py) .get_or_init(py)
.as_type_ptr()
} }
} }
} }

View file

@ -9,7 +9,8 @@ use parking_lot::{const_mutex, Mutex};
use crate::{ use crate::{
exceptions::PyRuntimeError, ffi, once_cell::GILOnceCell, pyclass::create_type_object, exceptions::PyRuntimeError, ffi, once_cell::GILOnceCell, pyclass::create_type_object,
IntoPyPointer, PyClass, PyErr, PyMethodDefType, PyObject, PyResult, Python, types::PyType, AsPyPointer, IntoPyPointer, Py, PyClass, PyErr, PyMethodDefType, PyObject,
PyResult, Python,
}; };
use super::PyClassItemsIter; use super::PyClassItemsIter;
@ -20,11 +21,11 @@ pub struct LazyTypeObject<T>(LazyTypeObjectInner, PhantomData<T>);
// Non-generic inner of LazyTypeObject to keep code size down // Non-generic inner of LazyTypeObject to keep code size down
struct LazyTypeObjectInner { struct LazyTypeObjectInner {
value: GILOnceCell<*mut ffi::PyTypeObject>, value: GILOnceCell<Py<PyType>>,
// Threads which have begun initialization of the `tp_dict`. Used for // Threads which have begun initialization of the `tp_dict`. Used for
// reentrant initialization detection. // reentrant initialization detection.
initializing_threads: Mutex<Vec<ThreadId>>, initializing_threads: Mutex<Vec<ThreadId>>,
tp_dict_filled: GILOnceCell<PyResult<()>>, tp_dict_filled: GILOnceCell<()>,
} }
impl<T> LazyTypeObject<T> { impl<T> LazyTypeObject<T> {
@ -43,7 +44,7 @@ impl<T> LazyTypeObject<T> {
impl<T: PyClass> LazyTypeObject<T> { impl<T: PyClass> LazyTypeObject<T> {
/// Gets the type object contained by this `LazyTypeObject`, initializing it if needed. /// Gets the type object contained by this `LazyTypeObject`, initializing it if needed.
pub fn get_or_init(&self, py: Python<'_>) -> *mut ffi::PyTypeObject { pub fn get_or_init<'py>(&'py self, py: Python<'py>) -> &'py PyType {
self.get_or_try_init(py).unwrap_or_else(|err| { self.get_or_try_init(py).unwrap_or_else(|err| {
err.print(py); err.print(py);
panic!("failed to create type object for {}", T::NAME) panic!("failed to create type object for {}", T::NAME)
@ -51,31 +52,26 @@ impl<T: PyClass> LazyTypeObject<T> {
} }
/// Fallible version of the above. /// Fallible version of the above.
pub(crate) fn get_or_try_init(&self, py: Python<'_>) -> PyResult<*mut ffi::PyTypeObject> { pub(crate) fn get_or_try_init<'py>(&'py self, py: Python<'py>) -> PyResult<&'py PyType> {
fn inner<T: PyClass>() -> PyResult<*mut ffi::PyTypeObject> {
// Safety: `py` is held by the caller of `get_or_init`.
let py = unsafe { Python::assume_gil_acquired() };
create_type_object::<T>(py)
}
self.0 self.0
.get_or_try_init(py, inner::<T>, T::NAME, T::items_iter()) .get_or_try_init(py, create_type_object::<T>, T::NAME, T::items_iter())
} }
} }
impl LazyTypeObjectInner { impl LazyTypeObjectInner {
fn get_or_try_init( // Uses dynamically dispatched fn(Python<'py>) -> PyResult<Py<PyType>
&self, // so that this code is only instantiated once, instead of for every T
py: Python<'_>, // like the generic LazyTypeObject<T> methods above.
init: fn() -> PyResult<*mut ffi::PyTypeObject>, fn get_or_try_init<'py>(
&'py self,
py: Python<'py>,
init: fn(Python<'py>) -> PyResult<Py<PyType>>,
name: &str, name: &str,
items_iter: PyClassItemsIter, items_iter: PyClassItemsIter,
) -> PyResult<*mut ffi::PyTypeObject> { ) -> PyResult<&'py PyType> {
(|| -> PyResult<_> { (|| -> PyResult<_> {
// Uses explicit GILOnceCell::get_or_init::<fn() -> *mut ffi::PyTypeObject> monomorphization let type_object = self.value.get_or_try_init(py, || init(py))?.as_ref(py);
// so that this code is only monomorphized once, instead of for every T. self.ensure_init(type_object, name, items_iter)?;
let type_object = *self.value.get_or_try_init(py, init)?;
self.ensure_init(py, type_object, name, items_iter)?;
Ok(type_object) Ok(type_object)
})() })()
.map_err(|err| { .map_err(|err| {
@ -89,11 +85,12 @@ impl LazyTypeObjectInner {
fn ensure_init( fn ensure_init(
&self, &self,
py: Python<'_>, type_object: &PyType,
type_object: *mut ffi::PyTypeObject,
name: &str, name: &str,
items_iter: PyClassItemsIter, items_iter: PyClassItemsIter,
) -> PyResult<()> { ) -> PyResult<()> {
let py = type_object.py();
// We might want to fill the `tp_dict` with python instances of `T` // We might want to fill the `tp_dict` with python instances of `T`
// itself. In order to do so, we must first initialize the type object // itself. In order to do so, we must first initialize the type object
// with an empty `tp_dict`: now we can create instances of `T`. // with an empty `tp_dict`: now we can create instances of `T`.
@ -167,8 +164,8 @@ impl LazyTypeObjectInner {
// Now we hold the GIL and we can assume it won't be released until we // Now we hold the GIL and we can assume it won't be released until we
// return from the function. // return from the function.
let result = self.tp_dict_filled.get_or_init(py, move || { let result = self.tp_dict_filled.get_or_try_init(py, move || {
let result = initialize_tp_dict(py, type_object as *mut ffi::PyObject, items); let result = initialize_tp_dict(py, type_object.as_ptr(), items);
// Initialization successfully complete, can clear the thread list. // Initialization successfully complete, can clear the thread list.
// (No further calls to get_or_init() will try to init, on any thread.) // (No further calls to get_or_init() will try to init, on any thread.)

View file

@ -5,7 +5,8 @@ use crate::{
assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc, assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
PyClassItemsIter, PyClassItemsIter,
}, },
PyClass, PyErr, PyMethodDefType, PyResult, PyTypeInfo, Python, types::PyType,
Py, PyClass, PyMethodDefType, PyResult, PyTypeInfo, Python,
}; };
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -15,7 +16,7 @@ use std::{
ptr, ptr,
}; };
pub(crate) fn create_type_object<T>(py: Python<'_>) -> PyResult<*mut ffi::PyTypeObject> pub(crate) fn create_type_object<T>(py: Python<'_>) -> PyResult<Py<PyType>>
where where
T: PyClass, T: PyClass,
{ {
@ -322,7 +323,7 @@ impl PyTypeBuilder {
name: &'static str, name: &'static str,
module_name: Option<&'static str>, module_name: Option<&'static str>,
basicsize: usize, basicsize: usize,
) -> PyResult<*mut ffi::PyTypeObject> { ) -> PyResult<Py<PyType>> {
// `c_ulong` and `c_uint` have the same size // `c_ulong` and `c_uint` have the same size
// on some platforms (like windows) // on some platforms (like windows)
#![allow(clippy::useless_conversion)] #![allow(clippy::useless_conversion)]
@ -370,16 +371,14 @@ impl PyTypeBuilder {
}; };
// Safety: We've correctly setup the PyType_Spec at this point // Safety: We've correctly setup the PyType_Spec at this point
let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) }; let type_object: Py<PyType> =
if type_object.is_null() { unsafe { Py::from_owned_ptr_or_err(py, ffi::PyType_FromSpec(&mut spec))? };
Err(PyErr::fetch(py))
} else {
for cleanup in std::mem::take(&mut self.cleanup) { for cleanup in std::mem::take(&mut self.cleanup) {
cleanup(&self, type_object as _); cleanup(&self, type_object.as_ref(py).as_type_ptr());
} }
Ok(type_object as _) Ok(type_object)
}
} }
} }

View file

@ -7,7 +7,7 @@ use crate::err::{PyErr, PyResult};
use crate::exceptions; use crate::exceptions;
use crate::ffi; use crate::ffi;
use crate::pyclass::PyClass; use crate::pyclass::PyClass;
use crate::types::{PyAny, PyCFunction, PyDict, PyList, PyString, PyType}; use crate::types::{PyAny, PyCFunction, PyDict, PyList, PyString};
use crate::{AsPyPointer, IntoPy, Py, PyObject, Python}; use crate::{AsPyPointer, IntoPy, Py, PyObject, Python};
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::str; use std::str;
@ -295,9 +295,7 @@ impl PyModule {
T: PyClass, T: PyClass,
{ {
let py = self.py(); let py = self.py();
self.add(T::NAME, unsafe { self.add(T::NAME, T::lazy_type_object().get_or_try_init(py)?)
PyType::from_type_ptr(py, T::lazy_type_object().get_or_try_init(py)?)
})
} }
/// Adds a function or a (sub)module to a module, using the functions name as name. /// Adds a function or a (sub)module to a module, using the functions name as name.

View file

@ -1,6 +1,6 @@
#![cfg(feature = "macros")] #![cfg(feature = "macros")]
use pyo3::{exceptions::PyValueError, prelude::*, types::PyString}; use pyo3::{prelude::*, types::PyString};
mod common; mod common;
@ -98,6 +98,8 @@ fn recursive_class_attributes() {
#[test] #[test]
#[cfg(panic = "unwind")] #[cfg(panic = "unwind")]
fn test_fallible_class_attribute() { fn test_fallible_class_attribute() {
use pyo3::exceptions::PyValueError;
#[pyclass] #[pyclass]
struct BrokenClass; struct BrokenClass;