diff --git a/examples/rustapi_module/tests/test_datetime.py b/examples/rustapi_module/tests/test_datetime.py index fa9cb89a9ee..e44b509081e 100644 --- a/examples/rustapi_module/tests/test_datetime.py +++ b/examples/rustapi_module/tests/test_datetime.py @@ -1,6 +1,7 @@ import datetime as pdt import platform import struct +import re import sys import pytest @@ -327,4 +328,5 @@ def test_tz_class_introspection(): tzi = rdt.TzClass() assert tzi.__class__ == rdt.TzClass - assert repr(tzi).startswith(" for some reason. + assert re.match(r"^<[\w\.]*TzClass object at", repr(tzi)) diff --git a/guide/src/class.md b/guide/src/class.md index 4ee4668764b..d63d2bda28a 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -205,7 +205,7 @@ or by `self_.into_super()` as `PyRef`. ```rust # use pyo3::prelude::*; -#[pyclass] +#[pyclass(subclass)] struct BaseClass { val1: usize, } @@ -222,7 +222,7 @@ impl BaseClass { } } -#[pyclass(extends=BaseClass)] +#[pyclass(extends=BaseClass, subclass)] struct SubClass { val2: usize, } diff --git a/src/class/basic.rs b/src/class/basic.rs index d95053bf828..cd7bc713b8b 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -9,8 +9,9 @@ //! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html) use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput}; +use crate::pyclass::maybe_push_slot; use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyErr, PyObject, PyResult}; -use std::os::raw::c_int; +use std::os::raw::{c_int, c_void}; /// Operators for the __richcmp__ method #[derive(Debug)] @@ -147,13 +148,38 @@ pub struct PyObjectMethods { #[doc(hidden)] impl PyObjectMethods { - pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) { - type_object.tp_str = self.tp_str; - type_object.tp_repr = self.tp_repr; - type_object.tp_hash = self.tp_hash; - type_object.tp_getattro = self.tp_getattro; - type_object.tp_richcompare = self.tp_richcompare; - type_object.tp_setattro = self.tp_setattro; + pub(crate) fn update_slots(&self, slots: &mut Vec) { + maybe_push_slot(slots, ffi::Py_tp_str, self.tp_str.map(|v| v as *mut c_void)); + maybe_push_slot( + slots, + ffi::Py_tp_repr, + self.tp_repr.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_hash, + self.tp_hash.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_getattro, + self.tp_getattro.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_richcompare, + self.tp_richcompare.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_setattro, + self.tp_setattro.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_bool, + self.nb_bool.map(|v| v as *mut c_void), + ); } // Set functions used by `#[pyproto]`. pub fn set_str(&mut self) diff --git a/src/class/descr.rs b/src/class/descr.rs index 29cfdcfb4e0..3ff50e8896e 100644 --- a/src/class/descr.rs +++ b/src/class/descr.rs @@ -6,9 +6,10 @@ //! https://docs.python.org/3/reference/datamodel.html#implementing-descriptors) use crate::callback::IntoPyCallbackOutput; +use crate::pyclass::maybe_push_slot; use crate::types::PyAny; use crate::{ffi, FromPyObject, PyClass, PyObject}; -use std::os::raw::c_int; +use std::os::raw::{c_int, c_void}; /// Descriptor interface #[allow(unused_variables)] @@ -79,9 +80,17 @@ pub struct PyDescrMethods { #[doc(hidden)] impl PyDescrMethods { - pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) { - type_object.tp_descr_get = self.tp_descr_get; - type_object.tp_descr_set = self.tp_descr_set; + pub(crate) fn update_slots(&self, slots: &mut Vec) { + maybe_push_slot( + slots, + ffi::Py_tp_descr_get, + self.tp_descr_get.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_descr_set, + self.tp_descr_set.map(|v| v as *mut c_void), + ); } pub fn set_descr_get(&mut self) where diff --git a/src/class/gc.rs b/src/class/gc.rs index f639b8dcc35..d6a29be7ea1 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -3,6 +3,7 @@ //! Python GC support //! +use crate::pyclass::maybe_push_slot; use crate::{ffi, AsPyPointer, PyCell, PyClass, Python}; use std::os::raw::{c_int, c_void}; @@ -27,9 +28,17 @@ pub struct PyGCMethods { #[doc(hidden)] impl PyGCMethods { - pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) { - type_object.tp_traverse = self.tp_traverse; - type_object.tp_clear = self.tp_clear; + pub(crate) fn update_slots(&self, slots: &mut Vec) { + maybe_push_slot( + slots, + ffi::Py_tp_traverse, + self.tp_traverse.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_clear, + self.tp_clear.map(|v| v as *mut c_void), + ); } pub fn set_traverse(&mut self) diff --git a/src/class/iter.rs b/src/class/iter.rs index 0818feb0a44..1ac2475c8a7 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -5,7 +5,9 @@ use crate::callback::IntoPyCallbackOutput; use crate::derive_utils::TryFromPyCell; use crate::err::PyResult; +use crate::pyclass::maybe_push_slot; use crate::{ffi, IntoPy, IntoPyPointer, PyClass, PyObject, Python}; +use std::os::raw::c_void; /// Python Iterator Interface. /// @@ -79,9 +81,17 @@ pub struct PyIterMethods { #[doc(hidden)] impl PyIterMethods { - pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) { - type_object.tp_iter = self.tp_iter; - type_object.tp_iternext = self.tp_iternext; + pub(crate) fn update_slots(&self, slots: &mut Vec) { + maybe_push_slot( + slots, + ffi::Py_tp_iter, + self.tp_iter.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_tp_iternext, + self.tp_iternext.map(|v| v as *mut c_void), + ); } pub fn set_iter(&mut self) where diff --git a/src/class/number.rs b/src/class/number.rs index 568ed38b780..6d6c3255365 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -581,11 +581,6 @@ pub trait PyNumberIndexProtocol<'p>: PyNumberProtocol<'p> { #[doc(hidden)] impl ffi::PyNumberMethods { - pub(crate) fn from_nb_bool(nb_bool: ffi::inquiry) -> *mut Self { - let mut nm = ffi::PyNumberMethods_INIT; - nm.nb_bool = Some(nb_bool); - Box::into_raw(Box::new(nm)) - } pub fn set_add_radd(&mut self) where T: for<'p> PyNumberAddProtocol<'p> + for<'p> PyNumberRAddProtocol<'p>, diff --git a/src/pycell.rs b/src/pycell.rs index ef1acb92372..73a14a31b78 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -455,7 +455,7 @@ impl fmt::Debug for PyCell { /// - You want to get super class. /// ``` /// # use pyo3::prelude::*; -/// #[pyclass] +/// #[pyclass(subclass)] /// struct Parent { /// basename: &'static str, /// } @@ -516,11 +516,11 @@ where /// # Examples /// ``` /// # use pyo3::prelude::*; - /// #[pyclass] + /// #[pyclass(subclass)] /// struct Base1 { /// name1: &'static str, /// } - /// #[pyclass(extends=Base1)] + /// #[pyclass(extends=Base1, subclass)] /// struct Base2 { /// name2: &'static str, /// } diff --git a/src/pyclass.rs b/src/pyclass.rs index a4c10876493..a614897e101 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -7,9 +7,10 @@ use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; use crate::type_object::{type_flags, PyLayout}; use crate::types::PyAny; use crate::{class, ffi, PyCell, PyErr, PyNativeType, PyResult, PyTypeInfo, Python}; +use std::convert::TryInto; use std::ffi::CString; use std::marker::PhantomData; -use std::os::raw::c_void; +use std::os::raw::{c_int, c_uint, c_void}; use std::{ptr, thread}; #[inline] @@ -107,134 +108,423 @@ pub trait PyClass: type BaseNativeType: PyTypeInfo + PyNativeType; } -#[cfg(not(Py_LIMITED_API))] -pub(crate) fn initialize_type_object( +pub(crate) fn maybe_push_slot( + slots: &mut Vec, + slot: c_int, + val: Option<*mut c_void>, +) { + if let Some(v) = val { + slots.push(ffi::PyType_Slot { slot, pfunc: v }); + } +} + +fn push_numbers_slots(slots: &mut Vec, numbers: &ffi::PyNumberMethods) { + maybe_push_slot( + slots, + ffi::Py_nb_add, + numbers.nb_add.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_subtract, + numbers.nb_subtract.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_multiply, + numbers.nb_multiply.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_remainder, + numbers.nb_remainder.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_divmod, + numbers.nb_divmod.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_power, + numbers.nb_power.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_negative, + numbers.nb_negative.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_positive, + numbers.nb_positive.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_absolute, + numbers.nb_absolute.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_bool, + numbers.nb_bool.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_invert, + numbers.nb_invert.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_lshift, + numbers.nb_lshift.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_rshift, + numbers.nb_rshift.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_and, + numbers.nb_and.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_xor, + numbers.nb_xor.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_or, + numbers.nb_or.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_int, + numbers.nb_int.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_float, + numbers.nb_float.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_add, + numbers.nb_inplace_add.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_subtract, + numbers.nb_inplace_subtract.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_multiply, + numbers.nb_inplace_multiply.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_remainder, + numbers.nb_inplace_remainder.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_power, + numbers.nb_inplace_power.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_lshift, + numbers.nb_inplace_lshift.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_rshift, + numbers.nb_inplace_rshift.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_and, + numbers.nb_inplace_and.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_xor, + numbers.nb_inplace_xor.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_or, + numbers.nb_inplace_or.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_floor_divide, + numbers.nb_floor_divide.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_true_divide, + numbers.nb_true_divide.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_floor_divide, + numbers.nb_inplace_floor_divide.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_true_divide, + numbers.nb_inplace_true_divide.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_index, + numbers.nb_index.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_matrix_multiply, + numbers.nb_matrix_multiply.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_nb_inplace_matrix_multiply, + numbers.nb_inplace_matrix_multiply.map(|v| v as *mut c_void), + ); +} + +fn push_mapping_slots(slots: &mut Vec, mapping: &ffi::PyMappingMethods) { + maybe_push_slot( + slots, + ffi::Py_mp_length, + mapping.mp_length.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_mp_subscript, + mapping.mp_subscript.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_mp_ass_subscript, + mapping.mp_ass_subscript.map(|v| v as *mut c_void), + ); +} + +fn push_sequence_slots(slots: &mut Vec, seq: &ffi::PySequenceMethods) { + maybe_push_slot( + slots, + ffi::Py_sq_length, + seq.sq_length.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_sq_concat, + seq.sq_concat.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_sq_repeat, + seq.sq_repeat.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_sq_item, + seq.sq_item.map(|v| v as *mut c_void), + ); + + maybe_push_slot( + slots, + ffi::Py_sq_ass_item, + seq.sq_ass_item.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_sq_contains, + seq.sq_contains.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_sq_inplace_concat, + seq.sq_inplace_concat.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_sq_inplace_repeat, + seq.sq_inplace_repeat.map(|v| v as *mut c_void), + ); +} + +fn push_async_slots(slots: &mut Vec, asnc: &ffi::PyAsyncMethods) { + maybe_push_slot( + slots, + ffi::Py_am_await, + asnc.am_await.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_am_aiter, + asnc.am_aiter.map(|v| v as *mut c_void), + ); + maybe_push_slot( + slots, + ffi::Py_am_anext, + asnc.am_anext.map(|v| v as *mut c_void), + ); +} + +pub(crate) fn create_type_object( py: Python, module_name: Option<&str>, - type_object: &mut ffi::PyTypeObject, -) -> PyResult<()> +) -> PyResult<*mut ffi::PyTypeObject> where T: PyClass, { - type_object.tp_doc = match T::DESCRIPTION { - // PyPy will segfault if passed only a nul terminator as `tp_doc`, ptr::null() is OK though. - "\0" => ptr::null(), - s if s.as_bytes().ends_with(b"\0") => s.as_ptr() as _, - // If the description is not null-terminated, create CString and leak it - s => CString::new(s)?.into_raw(), - }; + let mut slots = vec![]; - type_object.tp_base = T::BaseType::type_object_raw(py); + slots.push(ffi::PyType_Slot { + slot: ffi::Py_tp_base, + pfunc: T::BaseType::type_object_raw(py) as *mut c_void, + }); - type_object.tp_name = match module_name { - Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(), - None => CString::new(T::NAME)?.into_raw(), + let doc = match T::DESCRIPTION { + "\0" => None, + s if s.as_bytes().ends_with(b"\0") => Some(s.as_ptr() as _), + // If the description is not null-terminated, create CString and leak it + s => Some(CString::new(s)?.into_raw() as _), }; + maybe_push_slot(&mut slots, ffi::Py_tp_doc, doc); - // dealloc - type_object.tp_dealloc = tp_dealloc::(); - - // type size - type_object.tp_basicsize = std::mem::size_of::() as ffi::Py_ssize_t; + maybe_push_slot( + &mut slots, + ffi::Py_tp_dealloc, + tp_dealloc::().map(|v| v as *mut c_void), + ); - // __dict__ support - if let Some(dict_offset) = PyCell::::dict_offset() { - type_object.tp_dictoffset = dict_offset as ffi::Py_ssize_t; + let (new, call, mut methods) = py_class_method_defs::(); + maybe_push_slot(&mut slots, ffi::Py_tp_new, new.map(|v| v as *mut c_void)); + maybe_push_slot(&mut slots, ffi::Py_tp_call, call.map(|v| v as *mut c_void)); + // normal methods + if !methods.is_empty() { + methods.push(ffi::PyMethodDef_INIT); + maybe_push_slot( + &mut slots, + ffi::Py_tp_methods, + Some(Box::into_raw(methods.into_boxed_slice()) as *mut c_void), + ); } - // weakref support - if let Some(weakref_offset) = PyCell::::weakref_offset() { - type_object.tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t; + // properties + let mut props = py_class_properties::(); + + if !T::Dict::IS_DUMMY { + props.push(ffi::PyGetSetDef_DICT); + } + if !props.is_empty() { + props.push(ffi::PyGetSetDef_INIT); + maybe_push_slot( + &mut slots, + ffi::Py_tp_getset, + Some(Box::into_raw(props.into_boxed_slice()) as *mut c_void), + ); } - // GC support - if let Some(gc) = T::gc_methods() { - unsafe { gc.as_ref() }.update_typeobj(type_object); + if let Some(basic) = T::basic_methods() { + unsafe { basic.as_ref() }.update_slots(&mut slots); } - // descriptor protocol - if let Some(descr) = T::descr_methods() { - unsafe { descr.as_ref() }.update_typeobj(type_object); + if let Some(number) = T::number_methods() { + push_numbers_slots(&mut slots, unsafe { number.as_ref() }); } // iterator methods if let Some(iter) = T::iter_methods() { - unsafe { iter.as_ref() }.update_typeobj(type_object); + unsafe { iter.as_ref() }.update_slots(&mut slots); } - // nb_bool is a part of PyObjectProtocol, but should be placed under tp_as_number - let mut nb_bool = None; - // basic methods - if let Some(basic) = T::basic_methods() { - unsafe { basic.as_ref() }.update_typeobj(type_object); - nb_bool = unsafe { basic.as_ref() }.nb_bool; + // mapping methods + if let Some(mapping) = T::mapping_methods() { + push_mapping_slots(&mut slots, unsafe { mapping.as_ref() }); } - // number methods - type_object.tp_as_number = T::number_methods() - .map(|mut p| { - unsafe { p.as_mut() }.nb_bool = nb_bool; - p.as_ptr() - }) - .unwrap_or_else(|| nb_bool.map_or_else(ptr::null_mut, ffi::PyNumberMethods::from_nb_bool)); - // mapping methods - type_object.tp_as_mapping = T::mapping_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); // sequence methods - type_object.tp_as_sequence = T::sequence_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - // async methods - type_object.tp_as_async = T::async_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - // buffer protocol - type_object.tp_as_buffer = T::buffer_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - - let (new, call, mut methods) = py_class_method_defs::(); - - // normal methods - if !methods.is_empty() { - methods.push(ffi::PyMethodDef_INIT); - type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as _; + if let Some(seq) = T::sequence_methods() { + push_sequence_slots(&mut slots, unsafe { seq.as_ref() }); } - // __new__ method - type_object.tp_new = new; - // __call__ method - type_object.tp_call = call; - - // properties - let mut props = py_class_properties::(); + // descriptor protocol + if let Some(descr) = T::descr_methods() { + unsafe { descr.as_ref() }.update_slots(&mut slots); + } - if !T::Dict::IS_DUMMY { - props.push(ffi::PyGetSetDef_DICT); + // async methods + if let Some(asnc) = T::async_methods() { + push_async_slots(&mut slots, unsafe { asnc.as_ref() }); } - if !props.is_empty() { - props.push(ffi::PyGetSetDef_INIT); - type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as _; + + // GC support + if let Some(gc) = T::gc_methods() { + unsafe { gc.as_ref() }.update_slots(&mut slots); } - // set type flags - py_class_flags::(type_object); + slots.push(ffi::PyType_Slot { + slot: 0, + pfunc: ptr::null_mut(), + }); + let mut spec = ffi::PyType_Spec { + name: match module_name { + Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(), + None => CString::new(T::NAME)?.into_raw(), + }, + basicsize: std::mem::size_of::() as c_int, + itemsize: 0, + flags: py_class_flags::(), + slots: slots.as_mut_slice().as_mut_ptr(), + }; - // register type object - unsafe { - if ffi::PyType_Ready(type_object) == 0 { - Ok(()) - } else { - PyErr::fetch(py).into() + let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) }; + if type_object.is_null() { + PyErr::fetch(py).into() + } else { + // Just patch the type objects for the things there's no + // PyType_FromSpec API for... there's no reason this should work, + // except for that it does and we have tests. + let mut type_object = type_object as *mut ffi::PyTypeObject; + if let Some(buffer) = T::buffer_methods() { + unsafe { + (*(*type_object).tp_as_buffer).bf_getbuffer = buffer.as_ref().bf_getbuffer; + (*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.as_ref().bf_releasebuffer; + } + } + // __dict__ support + if let Some(dict_offset) = PyCell::::dict_offset() { + unsafe { + (*type_object).tp_dictoffset = dict_offset as ffi::Py_ssize_t; + } } + // weakref support + if let Some(weakref_offset) = PyCell::::weakref_offset() { + unsafe { + (*type_object).tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t; + } + } + Ok(type_object) } } -fn py_class_flags(type_object: &mut ffi::PyTypeObject) { - if type_object.tp_traverse != None - || type_object.tp_clear != None - || T::FLAGS & type_flags::GC != 0 - { - type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC; +fn py_class_flags() -> c_uint { + let mut flags = if T::gc_methods().is_some() || T::FLAGS & type_flags::GC != 0 { + ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC } else { - type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT; - } + ffi::Py_TPFLAGS_DEFAULT + }; if T::FLAGS & type_flags::BASETYPE != 0 { - type_object.tp_flags |= ffi::Py_TPFLAGS_BASETYPE; + flags |= ffi::Py_TPFLAGS_BASETYPE; } + flags.try_into().unwrap() } pub(crate) fn py_class_attributes() -> impl Iterator { @@ -244,6 +534,21 @@ pub(crate) fn py_class_attributes() -> impl Iterator Option { + unsafe extern "C" fn fallback_new( + _subtype: *mut ffi::PyTypeObject, + _args: *mut ffi::PyObject, + _kwds: *mut ffi::PyObject, + ) -> *mut ffi::PyObject { + crate::callback_body!(py, { + Err::<(), _>(crate::exceptions::PyTypeError::py_err( + "No constructor defined", + )) + }) + } + Some(fallback_new) +} + fn py_class_method_defs() -> ( Option, Option, @@ -251,7 +556,7 @@ fn py_class_method_defs() -> ( ) { let mut defs = Vec::new(); let mut call = None; - let mut new = None; + let mut new = fallback_new(); for def in T::py_methods() { match *def { diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index 81a68f2cbd1..1d73f960cfc 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -30,12 +30,12 @@ impl PyObjectInit for PyNativeTypeInitializer { /// ``` /// # use pyo3::prelude::*; /// # use pyo3::py_run; -/// #[pyclass] +/// #[pyclass(subclass)] /// struct BaseClass { /// #[pyo3(get)] /// basename: &'static str, /// } -/// #[pyclass(extends=BaseClass)] +/// #[pyclass(extends=BaseClass, subclass)] /// struct SubClass { /// #[pyo3(get)] /// subname: &'static str, diff --git a/src/type_object.rs b/src/type_object.rs index 546f87824fb..487e738036e 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -3,7 +3,7 @@ use crate::conversion::IntoPyPointer; use crate::once_cell::GILOnceCell; -use crate::pyclass::{initialize_type_object, py_class_attributes, PyClass}; +use crate::pyclass::{create_type_object, py_class_attributes, PyClass}; use crate::pyclass_init::PyObjectInit; use crate::types::{PyAny, PyType}; use crate::{ffi, AsPyPointer, PyErr, PyNativeType, PyObject, PyResult, Python}; @@ -157,12 +157,10 @@ impl LazyStaticType { pub fn get_or_init(&self, py: Python) -> *mut ffi::PyTypeObject { let type_object = *self.value.get_or_init(py, || { - let mut type_object = Box::new(ffi::PyTypeObject_INIT); - initialize_type_object::(py, T::MODULE, type_object.as_mut()).unwrap_or_else(|e| { + create_type_object::(py, T::MODULE).unwrap_or_else(|e| { e.print(py); panic!("An error occurred while initializing class {}", T::NAME) - }); - Box::into_raw(type_object) + }) }); // We might want to fill the `tp_dict` with python instances of `T` diff --git a/tests/test_class_attributes.rs b/tests/test_class_attributes.rs index 752935d76e5..e4893d631d6 100644 --- a/tests/test_class_attributes.rs +++ b/tests/test_class_attributes.rs @@ -51,7 +51,10 @@ fn class_attributes() { py_assert!(py, foo_obj, "foo_obj.MY_CONST == 'foobar'"); } +// Ignored because heap types are not immutable: +// https://github.com/python/cpython/blob/master/Objects/typeobject.c#L3399-L3409 #[test] +#[ignore] fn class_attributes_are_immutable() { let gil = Python::acquire_gil(); let py = gil.python(); diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index 649944a10f9..4c024ece2c2 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -119,7 +119,11 @@ fn test_raw_idents() { #[pyclass] struct EmptyClassInModule {} +// Ignored because heap types do not show up as being in builtins, instead they +// raise AttributeError: +// https://github.com/python/cpython/blob/master/Objects/typeobject.c#L544-L573 #[test] +#[ignore] fn empty_class_in_module() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -165,7 +169,7 @@ fn class_with_object_field() { py_assert!(py, ty, "ty(None).value == None"); } -#[pyclass(unsendable)] +#[pyclass(unsendable, subclass)] struct UnsendableBase { value: std::rc::Rc, } diff --git a/tests/test_class_conversion.rs b/tests/test_class_conversion.rs index 2c2d3030828..d379f774e0c 100644 --- a/tests/test_class_conversion.rs +++ b/tests/test_class_conversion.rs @@ -30,7 +30,7 @@ fn test_cloneable_pyclass() { assert_eq!(&c, &*mrc); } -#[pyclass] +#[pyclass(subclass)] #[derive(Default)] struct BaseClass { value: i32, diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs index 5cc56f61575..5d97f82b36b 100644 --- a/tests/test_dunder.rs +++ b/tests/test_dunder.rs @@ -453,7 +453,7 @@ fn test_cls_impl() { .unwrap(); } -#[pyclass(dict)] +#[pyclass(dict, subclass)] struct DunderDictSupport {} #[test] diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 6da23dc5fb1..b7aced4eecb 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -146,7 +146,7 @@ fn gc_integration2() { py_run!(py, inst, "import gc; assert inst in gc.get_objects()"); } -#[pyclass(weakref)] +#[pyclass(weakref, subclass)] struct WeakRefSupport {} #[test] @@ -179,7 +179,7 @@ fn inherited_weakref() { ); } -#[pyclass] +#[pyclass(subclass)] struct BaseClassWithDrop { data: Option>, } diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 4c346563e87..a2aad91a244 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -6,7 +6,7 @@ use pyo3::types::IntoPyDict; use pyo3::types::{PyDict, PySet}; mod common; -#[pyclass] +#[pyclass(subclass)] struct BaseClass { #[pyo3(get)] val1: usize, @@ -106,7 +106,7 @@ fn mutation_fails() { ) } -#[pyclass] +#[pyclass(subclass)] struct BaseClassWithResult { _val: usize, } diff --git a/tests/test_text_signature.rs b/tests/test_text_signature.rs index 85211a34f10..617676c88fb 100644 --- a/tests/test_text_signature.rs +++ b/tests/test_text_signature.rs @@ -31,7 +31,10 @@ fn class_with_docs() { py_assert!(py, typeobj, "typeobj.__text_signature__ is None"); } +// Ignored because heap types don't have working __text_signature__: +// https://github.com/python/cpython/blob/master/Objects/typeobject.c#L864-L870 #[test] +#[ignore] fn class_with_docs_and_signature() { /// docs line1 #[pyclass] @@ -66,7 +69,10 @@ fn class_with_docs_and_signature() { ); } +// Ignored because heap types don't have working __text_signature__: +// https://github.com/python/cpython/blob/master/Objects/typeobject.c#L864-L870 #[test] +#[ignore] fn class_with_signature() { #[pyclass] #[text_signature = "(a, b=None, *, c=42)"]