diff --git a/guide/src/class.md b/guide/src/class.md index 6b164ab1..cc5d4c94 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -757,7 +757,7 @@ impl pyo3::class::impl_::PyClassImpl for MyClass { type BaseType = PyAny; type ThreadChecker = pyo3::class::impl_::ThreadCheckerStub; - fn for_each_method_def(visitor: impl FnMut(&pyo3::class::PyMethodDefType)) { + fn for_each_method_def(visitor: &mut dyn FnMut(&pyo3::class::PyMethodDefType)) { use pyo3::class::impl_::*; let collector = PyClassImplCollector::::new(); collector.py_methods().iter() diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 4d568d79..669e07e6 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -436,7 +436,7 @@ fn impl_class( type BaseType = #base; type ThreadChecker = #thread_checker; - fn for_each_method_def(visitor: impl FnMut(&pyo3::class::PyMethodDefType)) { + fn for_each_method_def(visitor: &mut dyn FnMut(&pyo3::class::PyMethodDefType)) { use pyo3::class::impl_::*; let collector = PyClassImplCollector::::new(); #iter_py_methods diff --git a/src/class/impl_.rs b/src/class/impl_.rs index 3113be2b..169f59b3 100644 --- a/src/class/impl_.rs +++ b/src/class/impl_.rs @@ -65,7 +65,7 @@ pub trait PyClassImpl: Sized { /// can be accessed by multiple threads by `threading` module. type ThreadChecker: PyClassThreadChecker; - fn for_each_method_def(_visitor: impl FnMut(&PyMethodDefType)) {} + fn for_each_method_def(_visitor: &mut dyn FnMut(&PyMethodDefType)) {} fn get_new() -> Option { None } diff --git a/src/pyclass.rs b/src/pyclass.rs index da2d3e2a..fb02e501 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -206,13 +206,13 @@ where } // normal methods - let methods = py_class_method_defs::(); + let methods = py_class_method_defs(&T::for_each_method_def); if !methods.is_empty() { slots.push(ffi::Py_tp_methods, into_raw(methods)); } // properties - let props = py_class_properties::(); + let props = py_class_properties(T::Dict::IS_DUMMY, &T::for_each_method_def); if !props.is_empty() { slots.push(ffi::Py_tp_getset, into_raw(props)); } @@ -230,7 +230,7 @@ where name: get_type_name::(module_name)?, basicsize: std::mem::size_of::() as c_int, itemsize: 0, - flags: py_class_flags::(has_gc_methods), + flags: py_class_flags(has_gc_methods, T::IS_GC, T::IS_BASETYPE), slots: slots.0.as_mut_ptr(), }; @@ -299,22 +299,24 @@ fn tp_init_additional(type_object: *mut ffi::PyTypeObject) { #[cfg(any(Py_LIMITED_API, Py_3_10))] fn tp_init_additional(_type_object: *mut ffi::PyTypeObject) {} -fn py_class_flags(has_gc_methods: bool) -> c_uint { - let mut flags = if has_gc_methods || T::IS_GC { +fn py_class_flags(has_gc_methods: bool, is_gc: bool, is_basetype: bool) -> c_uint { + let mut flags = if has_gc_methods || is_gc { ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC } else { ffi::Py_TPFLAGS_DEFAULT }; - if T::IS_BASETYPE { + if is_basetype { flags |= ffi::Py_TPFLAGS_BASETYPE; } flags.try_into().unwrap() } -fn py_class_method_defs() -> Vec { +fn py_class_method_defs( + for_each_method_def: &dyn Fn(&mut dyn FnMut(&PyMethodDefType)), +) -> Vec { let mut defs = Vec::new(); - T::for_each_method_def(|def| match def { + for_each_method_def(&mut |def| match def { PyMethodDefType::Method(def) | PyMethodDefType::Class(def) | PyMethodDefType::Static(def) => { @@ -381,10 +383,13 @@ const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef { }; #[allow(clippy::clippy::collapsible_if)] // for if cfg! -fn py_class_properties() -> Vec { +fn py_class_properties( + is_dummy: bool, + for_each_method_def: &dyn Fn(&mut dyn FnMut(&PyMethodDefType)), +) -> Vec { let mut defs = std::collections::HashMap::new(); - T::for_each_method_def(|def| match def { + for_each_method_def(&mut |def| match def { PyMethodDefType::Getter(getter) => { getter.copy_to(defs.entry(getter.name).or_insert(PY_GET_SET_DEF_INIT)); } @@ -398,7 +403,7 @@ fn py_class_properties() -> Vec { // PyPy doesn't automatically adds __dict__ getter / setter. // PyObject_GenericGetDict not in the limited API until Python 3.10. - push_dict_getset::(&mut props); + push_dict_getset(&mut props, is_dummy); if !props.is_empty() { props.push(unsafe { std::mem::zeroed() }); @@ -407,8 +412,8 @@ fn py_class_properties() -> Vec { } #[cfg(not(any(PyPy, all(Py_LIMITED_API, not(Py_3_10)))))] -fn push_dict_getset(props: &mut Vec) { - if !T::Dict::IS_DUMMY { +fn push_dict_getset(props: &mut Vec, is_dummy: bool) { + if !is_dummy { props.push(ffi::PyGetSetDef { name: "__dict__\0".as_ptr() as *mut c_char, get: Some(ffi::PyObject_GenericGetDict), @@ -420,4 +425,4 @@ fn push_dict_getset(props: &mut Vec) { } #[cfg(any(PyPy, all(Py_LIMITED_API, not(Py_3_10))))] -fn push_dict_getset(_: &mut Vec) {} +fn push_dict_getset(_: &mut Vec, _is_dummy: bool) {} diff --git a/src/type_object.rs b/src/type_object.rs index ebf7df8a..cbc9e326 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -104,6 +104,17 @@ impl LazyStaticType { }) }); + self.ensure_init(py, type_object, T::NAME, &T::for_each_method_def); + type_object + } + + fn ensure_init( + &self, + py: Python, + type_object: *mut ffi::PyTypeObject, + name: &str, + for_each_method_def: &dyn Fn(&mut dyn FnMut(&PyMethodDefType)), + ) { // 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 // with an empty `tp_dict`: now we can create instances of `T`. @@ -117,7 +128,7 @@ impl LazyStaticType { if self.tp_dict_filled.get(py).is_some() { // `tp_dict` is already filled: ok. - return type_object; + return; } { @@ -126,7 +137,7 @@ impl LazyStaticType { if threads.contains(&thread_id) { // Reentrant call: just return the type object, even if the // `tp_dict` is not filled yet. - return type_object; + return; } threads.push(thread_id); } @@ -136,7 +147,7 @@ impl LazyStaticType { // means that another thread can continue the initialization in the // meantime: at worst, we'll just make a useless computation. let mut items = vec![]; - T::for_each_method_def(|def| { + for_each_method_def(&mut |def| { if let PyMethodDefType::ClassAttribute(attr) = def { items.push(( extract_cstr_or_leak_cstring( @@ -162,10 +173,8 @@ impl LazyStaticType { if let Err(err) = result { err.clone_ref(py).print(py); - panic!("An error occured while initializing `{}.__dict__`", T::NAME); + panic!("An error occured while initializing `{}.__dict__`", name); } - - type_object } }