From 400462063dd319b9b6b5b5016cedca3932d25f54 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 31 Aug 2020 20:36:26 -0400 Subject: [PATCH 01/36] Proof of concept of using PEP384s PyType_Spec --- .../rustapi_module/tests/test_datetime.py | 4 +- guide/src/class.md | 4 +- src/class/basic.rs | 42 +- src/class/descr.rs | 17 +- src/class/gc.rs | 15 +- src/class/iter.rs | 16 +- src/class/number.rs | 5 - src/pycell.rs | 6 +- src/pyclass.rs | 487 ++++++++++++++---- src/pyclass_init.rs | 4 +- src/type_object.rs | 8 +- tests/test_class_attributes.rs | 3 + tests/test_class_basics.rs | 6 +- tests/test_class_conversion.rs | 2 +- tests/test_dunder.rs | 2 +- tests/test_gc.rs | 4 +- tests/test_inheritance.rs | 4 +- tests/test_text_signature.rs | 6 + 18 files changed, 501 insertions(+), 134 deletions(-) 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)"] From d2a10b688f85a679b631cc269c89146830608dbd Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sat, 5 Sep 2020 17:52:41 +0900 Subject: [PATCH 02/36] Introduce all-apis feature to support abi3 --- Cargo.toml | 11 +++++------ build.rs | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e9e14fa6270..8f903e3a707 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,12 @@ trybuild = "1.0.23" rustversion = "1.0" [features] -default = ["macros"] +default = ["macros", "all-apis"] macros = ["ctor", "indoc", "inventory", "paste", "pyo3cls", "unindent"] +# Enable the use of `not(Py_LIMITED_API)` s. +# This is incompatible with abi3(PEP384). See https://www.python.org/dev/peps/pep-0384/ more. +all-apis = [] + # Optimizes PyObject to Vec conversion and so on. nightly = [] @@ -46,11 +50,6 @@ python3 = [] # so that the module can also be used with statically linked python interpreters. extension-module = [] -# The stable cpython abi as defined in PEP 384. Currently broken with -# many compilation errors. Pull Requests working towards fixing that -# are welcome. -# abi3 = [] - [workspace] members = [ "pyo3cls", diff --git a/build.rs b/build.rs index 04ea5a477e3..d537bdef962 100644 --- a/build.rs +++ b/build.rs @@ -757,7 +757,7 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result { bail!("Python 2 is not supported"); } - if env::var_os("CARGO_FEATURE_ABI3").is_some() { + if env::var_os("CARGO_FEATURE_ALL_APIS").is_none() { println!("cargo:rustc-cfg=Py_LIMITED_API"); } From c2f10e2185cb77dbefb3b0abbefb7d359dac85f8 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sat, 5 Sep 2020 19:01:22 +0900 Subject: [PATCH 03/36] Restructure protcol-table initialization --- pyo3-derive-backend/src/defs.rs | 10 +- src/class/basic.rs | 46 +--- src/class/buffer.rs | 15 +- src/class/descr.rs | 19 +- src/class/gc.rs | 17 +- src/class/iter.rs | 18 +- src/class/mapping.rs | 21 +- src/class/number.rs | 161 +++++++++++- src/class/proto_methods.rs | 7 +- src/class/pyasync.rs | 19 +- src/class/sequence.rs | 54 +++- src/ffi/descrobject.rs | 1 - src/ffi/object.rs | 119 +-------- src/pyclass.rs | 446 ++++++-------------------------- 14 files changed, 383 insertions(+), 570 deletions(-) diff --git a/pyo3-derive-backend/src/defs.rs b/pyo3-derive-backend/src/defs.rs index 0bdfedf9915..6199a722b8c 100644 --- a/pyo3-derive-backend/src/defs.rs +++ b/pyo3-derive-backend/src/defs.rs @@ -171,7 +171,7 @@ pub const OBJECT: Proto = Proto { pub const ASYNC: Proto = Proto { name: "Async", - slot_table: "pyo3::ffi::PyAsyncMethods", + slot_table: "pyo3::class::pyasync::PyAsyncMethods", set_slot_table: "set_async_methods", methods: &[ MethodProto::UnaryS { @@ -220,7 +220,7 @@ pub const ASYNC: Proto = Proto { pub const BUFFER: Proto = Proto { name: "Buffer", - slot_table: "pyo3::ffi::PyBufferProcs", + slot_table: "pyo3::class::buffer::PyBufferProcs", set_slot_table: "set_buffer_methods", methods: &[ MethodProto::Unary { @@ -358,7 +358,7 @@ pub const ITER: Proto = Proto { pub const MAPPING: Proto = Proto { name: "Mapping", - slot_table: "pyo3::ffi::PyMappingMethods", + slot_table: "pyo3::class::mapping::PyMappingMethods", set_slot_table: "set_mapping_methods", methods: &[ MethodProto::Unary { @@ -401,7 +401,7 @@ pub const MAPPING: Proto = Proto { pub const SEQ: Proto = Proto { name: "Sequence", - slot_table: "pyo3::ffi::PySequenceMethods", + slot_table: "pyo3::class::sequence::PySequenceMethods", set_slot_table: "set_sequence_methods", methods: &[ MethodProto::Unary { @@ -467,7 +467,7 @@ pub const SEQ: Proto = Proto { pub const NUM: Proto = Proto { name: "Number", - slot_table: "pyo3::ffi::PyNumberMethods", + slot_table: "pyo3::class::number::PyNumberMethods", set_slot_table: "set_number_methods", methods: &[ MethodProto::BinaryS { diff --git a/src/class/basic.rs b/src/class/basic.rs index cd7bc713b8b..37ca5275717 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -9,9 +9,8 @@ //! [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, c_void}; +use std::os::raw::c_int; /// Operators for the __richcmp__ method #[derive(Debug)] @@ -148,39 +147,6 @@ pub struct PyObjectMethods { #[doc(hidden)] impl PyObjectMethods { - 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) where @@ -242,6 +208,16 @@ impl PyObjectMethods { { self.nb_bool = py_unary_func!(PyObjectBoolProtocol, T::__bool__, c_int); } + + pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { + slots.maybe_push(ffi::Py_tp_str, self.tp_str.map(|v| v as _)); + slots.maybe_push(ffi::Py_tp_repr, self.tp_repr.map(|v| v as _)); + slots.maybe_push(ffi::Py_tp_hash, self.tp_hash.map(|v| v as _)); + slots.maybe_push(ffi::Py_tp_getattro, self.tp_getattro.map(|v| v as _)); + slots.maybe_push(ffi::Py_tp_richcompare, self.tp_richcompare.map(|v| v as _)); + slots.maybe_push(ffi::Py_tp_setattro, self.tp_setattro.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_bool, self.nb_bool.map(|v| v as _)); + } } fn tp_getattro() -> Option diff --git a/src/class/buffer.rs b/src/class/buffer.rs index f2977274b9f..99a382020c0 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -5,12 +5,19 @@ //! For more information check [buffer protocol](https://docs.python.org/3/c-api/buffer.html) //! c-api use crate::callback::IntoPyCallbackOutput; -use crate::{ - ffi::{self, PyBufferProcs}, - PyCell, PyClass, PyRefMut, -}; +use crate::{ffi, PyCell, PyClass, PyRefMut}; use std::os::raw::c_int; +#[cfg(Py_LIMITED_API)] +#[derive(Clone, Default)] +pub struct PyBufferProcs { + pub bf_getbuffer: Option, + pub bf_releasebuffer: Option, +} + +#[cfg(not(Py_LIMITED_API))] +pub use ffi::PyBufferProcs; + /// Buffer protocol interface /// /// For more information check [buffer protocol](https://docs.python.org/3/c-api/buffer.html) diff --git a/src/class/descr.rs b/src/class/descr.rs index 3ff50e8896e..88fb4dc50cd 100644 --- a/src/class/descr.rs +++ b/src/class/descr.rs @@ -6,10 +6,9 @@ //! 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, c_void}; +use std::os::raw::c_int; /// Descriptor interface #[allow(unused_variables)] @@ -80,18 +79,6 @@ pub struct PyDescrMethods { #[doc(hidden)] impl PyDescrMethods { - 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 T: for<'p> PyDescrGetProtocol<'p>, @@ -104,4 +91,8 @@ impl PyDescrMethods { { self.tp_descr_set = py_ternarys_func!(PyDescrSetProtocol, T::__set__, c_int); } + pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { + slots.maybe_push(ffi::Py_tp_descr_get, self.tp_descr_get.map(|v| v as _)); + slots.maybe_push(ffi::Py_tp_descr_set, self.tp_descr_set.map(|v| v as _)); + } } diff --git a/src/class/gc.rs b/src/class/gc.rs index d6a29be7ea1..d600e663f5b 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -3,7 +3,6 @@ //! Python GC support //! -use crate::pyclass::maybe_push_slot; use crate::{ffi, AsPyPointer, PyCell, PyClass, Python}; use std::os::raw::{c_int, c_void}; @@ -28,17 +27,9 @@ pub struct PyGCMethods { #[doc(hidden)] impl PyGCMethods { - 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(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { + slots.maybe_push(ffi::Py_tp_traverse, self.tp_traverse.map(|v| v as _)); + slots.maybe_push(ffi::Py_tp_clear, self.tp_clear.map(|v| v as _)); } pub fn set_traverse(&mut self) @@ -57,7 +48,7 @@ impl PyGCMethods { } /// Object visitor for GC. -#[derive(Copy, Clone)] +#[derive(Clone)] pub struct PyVisit<'p> { visit: ffi::visitproc, arg: *mut c_void, diff --git a/src/class/iter.rs b/src/class/iter.rs index 1ac2475c8a7..8c2934bb9aa 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -5,9 +5,7 @@ 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. /// @@ -81,18 +79,6 @@ pub struct PyIterMethods { #[doc(hidden)] impl PyIterMethods { - 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 T: for<'p> PyIterIterProtocol<'p>, @@ -105,6 +91,10 @@ impl PyIterMethods { { self.tp_iternext = py_unarys_func!(PyIterNextProtocol, T::__next__); } + pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { + slots.maybe_push(ffi::Py_tp_iter, self.tp_iter.map(|v| v as _)); + slots.maybe_push(ffi::Py_tp_iternext, self.tp_iternext.map(|v| v as _)); + } } /// Output of `__next__` which can either `yield` the next value in the iteration, or diff --git a/src/class/mapping.rs b/src/class/mapping.rs index d20547e18b8..61969883fab 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -7,6 +7,17 @@ use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; use crate::{exceptions, ffi, FromPyObject, PyClass, PyObject}; +#[cfg(Py_LIMITED_API)] +#[derive(Clone, Default)] +pub struct PyMappingMethods { + pub mp_length: Option, + pub mp_subscript: Option, + pub mp_ass_subscript: Option, +} + +#[cfg(not(Py_LIMITED_API))] +pub use ffi::PyMappingMethods; + /// Mapping interface #[allow(unused_variables)] pub trait PyMappingProtocol<'p>: PyClass { @@ -74,7 +85,7 @@ pub trait PyMappingReversedProtocol<'p>: PyMappingProtocol<'p> { } #[doc(hidden)] -impl ffi::PyMappingMethods { +impl PyMappingMethods { pub fn set_length(&mut self) where T: for<'p> PyMappingLenProtocol<'p>, @@ -111,4 +122,12 @@ impl ffi::PyMappingMethods { __delitem__ ); } + pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { + slots.maybe_push(ffi::Py_mp_length, self.mp_length.map(|v| v as _)); + slots.maybe_push(ffi::Py_mp_subscript, self.mp_subscript.map(|v| v as _)); + slots.maybe_push( + ffi::Py_mp_ass_subscript, + self.mp_ass_subscript.map(|v| v as _), + ); + } } diff --git a/src/class/number.rs b/src/class/number.rs index 6d6c3255365..8843587a49a 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -2,11 +2,98 @@ //! Python Number Interface //! Trait and support implementation for implementing number protocol - use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; use crate::{ffi, FromPyObject, PyClass, PyObject}; +#[cfg(Py_LIMITED_API)] +#[derive(Clone)] +pub struct PyNumberMethods { + pub nb_add: Option, + pub nb_subtract: Option, + pub nb_multiply: Option, + pub nb_remainder: Option, + pub nb_divmod: Option, + pub nb_power: Option, + pub nb_negative: Option, + pub nb_positive: Option, + pub nb_absolute: Option, + pub nb_bool: Option, + pub nb_invert: Option, + pub nb_lshift: Option, + pub nb_rshift: Option, + pub nb_and: Option, + pub nb_xor: Option, + pub nb_or: Option, + pub nb_int: Option, + pub nb_reserved: *mut c_void, + pub nb_float: Option, + pub nb_inplace_add: Option, + pub nb_inplace_subtract: Option, + pub nb_inplace_multiply: Option, + pub nb_inplace_remainder: Option, + pub nb_inplace_power: Option, + pub nb_inplace_lshift: Option, + pub nb_inplace_rshift: Option, + pub nb_inplace_and: Option, + pub nb_inplace_xor: Option, + pub nb_inplace_or: Option, + pub nb_floor_divide: Option, + pub nb_true_divide: Option, + pub nb_inplace_floor_divide: Option, + pub nb_inplace_true_divide: Option, + pub nb_index: Option, + pub nb_matrix_multiply: Option, + pub nb_inplace_matrix_multiply: Option, +} + +#[cfg(not(Py_LIMITED_API))] +pub use crate::ffi::PyNumberMethods; + +impl Default for PyNumberMethods { + #[inline] + fn default() -> Self { + Self { + nb_add: None, + nb_subtract: None, + nb_multiply: None, + nb_remainder: None, + nb_divmod: None, + nb_power: None, + nb_negative: None, + nb_positive: None, + nb_absolute: None, + nb_bool: None, + nb_invert: None, + nb_lshift: None, + nb_rshift: None, + nb_and: None, + nb_xor: None, + nb_or: None, + nb_int: None, + nb_reserved: ::std::ptr::null_mut(), + nb_float: None, + nb_inplace_add: None, + nb_inplace_subtract: None, + nb_inplace_multiply: None, + nb_inplace_remainder: None, + nb_inplace_power: None, + nb_inplace_lshift: None, + nb_inplace_rshift: None, + nb_inplace_and: None, + nb_inplace_xor: None, + nb_inplace_or: None, + nb_floor_divide: None, + nb_true_divide: None, + nb_inplace_floor_divide: None, + nb_inplace_true_divide: None, + nb_index: None, + nb_matrix_multiply: None, + nb_inplace_matrix_multiply: None, + } + } +} + /// Number interface #[allow(unused_variables)] pub trait PyNumberProtocol<'p>: PyClass { @@ -580,7 +667,7 @@ pub trait PyNumberIndexProtocol<'p>: PyNumberProtocol<'p> { } #[doc(hidden)] -impl ffi::PyNumberMethods { +impl PyNumberMethods { pub fn set_add_radd(&mut self) where T: for<'p> PyNumberAddProtocol<'p> + for<'p> PyNumberRAddProtocol<'p>, @@ -1068,4 +1155,74 @@ impl ffi::PyNumberMethods { self.nb_inplace_matrix_multiply = py_binary_self_func!(PyNumberIMatmulProtocol, T::__imatmul__); } + pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { + slots.maybe_push(ffi::Py_nb_add, self.nb_add.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_subtract, self.nb_subtract.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_multiply, self.nb_multiply.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_remainder, self.nb_remainder.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_divmod, self.nb_divmod.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_power, self.nb_power.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_negative, self.nb_negative.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_positive, self.nb_positive.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_absolute, self.nb_absolute.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_bool, self.nb_bool.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_invert, self.nb_invert.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_lshift, self.nb_lshift.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_rshift, self.nb_rshift.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_and, self.nb_and.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_xor, self.nb_xor.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_or, self.nb_or.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_int, self.nb_int.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_float, self.nb_float.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_inplace_add, self.nb_inplace_add.map(|v| v as _)); + slots.maybe_push( + ffi::Py_nb_inplace_subtract, + self.nb_inplace_subtract.map(|v| v as _), + ); + slots.maybe_push( + ffi::Py_nb_inplace_multiply, + self.nb_inplace_multiply.map(|v| v as _), + ); + slots.maybe_push( + ffi::Py_nb_inplace_remainder, + self.nb_inplace_remainder.map(|v| v as _), + ); + slots.maybe_push( + ffi::Py_nb_inplace_power, + self.nb_inplace_power.map(|v| v as _), + ); + slots.maybe_push( + ffi::Py_nb_inplace_lshift, + self.nb_inplace_lshift.map(|v| v as _), + ); + slots.maybe_push( + ffi::Py_nb_inplace_rshift, + self.nb_inplace_rshift.map(|v| v as _), + ); + slots.maybe_push(ffi::Py_nb_inplace_and, self.nb_inplace_and.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_inplace_xor, self.nb_inplace_xor.map(|v| v as _)); + slots.maybe_push(ffi::Py_nb_inplace_or, self.nb_inplace_or.map(|v| v as _)); + slots.maybe_push( + ffi::Py_nb_floor_divide, + self.nb_floor_divide.map(|v| v as _), + ); + slots.maybe_push(ffi::Py_nb_true_divide, self.nb_true_divide.map(|v| v as _)); + slots.maybe_push( + ffi::Py_nb_inplace_floor_divide, + self.nb_inplace_floor_divide.map(|v| v as _), + ); + slots.maybe_push( + ffi::Py_nb_inplace_true_divide, + self.nb_inplace_true_divide.map(|v| v as _), + ); + slots.maybe_push(ffi::Py_nb_index, self.nb_index.map(|v| v as _)); + slots.maybe_push( + ffi::Py_nb_matrix_multiply, + self.nb_matrix_multiply.map(|v| v as _), + ); + slots.maybe_push( + ffi::Py_nb_inplace_matrix_multiply, + self.nb_inplace_matrix_multiply.map(|v| v as _), + ); + } } diff --git a/src/class/proto_methods.rs b/src/class/proto_methods.rs index 76e75d9f440..d28d1522cca 100644 --- a/src/class/proto_methods.rs +++ b/src/class/proto_methods.rs @@ -1,8 +1,7 @@ use crate::class::{ - basic::PyObjectMethods, descr::PyDescrMethods, gc::PyGCMethods, iter::PyIterMethods, -}; -use crate::ffi::{ - PyAsyncMethods, PyBufferProcs, PyMappingMethods, PyNumberMethods, PySequenceMethods, + basic::PyObjectMethods, buffer::PyBufferProcs, descr::PyDescrMethods, gc::PyGCMethods, + iter::PyIterMethods, mapping::PyMappingMethods, number::PyNumberMethods, + pyasync::PyAsyncMethods, sequence::PySequenceMethods, }; use std::{ ptr::{self, NonNull}, diff --git a/src/class/pyasync.rs b/src/class/pyasync.rs index 82548835c7f..cbc67ae004a 100644 --- a/src/class/pyasync.rs +++ b/src/class/pyasync.rs @@ -13,6 +13,17 @@ use crate::derive_utils::TryFromPyCell; use crate::err::PyResult; use crate::{ffi, IntoPy, IntoPyPointer, PyClass, PyObject, Python}; +#[cfg(Py_LIMITED_API)] +#[derive(Clone, Default)] +pub struct PyAsyncMethods { + pub am_await: Option, + pub am_aiter: Option, + pub am_anext: Option, +} + +#[cfg(not(Py_LIMITED_API))] +pub use ffi::PyAsyncMethods; + /// Python Async/Await support interface. /// /// Each method in this trait corresponds to Python async/await implementation. @@ -86,7 +97,7 @@ pub trait PyAsyncAexitProtocol<'p>: PyAsyncProtocol<'p> { } #[doc(hidden)] -impl ffi::PyAsyncMethods { +impl PyAsyncMethods { pub fn set_await(&mut self) where T: for<'p> PyAsyncAwaitProtocol<'p>, @@ -105,6 +116,12 @@ impl ffi::PyAsyncMethods { { self.am_anext = am_anext::(); } + + pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { + slots.maybe_push(ffi::Py_am_await, self.am_await.map(|v| v as _)); + slots.maybe_push(ffi::Py_am_aiter, self.am_aiter.map(|v| v as _)); + slots.maybe_push(ffi::Py_am_anext, self.am_anext.map(|v| v as _)); + } } pub enum IterANextOutput { diff --git a/src/class/sequence.rs b/src/class/sequence.rs index a9c62f03d0b..3d2e29f201a 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -9,6 +9,41 @@ use crate::err::PyErr; use crate::{exceptions, ffi, PyAny, PyCell, PyClass, PyObject}; use std::os::raw::c_int; +#[cfg(Py_LIMITED_API)] +#[derive(Clone)] +pub struct PySequenceMethods { + pub sq_length: Option, + pub sq_concat: Option, + pub sq_repeat: Option, + pub sq_item: Option, + pub was_sq_slice: *mut c_void, + pub sq_ass_item: Option, + pub was_sq_ass_slice: *mut c_void, + pub sq_contains: Option, + pub sq_inplace_concat: Option, + pub sq_inplace_repeat: Option, +} + +#[cfg(not(Py_LIMITED_API))] +pub use ffi::PySequenceMethods; + +impl Default for PySequenceMethods { + fn default() -> Self { + Self { + sq_length: None, + sq_concat: None, + sq_repeat: None, + sq_item: None, + was_sq_slice: std::ptr::null_mut(), + sq_ass_item: None, + was_sq_ass_slice: std::ptr::null_mut(), + sq_contains: None, + sq_inplace_concat: None, + sq_inplace_repeat: None, + } + } +} + /// Sequence interface #[allow(unused_variables)] pub trait PySequenceProtocol<'p>: PyClass + Sized { @@ -129,7 +164,7 @@ pub trait PySequenceInplaceRepeatProtocol<'p>: } #[doc(hidden)] -impl ffi::PySequenceMethods { +impl PySequenceMethods { pub fn set_len(&mut self) where T: for<'p> PySequenceLenProtocol<'p>, @@ -199,6 +234,23 @@ impl ffi::PySequenceMethods { call_mut ) } + + pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { + slots.maybe_push(ffi::Py_sq_length, self.sq_length.map(|v| v as _)); + slots.maybe_push(ffi::Py_sq_concat, self.sq_concat.map(|v| v as _)); + slots.maybe_push(ffi::Py_sq_repeat, self.sq_repeat.map(|v| v as _)); + slots.maybe_push(ffi::Py_sq_item, self.sq_item.map(|v| v as _)); + slots.maybe_push(ffi::Py_sq_ass_item, self.sq_ass_item.map(|v| v as _)); + slots.maybe_push(ffi::Py_sq_contains, self.sq_contains.map(|v| v as _)); + slots.maybe_push( + ffi::Py_sq_inplace_concat, + self.sq_inplace_concat.map(|v| v as _), + ); + slots.maybe_push( + ffi::Py_sq_inplace_repeat, + self.sq_inplace_repeat.map(|v| v as _), + ); + } } /// It can be possible to delete and set items (PySequenceSetItemProtocol and diff --git a/src/ffi/descrobject.rs b/src/ffi/descrobject.rs index 828b85c941d..0651eee59b9 100644 --- a/src/ffi/descrobject.rs +++ b/src/ffi/descrobject.rs @@ -7,7 +7,6 @@ use std::os::raw::{c_char, c_int, c_void}; use std::ptr; pub type getter = unsafe extern "C" fn(slf: *mut PyObject, closure: *mut c_void) -> *mut PyObject; - pub type setter = unsafe extern "C" fn(slf: *mut PyObject, value: *mut PyObject, closure: *mut c_void) -> c_int; diff --git a/src/ffi/object.rs b/src/ffi/object.rs index d4f1b8d8676..80c9278d3fd 100644 --- a/src/ffi/object.rs +++ b/src/ffi/object.rs @@ -314,69 +314,14 @@ mod typeobject { pub nb_inplace_matrix_multiply: Option, } - impl Default for PyNumberMethods { - #[inline] - fn default() -> Self { - PyNumberMethods_INIT - } - } macro_rules! as_expr { ($e:expr) => { $e }; } - macro_rules! py_number_methods_init { - ($($tail:tt)*) => { - as_expr! { - PyNumberMethods { - nb_add: None, - nb_subtract: None, - nb_multiply: None, - nb_remainder: None, - nb_divmod: None, - nb_power: None, - nb_negative: None, - nb_positive: None, - nb_absolute: None, - nb_bool: None, - nb_invert: None, - nb_lshift: None, - nb_rshift: None, - nb_and: None, - nb_xor: None, - nb_or: None, - nb_int: None, - nb_reserved: ::std::ptr::null_mut(), - nb_float: None, - nb_inplace_add: None, - nb_inplace_subtract: None, - nb_inplace_multiply: None, - nb_inplace_remainder: None, - nb_inplace_power: None, - nb_inplace_lshift: None, - nb_inplace_rshift: None, - nb_inplace_and: None, - nb_inplace_xor: None, - nb_inplace_or: None, - nb_floor_divide: None, - nb_true_divide: None, - nb_inplace_floor_divide: None, - nb_inplace_true_divide: None, - nb_index: None, - $($tail)* - } - } - } - } - - pub const PyNumberMethods_INIT: PyNumberMethods = py_number_methods_init! { - nb_matrix_multiply: None, - nb_inplace_matrix_multiply: None, - }; - #[repr(C)] - #[derive(Copy, Clone)] + #[derive(Clone)] pub struct PySequenceMethods { pub sq_length: Option, pub sq_concat: Option, @@ -390,83 +335,29 @@ mod typeobject { pub sq_inplace_repeat: Option, } - impl Default for PySequenceMethods { - #[inline] - fn default() -> Self { - unsafe { mem::zeroed() } - } - } - pub const PySequenceMethods_INIT: PySequenceMethods = PySequenceMethods { - sq_length: None, - sq_concat: None, - sq_repeat: None, - sq_item: None, - was_sq_slice: ptr::null_mut(), - sq_ass_item: None, - was_sq_ass_slice: ptr::null_mut(), - sq_contains: None, - sq_inplace_concat: None, - sq_inplace_repeat: None, - }; #[repr(C)] - #[derive(Copy, Clone)] + #[derive(Clone, Default)] pub struct PyMappingMethods { pub mp_length: Option, pub mp_subscript: Option, pub mp_ass_subscript: Option, } - impl Default for PyMappingMethods { - #[inline] - fn default() -> Self { - unsafe { mem::zeroed() } - } - } - pub const PyMappingMethods_INIT: PyMappingMethods = PyMappingMethods { - mp_length: None, - mp_subscript: None, - mp_ass_subscript: None, - }; #[repr(C)] - #[derive(Copy, Clone)] + #[derive(Clone, Default)] pub struct PyAsyncMethods { pub am_await: Option, pub am_aiter: Option, pub am_anext: Option, } - impl Default for PyAsyncMethods { - #[inline] - fn default() -> Self { - PyAsyncMethods_INIT - } - } - - pub const PyAsyncMethods_INIT: PyAsyncMethods = PyAsyncMethods { - am_await: None, - am_aiter: None, - am_anext: None, - }; - #[repr(C)] - #[derive(Copy, Clone, Debug)] + #[derive(Clone, Default)] pub struct PyBufferProcs { pub bf_getbuffer: Option, pub bf_releasebuffer: Option, } - impl Default for PyBufferProcs { - #[inline] - fn default() -> Self { - PyBufferProcs_INIT - } - } - - pub const PyBufferProcs_INIT: PyBufferProcs = PyBufferProcs { - bf_getbuffer: None, - bf_releasebuffer: None, - }; - #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct PyTypeObject { @@ -650,7 +541,7 @@ mod typeobject { pub const PyTypeObject_INIT: PyTypeObject = type_object_init!(); #[repr(C)] - #[derive(Copy, Clone)] + #[derive(Clone)] pub struct PyHeapTypeObject { pub ht_type: PyTypeObject, pub as_async: PyAsyncMethods, diff --git a/src/pyclass.rs b/src/pyclass.rs index a614897e101..01923544f27 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -10,7 +10,7 @@ use crate::{class, ffi, PyCell, PyErr, PyNativeType, PyResult, PyTypeInfo, Pytho use std::convert::TryInto; use std::ffi::CString; use std::marker::PhantomData; -use std::os::raw::{c_int, c_uint, c_void}; +use std::os::raw::{c_char, c_int, c_uint, c_void}; use std::{ptr, thread}; #[inline] @@ -108,272 +108,37 @@ pub trait PyClass: type BaseNativeType: PyTypeInfo + PyNativeType; } -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 }); - } -} +/// For collecting slot items. +#[derive(Default)] +pub(crate) struct TypeSlots(Vec); -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), - ); +impl TypeSlots { + fn push(&mut self, slot: c_int, pfunc: *mut c_void) { + self.0.push(ffi::PyType_Slot { slot, pfunc }); + } + pub(crate) fn maybe_push(&mut self, slot: c_int, value: Option<*mut c_void>) { + value.map(|v| self.push(slot, v)); + } } -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 tp_doc() -> PyResult> { + Ok(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 _), + }) } -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 get_type_name(module_name: Option<&str>) -> PyResult<*mut c_char> { + Ok(match module_name { + Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(), + None => CString::new(T::NAME)?.into_raw(), + }) } -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), - ); +fn into_raw(vec: Vec) -> *mut c_void { + Box::into_raw(vec.into_boxed_slice()) as _ } pub(crate) fn create_type_object( @@ -383,138 +148,89 @@ pub(crate) fn create_type_object( where T: PyClass, { - let mut slots = vec![]; - - slots.push(ffi::PyType_Slot { - slot: ffi::Py_tp_base, - pfunc: T::BaseType::type_object_raw(py) as *mut c_void, - }); - - 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); + let mut slots = TypeSlots::default(); - maybe_push_slot( - &mut slots, - ffi::Py_tp_dealloc, - tp_dealloc::().map(|v| v as *mut c_void), - ); + slots.push(ffi::Py_tp_base, T::BaseType::type_object_raw(py) as _); + slots.maybe_push(ffi::Py_tp_doc, tp_doc::()?); + slots.maybe_push(ffi::Py_tp_dealloc, tp_dealloc::().map(|v| v as _)); - 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)); + let (new, call, methods) = py_class_method_defs::(); + slots.maybe_push(ffi::Py_tp_new, new.map(|v| v as _)); + slots.maybe_push(ffi::Py_tp_call, call.map(|v| v as _)); // 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), - ); + slots.push(ffi::Py_tp_methods, into_raw(methods)); } // properties - let mut props = py_class_properties::(); - - if !T::Dict::IS_DUMMY { - props.push(ffi::PyGetSetDef_DICT); - } + let props = py_class_properties::(); 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), - ); - } - - if let Some(basic) = T::basic_methods() { - unsafe { basic.as_ref() }.update_slots(&mut slots); - } - - if let Some(number) = T::number_methods() { - push_numbers_slots(&mut slots, unsafe { number.as_ref() }); + slots.push(ffi::Py_tp_getset, into_raw(props)); } + // basic methods + T::basic_methods().map(|basic| unsafe { basic.as_ref() }.update_slots(&mut slots)); + // number methods + T::number_methods().map(|num| unsafe { num.as_ref() }.update_slots(&mut slots)); // iterator methods - if let Some(iter) = T::iter_methods() { - unsafe { iter.as_ref() }.update_slots(&mut slots); - } - + T::iter_methods().map(|iter| unsafe { iter.as_ref() }.update_slots(&mut slots)); // mapping methods - if let Some(mapping) = T::mapping_methods() { - push_mapping_slots(&mut slots, unsafe { mapping.as_ref() }); - } - + T::mapping_methods().map(|map| unsafe { map.as_ref() }.update_slots(&mut slots)); // sequence methods - if let Some(seq) = T::sequence_methods() { - push_sequence_slots(&mut slots, unsafe { seq.as_ref() }); - } - + T::sequence_methods().map(|seq| unsafe { seq.as_ref() }.update_slots(&mut slots)); // descriptor protocol - if let Some(descr) = T::descr_methods() { - unsafe { descr.as_ref() }.update_slots(&mut slots); - } - + T::descr_methods().map(|descr| unsafe { descr.as_ref() }.update_slots(&mut slots)); // async methods - if let Some(asnc) = T::async_methods() { - push_async_slots(&mut slots, unsafe { asnc.as_ref() }); - } - + T::async_methods().map(|asnc| unsafe { asnc.as_ref() }.update_slots(&mut slots)); // GC support - if let Some(gc) = T::gc_methods() { - unsafe { gc.as_ref() }.update_slots(&mut slots); - } + T::gc_methods().map(|gc| unsafe { gc.as_ref() }.update_slots(&mut slots)); - slots.push(ffi::PyType_Slot { - slot: 0, - pfunc: ptr::null_mut(), - }); + slots.push(0, 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(), - }, + name: get_type_name::(module_name)?, basicsize: std::mem::size_of::() as c_int, itemsize: 0, flags: py_class_flags::(), - slots: slots.as_mut_slice().as_mut_ptr(), + slots: slots.0.as_mut_slice().as_mut_ptr(), }; 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; - } + tp_init_additional::(type_object as _); + Ok(type_object as _) + } +} + +#[cfg(not(Py_LIMITED_API))] +fn tp_init_additional(type_object: *mut ffi::PyTypeObject) { + // 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. + 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; - } + } + // __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; - } + } + // 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) } } +#[cfg(Py_LIMITED_API)] +fn tp_init_additional(type_object: *mut ffi::PyTypeObject) {} + 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 @@ -581,10 +297,14 @@ fn py_class_method_defs() -> ( } } + if !defs.is_empty() { + defs.push(ffi::PyMethodDef_INIT); + } + (new, call, defs) } -fn py_class_properties() -> Vec { +fn py_class_properties() -> Vec { let mut defs = std::collections::HashMap::new(); for def in T::py_methods() { @@ -609,7 +329,11 @@ fn py_class_properties() -> Vec { } } - defs.values().cloned().collect() + let mut props: Vec<_> = defs.values().cloned().collect(); + if !T::Dict::IS_DUMMY { + props.push(ffi::PyGetSetDef_DICT); + } + props } /// This trait is implemented for `#[pyclass]` and handles following two situations: From 1941f4daefa940d5f5426061ba8598e781186031 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 6 Sep 2020 14:02:10 +0900 Subject: [PATCH 04/36] Rename all-apis with unstable-api --- .github/workflows/ci.yml | 2 +- Cargo.toml | 8 ++++---- build.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d4a7685991..2cb2acd06f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,7 +82,7 @@ jobs: - run: rustup set default-host ${{ matrix.platform.rust-target }} - name: Build without default features - run: cargo build --no-default-features --verbose + run: cargo build --no-default-features --features "unstable-api" --verbose - name: Build with default features run: cargo build --features "num-bigint num-complex" --verbose diff --git a/Cargo.toml b/Cargo.toml index 8f903e3a707..c4a437d92b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,11 @@ trybuild = "1.0.23" rustversion = "1.0" [features] -default = ["macros", "all-apis"] +default = ["macros", "unstable-api"] macros = ["ctor", "indoc", "inventory", "paste", "pyo3cls", "unindent"] -# Enable the use of `not(Py_LIMITED_API)` s. -# This is incompatible with abi3(PEP384). See https://www.python.org/dev/peps/pep-0384/ more. -all-apis = [] +# Enable unstable API incompatible with abi3(PEP384). +# See https://www.python.org/dev/peps/pep-0384/ more. +unstable-api = [] # Optimizes PyObject to Vec conversion and so on. nightly = [] diff --git a/build.rs b/build.rs index d537bdef962..be714f3e75a 100644 --- a/build.rs +++ b/build.rs @@ -757,7 +757,7 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result { bail!("Python 2 is not supported"); } - if env::var_os("CARGO_FEATURE_ALL_APIS").is_none() { + if env::var_os("CARGO_FEATURE_UNSTABLE_API").is_none() { println!("cargo:rustc-cfg=Py_LIMITED_API"); } From e0f75f80b1de409d03ba77f8253d328a135c7965 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 6 Sep 2020 17:37:59 +0900 Subject: [PATCH 05/36] Fix missing PyGetSetDef_INIT and Adress clippy warnings --- src/pyclass.rs | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/pyclass.rs b/src/pyclass.rs index 01923544f27..97c2a6b73d8 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -117,7 +117,9 @@ impl TypeSlots { self.0.push(ffi::PyType_Slot { slot, pfunc }); } pub(crate) fn maybe_push(&mut self, slot: c_int, value: Option<*mut c_void>) { - value.map(|v| self.push(slot, v)); + if let Some(v) = value { + self.push(slot, v); + } } } @@ -169,21 +171,37 @@ where } // basic methods - T::basic_methods().map(|basic| unsafe { basic.as_ref() }.update_slots(&mut slots)); + if let Some(basic) = T::basic_methods() { + unsafe { basic.as_ref() }.update_slots(&mut slots); + } // number methods - T::number_methods().map(|num| unsafe { num.as_ref() }.update_slots(&mut slots)); + if let Some(number) = T::number_methods() { + unsafe { number.as_ref() }.update_slots(&mut slots); + } // iterator methods - T::iter_methods().map(|iter| unsafe { iter.as_ref() }.update_slots(&mut slots)); + if let Some(iter) = T::iter_methods() { + unsafe { iter.as_ref() }.update_slots(&mut slots); + } // mapping methods - T::mapping_methods().map(|map| unsafe { map.as_ref() }.update_slots(&mut slots)); + if let Some(mapping) = T::mapping_methods() { + unsafe { mapping.as_ref() }.update_slots(&mut slots); + } // sequence methods - T::sequence_methods().map(|seq| unsafe { seq.as_ref() }.update_slots(&mut slots)); + if let Some(sequence) = T::sequence_methods() { + unsafe { sequence.as_ref() }.update_slots(&mut slots); + } // descriptor protocol - T::descr_methods().map(|descr| unsafe { descr.as_ref() }.update_slots(&mut slots)); + if let Some(descr) = T::descr_methods() { + unsafe { descr.as_ref() }.update_slots(&mut slots); + } // async methods - T::async_methods().map(|asnc| unsafe { asnc.as_ref() }.update_slots(&mut slots)); - // GC support - T::gc_methods().map(|gc| unsafe { gc.as_ref() }.update_slots(&mut slots)); + if let Some(asnc) = T::async_methods() { + unsafe { asnc.as_ref() }.update_slots(&mut slots); + } + // gc methods + if let Some(gc) = T::gc_methods() { + unsafe { gc.as_ref() }.update_slots(&mut slots); + } slots.push(0, ptr::null_mut()); let mut spec = ffi::PyType_Spec { @@ -333,6 +351,9 @@ fn py_class_properties() -> Vec { if !T::Dict::IS_DUMMY { props.push(ffi::PyGetSetDef_DICT); } + if !props.is_empty() { + props.push(ffi::PyGetSetDef_INIT); + } props } From 4cd6d4c772b9ba9dd72c0697a8af76203f828a48 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 6 Sep 2020 17:35:05 -0400 Subject: [PATCH 06/36] Fixed a few compilation errors on the abi3 branch --- src/class/number.rs | 3 +++ src/class/sequence.rs | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/class/number.rs b/src/class/number.rs index 8843587a49a..60f7d17eb6a 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -5,6 +5,8 @@ use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; use crate::{ffi, FromPyObject, PyClass, PyObject}; +#[cfg(Py_LIMITED_API)] +use std::os::raw::c_void; #[cfg(Py_LIMITED_API)] #[derive(Clone)] @@ -26,6 +28,7 @@ pub struct PyNumberMethods { pub nb_xor: Option, pub nb_or: Option, pub nb_int: Option, + #[allow(dead_code)] pub nb_reserved: *mut c_void, pub nb_float: Option, pub nb_inplace_add: Option, diff --git a/src/class/sequence.rs b/src/class/sequence.rs index 3d2e29f201a..fbd4ba71bf4 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -8,6 +8,8 @@ use crate::conversion::{FromPyObject, IntoPy}; use crate::err::PyErr; use crate::{exceptions, ffi, PyAny, PyCell, PyClass, PyObject}; use std::os::raw::c_int; +#[cfg(Py_LIMITED_API)] +use std::os::raw::c_void; #[cfg(Py_LIMITED_API)] #[derive(Clone)] @@ -16,8 +18,10 @@ pub struct PySequenceMethods { pub sq_concat: Option, pub sq_repeat: Option, pub sq_item: Option, + #[allow(dead_code)] pub was_sq_slice: *mut c_void, pub sq_ass_item: Option, + #[allow(dead_code)] pub was_sq_ass_slice: *mut c_void, pub sq_contains: Option, pub sq_inplace_concat: Option, From 80e2497c4cf5fffcf30d59f43544e7cbe579b24b Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 7 Sep 2020 07:48:03 -0400 Subject: [PATCH 07/36] Complete the process of disabling buffers with Py_LIMITED_API --- src/class/proto_methods.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/class/proto_methods.rs b/src/class/proto_methods.rs index d28d1522cca..e9018789cfa 100644 --- a/src/class/proto_methods.rs +++ b/src/class/proto_methods.rs @@ -1,7 +1,9 @@ +#[cfg(not(Py_LIMITED_API))] +use crate::class::buffer::PyBufferProcs; use crate::class::{ - basic::PyObjectMethods, buffer::PyBufferProcs, descr::PyDescrMethods, gc::PyGCMethods, - iter::PyIterMethods, mapping::PyMappingMethods, number::PyNumberMethods, - pyasync::PyAsyncMethods, sequence::PySequenceMethods, + basic::PyObjectMethods, descr::PyDescrMethods, gc::PyGCMethods, iter::PyIterMethods, + mapping::PyMappingMethods, number::PyNumberMethods, pyasync::PyAsyncMethods, + sequence::PySequenceMethods, }; use std::{ ptr::{self, NonNull}, @@ -17,6 +19,7 @@ pub trait PyProtoMethods { fn basic_methods() -> Option> { None } + #[cfg(not(Py_LIMITED_API))] fn buffer_methods() -> Option> { None } @@ -53,6 +56,7 @@ impl PyProtoMethods for T { fn basic_methods() -> Option> { NonNull::new(Self::registry().basic_methods.load(Ordering::Relaxed)) } + #[cfg(not(Py_LIMITED_API))] fn buffer_methods() -> Option> { NonNull::new(Self::registry().buffer_methods.load(Ordering::Relaxed)) } @@ -85,6 +89,7 @@ pub struct PyProtoRegistry { /// Basic protocols. basic_methods: AtomicPtr, /// Buffer protocols. + #[cfg(not(Py_LIMITED_API))] buffer_methods: AtomicPtr, /// Descr pProtocols. descr_methods: AtomicPtr, @@ -105,6 +110,7 @@ impl PyProtoRegistry { PyProtoRegistry { async_methods: AtomicPtr::new(ptr::null_mut()), basic_methods: AtomicPtr::new(ptr::null_mut()), + #[cfg(not(Py_LIMITED_API))] buffer_methods: AtomicPtr::new(ptr::null_mut()), descr_methods: AtomicPtr::new(ptr::null_mut()), gc_methods: AtomicPtr::new(ptr::null_mut()), @@ -122,6 +128,7 @@ impl PyProtoRegistry { self.basic_methods .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) } + #[cfg(not(Py_LIMITED_API))] pub fn set_buffer_methods(&self, methods: PyBufferProcs) { self.buffer_methods .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) From 71a7b1a6b6cb58b1677829db55c85b9f8f7bb3c2 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 7 Sep 2020 12:25:08 -0400 Subject: [PATCH 08/36] Properly mark a funtion as limited API only --- src/ffi/ceval.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ffi/ceval.rs b/src/ffi/ceval.rs index 20b4d0b361b..60fcc5deed7 100644 --- a/src/ffi/ceval.rs +++ b/src/ffi/ceval.rs @@ -1,4 +1,4 @@ -#[cfg(Py_3_6)] +#[cfg(all(Py_3_6, not(Py_LIMITED_API)))] use crate::ffi::code::FreeFunc; use crate::ffi::object::PyObject; use crate::ffi::pystate::PyThreadState; @@ -69,7 +69,7 @@ extern "C" { arg1: *mut crate::ffi::PyFrameObject, exc: c_int, ) -> *mut PyObject; - #[cfg(Py_3_6)] + #[cfg(all(Py_3_6, not(Py_LIMITED_API)))] pub fn _PyEval_RequestCodeExtraIndex(func: FreeFunc) -> c_int; pub fn PyEval_EvalFrameEx(f: *mut crate::ffi::PyFrameObject, exc: c_int) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyEval_SaveThread")] From 0709a02310851f8a661cd3bcd474004e551c14f6 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 8 Sep 2020 08:18:52 -0400 Subject: [PATCH 09/36] Fill tp_dict on types in an abi3-friendly way I think this might technically be backwards incompatible if you had a custom metaclass with fancy behavior, but pyo3 doesn't seem to have any native support for those. --- src/type_object.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/type_object.rs b/src/type_object.rs index 487e738036e..6e8e79004fb 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -202,10 +202,7 @@ impl LazyStaticType { // Now we hold the GIL and we can assume it won't be released until we // return from the function. let result = self.tp_dict_filled.get_or_init(py, move || { - let tp_dict = unsafe { (*type_object).tp_dict }; - let result = initialize_tp_dict(py, tp_dict, items); - // See discussion on #982 for why we need this. - unsafe { ffi::PyType_Modified(type_object) }; + let result = initialize_tp_dict(py, type_object as *mut ffi::PyObject, items); // Initialization successfully complete, can clear the thread list. // (No further calls to get_or_init() will try to init, on any thread.) @@ -224,7 +221,7 @@ impl LazyStaticType { fn initialize_tp_dict( py: Python, - tp_dict: *mut ffi::PyObject, + type_object: *mut ffi::PyObject, items: Vec<(&'static str, PyObject)>, ) -> PyResult<()> { use std::ffi::CString; @@ -233,7 +230,7 @@ fn initialize_tp_dict( // the POV of other threads. for (key, val) in items { let ret = unsafe { - ffi::PyDict_SetItemString(tp_dict, CString::new(key)?.as_ptr(), val.into_ptr()) + ffi::PyObject_SetAttrString(type_object, CString::new(key)?.as_ptr(), val.into_ptr()) }; if ret < 0 { return Err(PyErr::fetch(py)); From 117f60bed0f906fed843455b3dd77aa03303fe5f Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 8 Sep 2020 08:59:22 -0400 Subject: [PATCH 10/36] Make PyType::name abi3 compatible The implementation is more complex, because there's no equivalent to tp_name in the limited API --- guide/src/trait_bounds.md | 8 ++++---- pyo3-derive-backend/src/from_pyobject.rs | 2 +- src/err.rs | 11 +++++++---- src/exceptions.rs | 6 +++--- src/types/typeobject.rs | 6 ++---- tests/test_methods.rs | 4 ++-- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/guide/src/trait_bounds.md b/guide/src/trait_bounds.md index e3aac085733..8dde99e0726 100644 --- a/guide/src/trait_bounds.md +++ b/guide/src/trait_bounds.md @@ -408,8 +408,8 @@ impl Model for UserModel { .call_method("get_results", (), None) .unwrap(); - if py_result.get_type().name() != "list" { - panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name()); + if py_result.get_type().name().unwrap() != "list" { + panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name().unwrap()); } py_result.extract() }) @@ -536,8 +536,8 @@ impl Model for UserModel { .call_method("get_results", (), None) .unwrap(); - if py_result.get_type().name() != "list" { - panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name()); + if py_result.get_type().name().unwrap() != "list" { + panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name().unwrap()); } py_result.extract() }) diff --git a/pyo3-derive-backend/src/from_pyobject.rs b/pyo3-derive-backend/src/from_pyobject.rs index f7688e2b3e4..c6dbd31ae47 100644 --- a/pyo3-derive-backend/src/from_pyobject.rs +++ b/pyo3-derive-backend/src/from_pyobject.rs @@ -72,7 +72,7 @@ impl<'a> Enum<'a> { }; quote!( #(#var_extracts)* - let type_name = obj.get_type().name(); + let type_name = obj.get_type().name()?; let from = obj .repr() .map(|s| format!("{} ({})", s.to_string_lossy(), type_name)) diff --git a/src/err.rs b/src/err.rs index e1ade1eeb3a..5fc10fef452 100644 --- a/src/err.rs +++ b/src/err.rs @@ -478,10 +478,13 @@ impl<'a> std::fmt::Display for PyDowncastError<'a> { write!( f, "Can't convert {} to {}", - self.from - .repr() - .map(|s| s.to_string_lossy()) - .unwrap_or_else(|_| self.from.get_type().name()), + self.from.repr().map(|s| s.to_string_lossy()).or_else(|_| { + self.from + .get_type() + .name() + .map_err(|_| std::fmt::Error) + .map(|s| s.into()) + })?, self.to ) } diff --git a/src/exceptions.rs b/src/exceptions.rs index be73c5d4bcc..ca0412cc42f 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -38,8 +38,8 @@ macro_rules! impl_exception_boilerplate { impl std::fmt::Debug for $name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let type_name = self.get_type().name(); - f.debug_struct(&*type_name) + let type_name = self.get_type().name().map_err(|_| std::fmt::Error)?; + f.debug_struct(type_name) // TODO: print out actual fields! .finish() } @@ -47,7 +47,7 @@ macro_rules! impl_exception_boilerplate { impl std::fmt::Display for $name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let type_name = self.get_type().name(); + let type_name = self.get_type().name().map_err(|_| std::fmt::Error)?; write!(f, "{}", type_name)?; if let Ok(s) = self.str() { write!(f, ": {}", &s.to_string_lossy()) diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 7db9d6c347c..30037a5c3ee 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -6,8 +6,6 @@ use crate::err::{PyErr, PyResult}; use crate::instance::PyNativeType; use crate::type_object::PyTypeObject; use crate::{ffi, AsPyPointer, PyAny, Python}; -use std::borrow::Cow; -use std::ffi::CStr; /// Represents a reference to a Python `type object`. #[repr(transparent)] @@ -39,8 +37,8 @@ impl PyType { } /// Gets the name of the `PyType`. - pub fn name(&self) -> Cow { - unsafe { CStr::from_ptr((*self.as_type_ptr()).tp_name).to_string_lossy() } + pub fn name(&self) -> PyResult<&str> { + self.getattr("__qualname__")?.extract() } /// Checks whether `self` is subclass of type `T`. diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 40db57aeadb..b6b8a7f8d95 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -76,7 +76,7 @@ impl ClassMethod { #[classmethod] /// Test class method. fn method(cls: &PyType) -> PyResult { - Ok(format!("{}.method()!", cls.name())) + Ok(format!("{}.method()!", cls.name()?)) } } @@ -104,7 +104,7 @@ struct ClassMethodWithArgs {} impl ClassMethodWithArgs { #[classmethod] fn method(cls: &PyType, input: &PyString) -> PyResult { - Ok(format!("{}.method({})", cls.name(), input)) + Ok(format!("{}.method({})", cls.name()?, input)) } } From d6c9435aefe368e79b49aeaf00ee7148d7333101 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 8 Sep 2020 15:19:32 -0400 Subject: [PATCH 11/36] Implement set iterators in terms of limited API --- src/types/set.rs | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/types/set.rs b/src/types/set.rs index 09417534b7e..d631c0809c2 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -2,6 +2,8 @@ // use crate::err::{self, PyErr, PyResult}; +#[cfg(Py_LIMITED_API)] +use crate::types::PyIterator; use crate::{ ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyNativeType, PyObject, Python, ToBorrowedObject, ToPyObject, @@ -110,21 +112,48 @@ impl PySet { /// Returns an iterator of values in this set. /// /// Note that it can be unsafe to use when the set might be changed by other code. - #[cfg(not(Py_LIMITED_API))] pub fn iter(&self) -> PySetIterator { + PySetIterator::new(self) + } +} + +#[cfg(Py_LIMITED_API)] +pub struct PySetIterator<'py> { + it: PyIterator<'py>, +} + +#[cfg(Py_LIMITED_API)] +impl PySetIterator<'_> { + fn new(set: &PyAny) -> PySetIterator { PySetIterator { - set: self.as_ref(), - pos: 0, + it: PyIterator::from_object(set.py(), set).unwrap(), } } } +#[cfg(Py_LIMITED_API)] +impl<'py> Iterator for PySetIterator<'py> { + type Item = &'py super::PyAny; + + #[inline] + fn next(&mut self) -> Option { + self.it.next().map(|p| p.unwrap()) + } +} + #[cfg(not(Py_LIMITED_API))] pub struct PySetIterator<'py> { set: &'py super::PyAny, pos: isize, } +#[cfg(not(Py_LIMITED_API))] +impl PySetIterator<'_> { + fn new(set: &PyAny) -> PySetIterator { + PySetIterator { set, pos: 0 } + } +} + #[cfg(not(Py_LIMITED_API))] impl<'py> Iterator for PySetIterator<'py> { type Item = &'py super::PyAny; @@ -145,7 +174,6 @@ impl<'py> Iterator for PySetIterator<'py> { } } -#[cfg(not(Py_LIMITED_API))] impl<'a> std::iter::IntoIterator for &'a PySet { type Item = &'a PyAny; type IntoIter = PySetIterator<'a>; @@ -281,22 +309,17 @@ impl PyFrozenSet { /// Returns an iterator of values in this frozen set. /// /// Note that it can be unsafe to use when the set might be changed by other code. - #[cfg(not(Py_LIMITED_API))] pub fn iter(&self) -> PySetIterator { - self.into_iter() + PySetIterator::new(self.as_ref()) } } -#[cfg(not(Py_LIMITED_API))] impl<'a> std::iter::IntoIterator for &'a PyFrozenSet { type Item = &'a PyAny; type IntoIter = PySetIterator<'a>; fn into_iter(self) -> Self::IntoIter { - PySetIterator { - set: self.as_ref(), - pos: 0, - } + self.iter() } } From 4d5c208c9ecfa28e578c2bf16d65aa61e57fb320 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 10 Sep 2020 17:05:24 -0400 Subject: [PATCH 12/36] fixes --- src/err/mod.rs | 7 ++++++- src/pyclass.rs | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/err/mod.rs b/src/err/mod.rs index 724ce0caecf..384fb259789 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -494,7 +494,12 @@ impl<'a> std::fmt::Display for PyDowncastError<'a> { self.from .repr() .map(|s| s.to_string_lossy()) - .unwrap_or_else(|_| self.from.get_type().name()), + .or_else(|_| self + .from + .get_type() + .name() + .map_err(|_| std::fmt::Error) + .map(|s| s.into()))?, self.to ) } diff --git a/src/pyclass.rs b/src/pyclass.rs index 97c2a6b73d8..85c1c60a62c 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -214,7 +214,7 @@ where let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) }; if type_object.is_null() { - PyErr::fetch(py).into() + Err(PyErr::fetch(py)) } else { tp_init_additional::(type_object as _); Ok(type_object as _) @@ -275,7 +275,7 @@ fn fallback_new() -> Option { _kwds: *mut ffi::PyObject, ) -> *mut ffi::PyObject { crate::callback_body!(py, { - Err::<(), _>(crate::exceptions::PyTypeError::py_err( + Err::<(), _>(crate::exceptions::PyTypeError::new_err( "No constructor defined", )) }) From 3cb0b112d2e9116607fa025983f1029748726005 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 12 Sep 2020 09:47:42 -0400 Subject: [PATCH 13/36] Update src/err/mod.rs Co-authored-by: Yuji Kanagawa --- src/err/mod.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/err/mod.rs b/src/err/mod.rs index 384fb259789..38af8ba989f 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -494,12 +494,8 @@ impl<'a> std::fmt::Display for PyDowncastError<'a> { self.from .repr() .map(|s| s.to_string_lossy()) - .or_else(|_| self - .from - .get_type() - .name() - .map_err(|_| std::fmt::Error) - .map(|s| s.into()))?, + .or_else(|_| self.from.get_type().name().map(|s| s.into())) + .map_err(|_| std::fmt::Error)?, self.to ) } From d0c2ebf0e17edde404657899c3ca9e5da7cda51b Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 13 Sep 2020 17:12:21 -0400 Subject: [PATCH 14/36] Remove finalizer code that was never reachable and switch field access to PyType_GetSlot --- src/freelist.rs | 15 +++++++-------- src/pyclass.rs | 50 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/freelist.rs b/src/freelist.rs index 2f78e900957..4e2bdf2692e 100644 --- a/src/freelist.rs +++ b/src/freelist.rs @@ -2,7 +2,7 @@ //! Free allocation list -use crate::pyclass::{tp_free_fallback, PyClassAlloc}; +use crate::pyclass::{get_type_free, tp_free_fallback, PyClassAlloc}; use crate::type_object::{PyLayout, PyTypeInfo}; use crate::{ffi, AsPyPointer, FromPyPointer, PyAny, Python}; use std::mem; @@ -85,15 +85,14 @@ where unsafe fn dealloc(py: Python, self_: *mut Self::Layout) { (*self_).py_drop(py); let obj = PyAny::from_borrowed_ptr_or_panic(py, self_ as _); - if Self::is_exact_instance(obj) && ffi::PyObject_CallFinalizerFromDealloc(obj.as_ptr()) < 0 - { - // tp_finalize resurrected. - return; - } if let Some(obj) = ::get_free_list().insert(obj.as_ptr()) { - match (*ffi::Py_TYPE(obj)).tp_free { - Some(free) => free(obj as *mut c_void), + match get_type_free(ffi::Py_TYPE(obj)) { + Some(free) => { + let ty = ffi::Py_TYPE(obj); + free(obj as *mut c_void); + ffi::Py_DECREF(ty as *mut ffi::PyObject); + } None => tp_free_fallback(obj), } } diff --git a/src/pyclass.rs b/src/pyclass.rs index 85c1c60a62c..5f02e3881de 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -10,9 +10,31 @@ use crate::{class, ffi, PyCell, PyErr, PyNativeType, PyResult, PyTypeInfo, Pytho use std::convert::TryInto; use std::ffi::CString; use std::marker::PhantomData; +#[cfg(not(PyPy))] +use std::mem; use std::os::raw::{c_char, c_int, c_uint, c_void}; use std::{ptr, thread}; +#[cfg(PyPy)] +unsafe fn get_type_alloc(tp: *mut ffi::PyTypeObject) -> Option { + (*tp).tp_alloc +} + +#[cfg(not(PyPy))] +unsafe fn get_type_alloc(tp: *mut ffi::PyTypeObject) -> Option { + mem::transmute(ffi::PyType_GetSlot(tp, ffi::Py_tp_alloc)) +} + +#[cfg(PyPy)] +pub(crate) unsafe fn get_type_free(tp: *mut ffi::PyTypeObject) -> Option { + (*tp).tp_free +} + +#[cfg(not(PyPy))] +pub(crate) unsafe fn get_type_free(tp: *mut ffi::PyTypeObject) -> Option { + mem::transmute(ffi::PyType_GetSlot(tp, ffi::Py_tp_free)) +} + #[inline] pub(crate) unsafe fn default_new( py: Python, @@ -20,12 +42,19 @@ pub(crate) unsafe fn default_new( ) -> *mut ffi::PyObject { // if the class derives native types(e.g., PyDict), call special new if T::FLAGS & type_flags::EXTENDED != 0 && T::BaseLayout::IS_NATIVE_TYPE { - let base_tp = T::BaseType::type_object_raw(py); - if let Some(base_new) = (*base_tp).tp_new { - return base_new(subtype, ptr::null_mut(), ptr::null_mut()); + #[cfg(not(Py_LIMITED_API))] + { + let base_tp = T::BaseType::type_object_raw(py); + if let Some(base_new) = (*base_tp).tp_new { + return base_new(subtype, ptr::null_mut(), ptr::null_mut()); + } + } + #[cfg(Py_LIMITED_API)] + { + unreachable!("Subclassing native types isn't support in limited API mode"); } } - let alloc = (*subtype).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc); + let alloc = get_type_alloc(subtype).unwrap_or(ffi::PyType_GenericAlloc); alloc(subtype, 0) as _ } @@ -46,14 +75,13 @@ pub trait PyClassAlloc: PyTypeInfo + Sized { unsafe fn dealloc(py: Python, self_: *mut Self::Layout) { (*self_).py_drop(py); let obj = PyAny::from_borrowed_ptr_or_panic(py, self_ as _); - if Self::is_exact_instance(obj) && ffi::PyObject_CallFinalizerFromDealloc(obj.as_ptr()) < 0 - { - // tp_finalize resurrected. - return; - } - match (*ffi::Py_TYPE(obj.as_ptr())).tp_free { - Some(free) => free(obj.as_ptr() as *mut c_void), + match get_type_free(ffi::Py_TYPE(obj.as_ptr())) { + Some(free) => { + let ty = ffi::Py_TYPE(obj.as_ptr()); + free(obj.as_ptr() as *mut c_void); + ffi::Py_DECREF(ty as *mut ffi::PyObject); + } None => tp_free_fallback(obj.as_ptr()), } } From 1b2d26794a218f5f09dc0061996617cd083d5663 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 14 Sep 2020 22:11:00 -0400 Subject: [PATCH 15/36] Make unicode handling abi3 friendly --- src/types/string.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/types/string.rs b/src/types/string.rs index 8e313edc848..2bb5e3cd290 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -44,6 +44,7 @@ impl PyString { /// (containing unpaired surrogates). #[inline] pub fn to_str(&self) -> PyResult<&str> { + #[cfg(not(Py_LIMITED_API))] unsafe { let mut size: ffi::Py_ssize_t = 0; let data = ffi::PyUnicode_AsUTF8AndSize(self.as_ptr(), &mut size) as *const u8; @@ -54,6 +55,16 @@ impl PyString { Ok(std::str::from_utf8_unchecked(slice)) } } + #[cfg(Py_LIMITED_API)] + unsafe { + let data = ffi::PyUnicode_AsUTF8String(self.as_ptr()); + if data.is_null() { + Err(PyErr::fetch(self.py())) + } else { + let bytes = self.py().from_owned_ptr::(data); + Ok(std::str::from_utf8_unchecked(bytes.as_bytes())) + } + } } /// Converts the `PyString` into a Rust string. From 870914da90f121088b546ce2d2e1883afe1083a4 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 15 Sep 2020 07:50:24 -0400 Subject: [PATCH 16/36] Make check warning clean in limited API mode --- src/pycell.rs | 12 +++++++++--- src/pyclass.rs | 4 +++- src/types/complex.rs | 4 ++-- src/types/tuple.rs | 3 +-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/pycell.rs b/src/pycell.rs index a880944630f..da6a73f998d 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -8,9 +8,9 @@ use crate::type_object::{PyBorrowFlagLayout, PyLayout, PySizedLayout, PyTypeInfo use crate::types::PyAny; use crate::{ffi, IntoPy, PyErr, PyNativeType, PyObject, PyResult, Python}; use std::cell::{Cell, UnsafeCell}; +use std::fmt; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; -use std::{fmt, mem}; /// Base layout of PyCell. /// This is necessary for sharing BorrowFlag between parents and children. @@ -170,20 +170,26 @@ pub struct PyCell { impl PyCell { /// Get the offset of the dictionary from the start of the struct in bytes. + #[cfg(not(Py_LIMITED_API))] pub(crate) fn dict_offset() -> Option { if T::Dict::IS_DUMMY { None } else { - Some(mem::size_of::() - mem::size_of::() - mem::size_of::()) + Some( + std::mem::size_of::() + - std::mem::size_of::() + - std::mem::size_of::(), + ) } } /// Get the offset of the weakref list from the start of the struct in bytes. + #[cfg(not(Py_LIMITED_API))] pub(crate) fn weakref_offset() -> Option { if T::WeakRef::IS_DUMMY { None } else { - Some(mem::size_of::() - mem::size_of::()) + Some(std::mem::size_of::() - std::mem::size_of::()) } } } diff --git a/src/pyclass.rs b/src/pyclass.rs index 5f02e3881de..0b5f8db4108 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -51,6 +51,8 @@ pub(crate) unsafe fn default_new( } #[cfg(Py_LIMITED_API)] { + // Silence unused parameter warning. + let _ = py; unreachable!("Subclassing native types isn't support in limited API mode"); } } @@ -275,7 +277,7 @@ fn tp_init_additional(type_object: *mut ffi::PyTypeObject) { } #[cfg(Py_LIMITED_API)] -fn tp_init_additional(type_object: *mut ffi::PyTypeObject) {} +fn tp_init_additional(_type_object: *mut ffi::PyTypeObject) {} fn py_class_flags() -> c_uint { let mut flags = if T::gc_methods().is_some() || T::FLAGS & type_flags::GC != 0 { diff --git a/src/types/complex.rs b/src/types/complex.rs index c3a38bb467c..69d92c17c72 100644 --- a/src/types/complex.rs +++ b/src/types/complex.rs @@ -1,7 +1,7 @@ -#[cfg(not(PyPy))] +#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] use crate::instance::PyNativeType; use crate::{ffi, AsPyPointer, PyAny, Python}; -#[cfg(not(PyPy))] +#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] use std::ops::*; use std::os::raw::c_double; diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 77e7c903912..9dbe9395164 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -5,7 +5,6 @@ use crate::{ exceptions, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyAny, PyErr, PyNativeType, PyObject, PyResult, PyTryFrom, Python, ToPyObject, }; -use std::slice; /// Represents a Python `tuple` object. /// @@ -87,7 +86,7 @@ impl PyTuple { // and because tuples are immutable. unsafe { let ptr = self.as_ptr() as *mut ffi::PyTupleObject; - let slice = slice::from_raw_parts((*ptr).ob_item.as_ptr(), self.len()); + let slice = std::slice::from_raw_parts((*ptr).ob_item.as_ptr(), self.len()); &*(slice as *const [*mut ffi::PyObject] as *const [&PyAny]) } } From ba1056006a513dd337263bd61060b94107844fd3 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 15 Sep 2020 08:21:49 -0400 Subject: [PATCH 17/36] Get all the tests building, everythign except doctests passes! --- guide/src/class.md | 2 +- pyo3-derive-backend/src/from_pyobject.rs | 3 +-- src/types/floatob.rs | 4 +++- tests/test_buffer_protocol.rs | 2 ++ tests/test_dunder.rs | 4 ++++ tests/test_gc.rs | 14 +++++++++++++- tests/test_inheritance.rs | 4 ++++ tests/test_pyfunction.rs | 3 +++ tests/test_unsendable_dict.rs | 2 ++ tests/test_various.rs | 1 + 10 files changed, 34 insertions(+), 5 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index 1fd1009d488..9a2e48d4a70 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -271,7 +271,7 @@ You can also inherit native types such as `PyDict`, if they implement However, because of some technical problems, we don't currently provide safe upcasting methods for types that inherit native types. Even in such cases, you can unsafely get a base class by raw pointer conversion. -```rust +```rust,no_run # use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::{AsPyPointer, PyNativeType}; diff --git a/pyo3-derive-backend/src/from_pyobject.rs b/pyo3-derive-backend/src/from_pyobject.rs index d4d097112ee..656532bea1e 100644 --- a/pyo3-derive-backend/src/from_pyobject.rs +++ b/pyo3-derive-backend/src/from_pyobject.rs @@ -249,7 +249,7 @@ impl<'a> Container<'a> { let self_ty = &self.path; let mut fields: Punctuated = Punctuated::new(); for i in 0..len { - fields.push(quote!(slice[#i].extract()?)); + fields.push(quote!(s.get_item(#i).extract()?)); } let msg = if self.is_enum_variant { quote!(format!( @@ -265,7 +265,6 @@ impl<'a> Container<'a> { if s.len() != #len { return Err(::pyo3::exceptions::PyValueError::new_err(#msg)) } - let slice = s.as_slice(); Ok(#self_ty(#fields)) ) } diff --git a/src/types/floatob.rs b/src/types/floatob.rs index 0b0b5341310..5b773237069 100644 --- a/src/types/floatob.rs +++ b/src/types/floatob.rs @@ -83,7 +83,9 @@ impl<'source> FromPyObject<'source> for f32 { mod test { #[cfg(not(Py_LIMITED_API))] use crate::ffi::PyFloat_AS_DOUBLE; - use crate::{AsPyPointer, Python, ToPyObject}; + #[cfg(not(Py_LIMITED_API))] + use crate::AsPyPointer; + use crate::{Python, ToPyObject}; macro_rules! num_to_py_object_and_back ( ($func_name:ident, $t1:ty, $t2:ty) => ( diff --git a/tests/test_buffer_protocol.rs b/tests/test_buffer_protocol.rs index aca7ef1ca4e..c40e751b1e8 100644 --- a/tests/test_buffer_protocol.rs +++ b/tests/test_buffer_protocol.rs @@ -1,3 +1,5 @@ +#![cfg(not(Py_LIMITED_API))] + use pyo3::buffer::PyBuffer; use pyo3::class::PyBufferProtocol; use pyo3::exceptions::PyBufferError; diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs index 7b4ea91592d..e229e448181 100644 --- a/tests/test_dunder.rs +++ b/tests/test_dunder.rs @@ -457,6 +457,7 @@ fn test_cls_impl() { struct DunderDictSupport {} #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn dunder_dict_support() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -472,6 +473,7 @@ fn dunder_dict_support() { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn access_dunder_dict() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -493,6 +495,7 @@ struct InheritDict { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn inherited_dict() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -511,6 +514,7 @@ fn inherited_dict() { struct WeakRefDunderDictSupport {} #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn weakref_dunder_dict_support() { let gil = Python::acquire_gil(); let py = gil.python(); diff --git a/tests/test_gc.rs b/tests/test_gc.rs index b7aced4eecb..eb982887b93 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -150,6 +150,7 @@ fn gc_integration2() { struct WeakRefSupport {} #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn weakref_support() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -168,6 +169,7 @@ struct InheritWeakRef { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn inherited_weakref() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -269,6 +271,16 @@ impl PyGCProtocol for TraversableClass { } } +#[cfg(PyPy)] +unsafe fn get_type_traverse(tp: *mut pyo3::ffi::PyTypeObject) -> Option { + (*tp).tp_traverse +} + +#[cfg(not(PyPy))] +unsafe fn get_type_traverse(tp: *mut pyo3::ffi::PyTypeObject) -> Option { + std::mem::transmute(pyo3::ffi::PyType_GetSlot(tp, pyo3::ffi::Py_tp_traverse)) +} + #[test] fn gc_during_borrow() { let gil = Python::acquire_gil(); @@ -285,7 +297,7 @@ fn gc_during_borrow() { // get the traverse function let ty = TraversableClass::type_object(py).as_type_ptr(); - let traverse = (*ty).tp_traverse.unwrap(); + let traverse = get_type_traverse(ty).unwrap(); // create an object and check that traversing it works normally // when it's not borrowed diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index a2aad91a244..04fa732621f 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -168,7 +168,9 @@ impl SetWithName { } } +// Subclassing builtin types is not allowed in the LIMITED API. #[test] +#[cfg_attr(Py_LIMITED_API, should_panic)] fn inherit_set() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -195,7 +197,9 @@ impl DictWithName { } } +// Subclassing builtin types is not allowed in the LIMITED API. #[test] +#[cfg_attr(Py_LIMITED_API, should_panic)] fn inherit_dict() { let gil = Python::acquire_gil(); let py = gil.python(); diff --git a/tests/test_pyfunction.rs b/tests/test_pyfunction.rs index 89b4dbd01e3..f24f726682f 100644 --- a/tests/test_pyfunction.rs +++ b/tests/test_pyfunction.rs @@ -1,3 +1,4 @@ +#[cfg(not(Py_LIMITED_API))] use pyo3::buffer::PyBuffer; use pyo3::prelude::*; use pyo3::types::{PyCFunction, PyFunction}; @@ -23,6 +24,7 @@ fn test_optional_bool() { py_assert!(py, f, "f(None) == 'None'"); } +#[cfg(not(Py_LIMITED_API))] #[pyfunction] fn buffer_inplace_add(py: Python, x: PyBuffer, y: PyBuffer) { let x = x.as_mut_slice(py).unwrap(); @@ -33,6 +35,7 @@ fn buffer_inplace_add(py: Python, x: PyBuffer, y: PyBuffer) { } } +#[cfg(not(Py_LIMITED_API))] #[test] fn test_buffer_add() { let gil = Python::acquire_gil(); diff --git a/tests/test_unsendable_dict.rs b/tests/test_unsendable_dict.rs index 194b5888f83..86c974135d8 100644 --- a/tests/test_unsendable_dict.rs +++ b/tests/test_unsendable_dict.rs @@ -13,6 +13,7 @@ impl UnsendableDictClass { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn test_unsendable_dict() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -32,6 +33,7 @@ impl UnsendableDictClassWithWeakRef { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn test_unsendable_dict_with_weakref() { let gil = Python::acquire_gil(); let py = gil.python(); diff --git a/tests/test_various.rs b/tests/test_various.rs index 8a80df7d251..25c5e7bcb97 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -149,6 +149,7 @@ fn add_module(py: Python, module: &PyModule) -> PyResult<()> { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn test_pickle() { let gil = Python::acquire_gil(); let py = gil.python(); From c07e1aa40ace0c41c015d0dd10a7802377a922e5 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 20 Sep 2020 00:02:27 +0900 Subject: [PATCH 18/36] Use abi3 feature, instead of unstable-api --- .github/workflows/ci.yml | 2 +- Cargo.toml | 7 +++---- build.rs | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7698ce6753..032a1a2b815 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: - run: rustup set default-host ${{ matrix.platform.rust-target }} - name: Build without default features - run: cargo build --no-default-features --features "unstable-api" --verbose + run: cargo build --no-default-features --verbose - name: Build with default features run: cargo build --features "num-bigint num-complex" --verbose diff --git a/Cargo.toml b/Cargo.toml index 93e8491ac39..fa8c3c5eb8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,10 @@ trybuild = "1.0.23" rustversion = "1.0" [features] -default = ["macros", "unstable-api"] +default = ["macros"] macros = ["ctor", "indoc", "inventory", "paste", "pyo3cls", "unindent"] -# Enable unstable API incompatible with abi3(PEP384). -# See https://www.python.org/dev/peps/pep-0384/ more. -unstable-api = [] +# Use only limited API. See https://www.python.org/dev/peps/pep-0384/ for more. +abi3 = [] # Optimizes PyObject to Vec conversion and so on. nightly = [] diff --git a/build.rs b/build.rs index be714f3e75a..04ea5a477e3 100644 --- a/build.rs +++ b/build.rs @@ -757,7 +757,7 @@ fn configure(interpreter_config: &InterpreterConfig) -> Result { bail!("Python 2 is not supported"); } - if env::var_os("CARGO_FEATURE_UNSTABLE_API").is_none() { + if env::var_os("CARGO_FEATURE_ABI3").is_some() { println!("cargo:rustc-cfg=Py_LIMITED_API"); } From 2a85c17f110ef1b65a79c8a24b674052b04a303f Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 19 Sep 2020 11:27:06 -0400 Subject: [PATCH 19/36] Run abi3 tests in CI --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 032a1a2b815..1fffcefcce2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,10 @@ jobs: - if: matrix.python-version != 'pypy3' name: Test run: cargo test --features "num-bigint num-complex" + # Run tests again, but in abi3 mode + - if: matrix.python-version != 'pypy3' + name: Test + run: cargo test --no-default-features --features "abi3,macros" - name: Test proc-macro code run: cargo test --manifest-path=pyo3-derive-backend/Cargo.toml From 7644d67ad86fa434acc9b002ed80714c94f5180a Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 20 Sep 2020 00:42:55 +0900 Subject: [PATCH 20/36] Inhibit subclassing native types with ABI3 set --- src/types/mod.rs | 2 + tests/test_compile_error.rs | 3 + tests/test_inheritance.rs | 102 ++++++++++---------- tests/ui/abi3_nativetype_inheritance.rs | 7 ++ tests/ui/abi3_nativetype_inheritance.stderr | 13 +++ 5 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 tests/ui/abi3_nativetype_inheritance.rs create mode 100644 tests/ui/abi3_nativetype_inheritance.stderr diff --git a/src/types/mod.rs b/src/types/mod.rs index b3915a8f33f..b083d17fd74 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -103,6 +103,8 @@ macro_rules! pyobject_native_type_core { #[macro_export] macro_rules! pyobject_native_type_sized { ($name: ty, $layout: path $(,$type_param: ident)*) => { + // To prevent inheriting native types with ABI3 + #[cfg(not(Py_LIMITED_API))] impl $crate::type_object::PySizedLayout<$name> for $layout {} impl<'a, $($type_param,)*> $crate::derive_utils::PyBaseTypeUtils for $name { type Dict = $crate::pyclass_slots::PyClassDummySlot; diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 5d02b8e8991..3e01e1a0988 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -11,6 +11,9 @@ fn test_compile_errors() { t.compile_fail("tests/ui/reject_generics.rs"); t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs"); + #[cfg(Py_LIMITED_API)] + t.compile_fail("tests/ui/abi3_nativetype_inheritance.rs"); + tests_rust_1_43(&t); tests_rust_1_46(&t); diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 04fa732621f..da9ab298e0d 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -3,7 +3,6 @@ use pyo3::py_run; use pyo3::types::IntoPyDict; -use pyo3::types::{PyDict, PySet}; mod common; #[pyclass(subclass)] @@ -153,60 +152,63 @@ except Exception as e: ); } -#[pyclass(extends=PySet)] -#[derive(Debug)] -struct SetWithName { - #[pyo3(get(name))] - _name: &'static str, -} +// Subclassing builtin types is not allowed in the LIMITED API. +#[cfg(not(Py_LIMITED_API))] +mod inheriting_native_type { + use super::*; + use pyo3::types::{PyDict, PySet}; + + #[pyclass(extends=PySet)] + #[derive(Debug)] + struct SetWithName { + #[pyo3(get(name))] + _name: &'static str, + } -#[pymethods] -impl SetWithName { - #[new] - fn new() -> Self { - SetWithName { _name: "Hello :)" } + #[pymethods] + impl SetWithName { + #[new] + fn new() -> Self { + SetWithName { _name: "Hello :)" } + } } -} -// Subclassing builtin types is not allowed in the LIMITED API. -#[test] -#[cfg_attr(Py_LIMITED_API, should_panic)] -fn inherit_set() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let set_sub = pyo3::PyCell::new(py, SetWithName::new()).unwrap(); - py_run!( - py, - set_sub, - r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub._name == "Hello :)""# - ); -} + #[test] + fn inherit_set() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set_sub = pyo3::PyCell::new(py, SetWithName::new()).unwrap(); + py_run!( + py, + set_sub, + r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub._name == "Hello :)""# + ); + } -#[pyclass(extends=PyDict)] -#[derive(Debug)] -struct DictWithName { - #[pyo3(get(name))] - _name: &'static str, -} + #[pyclass(extends=PyDict)] + #[derive(Debug)] + struct DictWithName { + #[pyo3(get(name))] + _name: &'static str, + } -#[pymethods] -impl DictWithName { - #[new] - fn new() -> Self { - DictWithName { _name: "Hello :)" } + #[pymethods] + impl DictWithName { + #[new] + fn new() -> Self { + DictWithName { _name: "Hello :)" } + } } -} -// Subclassing builtin types is not allowed in the LIMITED API. -#[test] -#[cfg_attr(Py_LIMITED_API, should_panic)] -fn inherit_dict() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let dict_sub = pyo3::PyCell::new(py, DictWithName::new()).unwrap(); - py_run!( - py, - dict_sub, - r#"dict_sub[0] = 1; assert dict_sub[0] == 1; assert dict_sub._name == "Hello :)""# - ); + #[test] + fn inherit_dict() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let dict_sub = pyo3::PyCell::new(py, DictWithName::new()).unwrap(); + py_run!( + py, + dict_sub, + r#"dict_sub[0] = 1; assert dict_sub[0] == 1; assert dict_sub._name == "Hello :)""# + ); + } } diff --git a/tests/ui/abi3_nativetype_inheritance.rs b/tests/ui/abi3_nativetype_inheritance.rs new file mode 100644 index 00000000000..3795017dfc9 --- /dev/null +++ b/tests/ui/abi3_nativetype_inheritance.rs @@ -0,0 +1,7 @@ +use pyo3::prelude::*; +use pyo3::types::PyDict; + +#[pyclass(extends=PyDict)] +struct TestClass {} + +fn main() {} diff --git a/tests/ui/abi3_nativetype_inheritance.stderr b/tests/ui/abi3_nativetype_inheritance.stderr new file mode 100644 index 00000000000..922740ac3cf --- /dev/null +++ b/tests/ui/abi3_nativetype_inheritance.stderr @@ -0,0 +1,13 @@ +error[E0277]: the trait bound `pyo3::ffi::dictobject::PyDictObject: pyo3::type_object::PySizedLayout` is not satisfied + --> $DIR/abi3_nativetype_inheritance.rs:4:1 + | +4 | #[pyclass(extends=PyDict)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `pyo3::type_object::PySizedLayout` is not implemented for `pyo3::ffi::dictobject::PyDictObject` + | + ::: $WORKSPACE/src/type_object.rs:96:22 + | +96 | type BaseLayout: PySizedLayout; + | ----------------------------- required by this bound in `pyo3::type_object::PyTypeInfo` + | + = note: required because of the requirements on the impl of `pyo3::type_object::PySizedLayout` for `pyo3::pycell::PyCellBase` + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) From 9d8559158b708b1121f643876b663ac1035f40a4 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 19 Sep 2020 11:23:00 -0400 Subject: [PATCH 21/36] Hack __text_signature__ back to working with abi3 --- src/pyclass.rs | 16 ++++++++++++++++ tests/test_text_signature.rs | 14 +++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/pyclass.rs b/src/pyclass.rs index 0b5f8db4108..aa21881b030 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -256,6 +256,22 @@ fn tp_init_additional(type_object: *mut ffi::PyTypeObject) { // 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. + + // Running this causes PyPy to segfault. + #[cfg(not(PyPy))] + if T::DESCRIPTION != "\0" { + unsafe { + // Until CPython 3.10, tp_doc was treated specially for heap-types, + // and it removed the text_signature value from it. We go in after + // the fact and replace tp_doc with something that _does_ include + // the text_signature value! + ffi::PyObject_Free((*type_object).tp_doc as _); + let data = ffi::PyObject_Malloc(T::DESCRIPTION.len()); + data.copy_from(T::DESCRIPTION.as_ptr() as _, T::DESCRIPTION.len()); + (*type_object).tp_doc = data as _; + } + } + if let Some(buffer) = T::buffer_methods() { unsafe { (*(*type_object).tp_as_buffer).bf_getbuffer = buffer.as_ref().bf_getbuffer; diff --git a/tests/test_text_signature.rs b/tests/test_text_signature.rs index 929bb2eba5e..feca3f3e3de 100644 --- a/tests/test_text_signature.rs +++ b/tests/test_text_signature.rs @@ -31,10 +31,8 @@ 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] +#[cfg_attr(Py_LIMITED_API, ignore)] fn class_with_docs_and_signature() { /// docs line1 #[pyclass] @@ -69,10 +67,8 @@ 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] +#[cfg_attr(Py_LIMITED_API, ignore)] fn class_with_signature() { #[pyclass] #[text_signature = "(a, b=None, *, c=42)"] @@ -92,7 +88,11 @@ fn class_with_signature() { let py = gil.python(); let typeobj = py.get_type::(); - py_assert!(py, typeobj, "typeobj.__doc__ is None"); + py_assert!( + py, + typeobj, + "typeobj.__doc__ is None or typeobj.__doc__ == ''" + ); py_assert!( py, typeobj, From 869a5e2f20fbb2c2f29085d3aaf5e71afaf9efa3 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 20 Sep 2020 15:33:55 +0900 Subject: [PATCH 22/36] Fix an abi3 ui test for the latest Rustc --- tests/test_compile_error.rs | 5 ++--- tests/ui/abi3_nativetype_inheritance.rs | 1 + tests/ui/abi3_nativetype_inheritance.stderr | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 3e01e1a0988..80e3c681277 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -11,9 +11,6 @@ fn test_compile_errors() { t.compile_fail("tests/ui/reject_generics.rs"); t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs"); - #[cfg(Py_LIMITED_API)] - t.compile_fail("tests/ui/abi3_nativetype_inheritance.rs"); - tests_rust_1_43(&t); tests_rust_1_46(&t); @@ -29,6 +26,8 @@ fn test_compile_errors() { t.compile_fail("tests/ui/invalid_pymethod_receiver.rs"); t.compile_fail("tests/ui/invalid_result_conversion.rs"); t.compile_fail("tests/ui/missing_clone.rs"); + #[cfg(Py_LIMITED_API)] + t.compile_fail("tests/ui/abi3_nativetype_inheritance.rs"); } #[rustversion::before(1.46)] fn tests_rust_1_46(_t: &trybuild::TestCases) {} diff --git a/tests/ui/abi3_nativetype_inheritance.rs b/tests/ui/abi3_nativetype_inheritance.rs index 3795017dfc9..6816d2d54f9 100644 --- a/tests/ui/abi3_nativetype_inheritance.rs +++ b/tests/ui/abi3_nativetype_inheritance.rs @@ -1,3 +1,4 @@ +//! With abi3, we cannot inherite native types. use pyo3::prelude::*; use pyo3::types::PyDict; diff --git a/tests/ui/abi3_nativetype_inheritance.stderr b/tests/ui/abi3_nativetype_inheritance.stderr index 922740ac3cf..9787700b4a3 100644 --- a/tests/ui/abi3_nativetype_inheritance.stderr +++ b/tests/ui/abi3_nativetype_inheritance.stderr @@ -1,13 +1,13 @@ -error[E0277]: the trait bound `pyo3::ffi::dictobject::PyDictObject: pyo3::type_object::PySizedLayout` is not satisfied - --> $DIR/abi3_nativetype_inheritance.rs:4:1 +error[E0277]: the trait bound `pyo3::ffi::PyDictObject: pyo3::type_object::PySizedLayout` is not satisfied + --> $DIR/abi3_nativetype_inheritance.rs:5:1 | -4 | #[pyclass(extends=PyDict)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `pyo3::type_object::PySizedLayout` is not implemented for `pyo3::ffi::dictobject::PyDictObject` +5 | #[pyclass(extends=PyDict)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `pyo3::type_object::PySizedLayout` is not implemented for `pyo3::ffi::PyDictObject` | ::: $WORKSPACE/src/type_object.rs:96:22 | 96 | type BaseLayout: PySizedLayout; - | ----------------------------- required by this bound in `pyo3::type_object::PyTypeInfo` + | ----------------------------- required by this bound in `pyo3::PyTypeInfo` | - = note: required because of the requirements on the impl of `pyo3::type_object::PySizedLayout` for `pyo3::pycell::PyCellBase` + = note: required because of the requirements on the impl of `pyo3::type_object::PySizedLayout` for `pyo3::pycell::PyCellBase` = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) From 1985578bb7b2f11b5e0637195b0232e6d92c265d Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 20 Sep 2020 16:20:08 +0900 Subject: [PATCH 23/36] Don't compile extends=PyDict test in class.md with abi3 --- guide/src/class.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/guide/src/class.md b/guide/src/class.md index 9a2e48d4a70..4e6ce160e7f 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -271,7 +271,9 @@ You can also inherit native types such as `PyDict`, if they implement However, because of some technical problems, we don't currently provide safe upcasting methods for types that inherit native types. Even in such cases, you can unsafely get a base class by raw pointer conversion. -```rust,no_run +```rust +# #[cfg(Py_LIMITED_API)] fn main() {} +# #[cfg(not(Py_LIMITED_API))] fn main() { # use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::{AsPyPointer, PyNativeType}; @@ -300,6 +302,7 @@ impl DictWithCounter { # let py = gil.python(); # let cnt = pyo3::PyCell::new(py, DictWithCounter::new()).unwrap(); # pyo3::py_run!(py, cnt, "cnt.set('abc', 10); assert cnt['abc'] == 10") +# } ``` If `SubClass` does not provide a baseclass initialization, the compilation fails. From d8c8c1760e1d7ce4f69ef9e11c35ddcc1402b7f3 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 23 Sep 2020 08:31:24 -0400 Subject: [PATCH 24/36] Link python3.lib instead of python3x.lib on Windows in abi3 mode --- build.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.rs b/build.rs index 04ea5a477e3..a1572181eac 100644 --- a/build.rs +++ b/build.rs @@ -555,6 +555,11 @@ fn run_python_script(interpreter: &Path, script: &str) -> Result { fn get_library_link_name(version: &PythonVersion, ld_version: &str) -> String { if cfg!(target_os = "windows") { + // Mirrors the behavior in CPython's `PC/pyconfig.h`. + if env::var_os("CARGO_FEATURE_ABI3").is_some() { + return "python3".to_string(); + } + let minor_or_empty_string = match version.minor { Some(minor) => format!("{}", minor), None => String::new(), From e615ce8c055676ef17df1d5149d30f14b4fb1960 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 20 Sep 2020 09:36:20 -0400 Subject: [PATCH 25/36] Start documenting abi3 support --- guide/src/building_and_distribution.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/guide/src/building_and_distribution.md b/guide/src/building_and_distribution.md index f0b76f0d9ca..57464b493be 100644 --- a/guide/src/building_and_distribution.md +++ b/guide/src/building_and_distribution.md @@ -38,6 +38,23 @@ On Linux/macOS you might have to change `LD_LIBRARY_PATH` to include libpython, There are two ways to distribute your module as a Python package: The old, [setuptools-rust](https://github.com/PyO3/setuptools-rust), and the new, [maturin](https://github.com/pyo3/maturin). setuptools-rust needs several configuration files (`setup.py`, `MANIFEST.in`, `build-wheels.sh`, etc.). maturin doesn't need any configuration files, however it does not support some functionality of setuptools such as package data ([pyo3/maturin#258](https://github.com/PyO3/maturin/issues/258)) and requires a rigid project structure, while setuptools-rust allows (and sometimes requires) configuration with python code. +## `Py_LIMITED_API`/`abi3` + +By default, Python extension modules can only be used with the same Python version they were compiled against -- if you build an extension module with Python 3.5, you can't import it using Python 3.8. [PEP 384](https://www.python.org/dev/peps/pep-0384/) introduced the idea of the limited Python API, which would have a stable ABI enabling extension modules built with it to be used against multiple Python versions. This is also known as `abi3`. + +There are three steps involved in making use of `abi3` when building Python packages as wheels: + +1. Enable the `abi3` feature in `pyo3`. This ensures `pyo3` only calls Python C-API functions which are part of the stable API, and on Windows also ensures that the project links against the correct shared object (no special behavior is required on other platforms): + +```toml +[dependencies] +pyo3 = { version = "...", features = ["abi3"]} +``` + +2. Ensure that the built shared objects are correctly marked as `abi3`. This is accomplished by telling your build system that you're using the limited API. There is currently a [PR for `setuptools-rust` for this](https://github.com/PyO3/setuptools-rust/pull/82). + +3. Ensure that the `.whl` is correctly marked as `abi3`. For projects using `setuptools`, this is accomplished by passing `--py-limited-api=cp3x` (where `x` is the minimum Python version supported by the wheel, e.g. `--py-limited-api=cp35` for Python 3.5) to `setup.py bdist_wheel`. + ## Cross Compiling Cross compiling PyO3 modules is relatively straightforward and requires a few pieces of software: From c22dd6c54c065f734f42501cd9aa73479cf311ea Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 23 Sep 2020 08:50:40 -0400 Subject: [PATCH 26/36] Remove symbols not available in abi3 --- src/ffi/descrobject.rs | 7 ++++--- src/ffi/funcobject.rs | 2 ++ src/ffi/marshal.rs | 1 + src/ffi/object.rs | 1 + src/lib.rs | 1 + src/marshal.rs | 2 ++ src/types/function.rs | 1 + tests/test_pyfunction.rs | 25 +++++++++++++++++++------ 8 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/ffi/descrobject.rs b/src/ffi/descrobject.rs index 0651eee59b9..6cdb91a4626 100644 --- a/src/ffi/descrobject.rs +++ b/src/ffi/descrobject.rs @@ -1,6 +1,6 @@ use crate::ffi::methodobject::PyMethodDef; use crate::ffi::object::{PyObject, PyTypeObject}; -#[cfg(not(PyPy))] +#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] use crate::ffi::object::{PyObject_GenericGetDict, PyObject_GenericSetDict}; use crate::ffi::structmember::PyMemberDef; use std::os::raw::{c_char, c_int, c_void}; @@ -28,11 +28,12 @@ pub const PyGetSetDef_INIT: PyGetSetDef = PyGetSetDef { closure: ptr::null_mut(), }; -#[cfg(PyPy)] +#[cfg(any(PyPy, Py_LIMITED_API))] pub const PyGetSetDef_DICT: PyGetSetDef = PyGetSetDef_INIT; // PyPy doesn't export neither PyObject_GenericGetDict/PyObject_GenericSetDict -#[cfg(not(PyPy))] +// Py_LIMITED_API exposes PyObject_GenericSetDict but not Get. +#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] pub const PyGetSetDef_DICT: PyGetSetDef = PyGetSetDef { name: "__dict__\0".as_ptr() as *mut c_char, get: Some(PyObject_GenericGetDict), diff --git a/src/ffi/funcobject.rs b/src/ffi/funcobject.rs index 10b15345870..e57a87a7fe1 100644 --- a/src/ffi/funcobject.rs +++ b/src/ffi/funcobject.rs @@ -4,10 +4,12 @@ use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE}; #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { + #[cfg(not(Py_LIMITED_API))] #[cfg_attr(PyPy, link_name = "PyPyFunction_Type")] pub static mut PyFunction_Type: PyTypeObject; } +#[cfg(not(Py_LIMITED_API))] #[inline] pub unsafe fn PyFunction_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &mut PyFunction_Type) as c_int diff --git a/src/ffi/marshal.rs b/src/ffi/marshal.rs index 00237908832..01494ce6e51 100644 --- a/src/ffi/marshal.rs +++ b/src/ffi/marshal.rs @@ -1,6 +1,7 @@ use super::PyObject; use std::os::raw::{c_char, c_int}; +#[cfg(not(Py_LIMITED_API))] extern "C" { #[cfg_attr(PyPy, link_name = "PyPyMarshal_WriteObjectToString")] pub fn PyMarshal_WriteObjectToString(object: *mut PyObject, version: c_int) -> *mut PyObject; diff --git a/src/ffi/object.rs b/src/ffi/object.rs index 80c9278d3fd..10255639bf8 100644 --- a/src/ffi/object.rs +++ b/src/ffi/object.rs @@ -716,6 +716,7 @@ extern "C" { arg2: *mut PyObject, arg3: *mut PyObject, ) -> c_int; + #[cfg(not(Py_LIMITED_API))] pub fn PyObject_GenericGetDict(arg1: *mut PyObject, arg2: *mut c_void) -> *mut PyObject; pub fn PyObject_GenericSetDict( arg1: *mut PyObject, diff --git a/src/lib.rs b/src/lib.rs index 7826fcfbd5e..c5e5e7603d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,6 +184,7 @@ mod gil; mod instance; #[macro_use] mod internal_tricks; +#[cfg(not(Py_LIMITED_API))] pub mod marshal; pub mod once_cell; pub mod panic; diff --git a/src/marshal.rs b/src/marshal.rs index d0c7350b912..f8bcb6a4ce7 100644 --- a/src/marshal.rs +++ b/src/marshal.rs @@ -1,3 +1,5 @@ +#![cfg(not(Py_LIMITED_API))] + use crate::ffi; use crate::types::{PyAny, PyBytes}; use crate::{AsPyPointer, FromPyPointer, PyResult, Python}; diff --git a/src/types/function.rs b/src/types/function.rs index d215d6c4a43..5acb3ddd115 100644 --- a/src/types/function.rs +++ b/src/types/function.rs @@ -87,4 +87,5 @@ impl PyCFunction { #[repr(transparent)] pub struct PyFunction(PyAny); +#[cfg(not(Py_LIMITED_API))] pyobject_native_var_type!(PyFunction, ffi::PyFunction_Type, ffi::PyFunction_Check); diff --git a/tests/test_pyfunction.rs b/tests/test_pyfunction.rs index f24f726682f..8a1e52d21d0 100644 --- a/tests/test_pyfunction.rs +++ b/tests/test_pyfunction.rs @@ -1,7 +1,9 @@ #[cfg(not(Py_LIMITED_API))] use pyo3::buffer::PyBuffer; use pyo3::prelude::*; -use pyo3::types::{PyCFunction, PyFunction}; +use pyo3::types::PyCFunction; +#[cfg(not(Py_LIMITED_API))] +use pyo3::types::PyFunction; use pyo3::{raw_pycfunction, wrap_pyfunction}; mod common; @@ -67,6 +69,7 @@ assert a, array.array("i", [2, 4, 6, 8]) ); } +#[cfg(not(Py_LIMITED_API))] #[pyfunction] fn function_with_pyfunction_arg(fun: &PyFunction) -> PyResult<&PyAny> { fun.call((), None) @@ -81,21 +84,31 @@ fn function_with_pycfunction_arg(fun: &PyCFunction) -> PyResult<&PyAny> { fn test_functions_with_function_args() { let gil = Python::acquire_gil(); let py = gil.python(); - let py_func_arg = wrap_pyfunction!(function_with_pyfunction_arg)(py).unwrap(); let py_cfunc_arg = wrap_pyfunction!(function_with_pycfunction_arg)(py).unwrap(); let bool_to_string = wrap_pyfunction!(optional_bool)(py).unwrap(); pyo3::py_run!( py, - py_func_arg py_cfunc_arg bool_to_string, r#" - def foo(): return "bar" - assert py_func_arg(foo) == "bar" assert py_cfunc_arg(bool_to_string) == "Some(true)" "# - ) + ); + + #[cfg(not(Py_LIMITED_API))] + { + let py_func_arg = wrap_pyfunction!(function_with_pyfunction_arg)(py).unwrap(); + + pyo3::py_run!( + py, + py_func_arg, + r#" + def foo(): return "bar" + assert py_func_arg(foo) == "bar" + "# + ); + } } #[test] From 398369f2991970be28b45a54cc3d4c39398e069d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 10 Oct 2020 10:13:05 -0400 Subject: [PATCH 27/36] Fixed warning --- src/pyclass.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyclass.rs b/src/pyclass.rs index d61af118ec5..c3fe0231258 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -6,7 +6,7 @@ use crate::derive_utils::PyBaseTypeUtils; 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 crate::{ffi, PyCell, PyErr, PyNativeType, PyResult, PyTypeInfo, Python}; use std::convert::TryInto; use std::ffi::CString; use std::marker::PhantomData; From 877667a1c60dafe2b9c2db20604a9578e7fe897e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 10 Oct 2020 21:12:47 -0400 Subject: [PATCH 28/36] Improved documentation --- CHANGELOG.md | 8 ++++++++ guide/src/migration.md | 12 ++++++++++++ src/marshal.rs | 2 ++ src/pyclass.rs | 2 +- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52a4d7b23c1..ae7a2b32cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ PyO3 versions, please see the [migration guide](https://pyo3.rs/master/migration The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [Unreleased] +### Added +- Add support for building for CPython limited API. This required a few minor changes to runtime behaviour of of pyo3 `#[pyclass]` types. See the migration guide for full details. [#1152](https://github.com/PyO3/pyo3/pull/1152) + +### Changed +- Change return type `PyType::name()` from `Cow` to `PyResult<&str>`. [#1152](https://github.com/PyO3/pyo3/pull/1152) +- `#[pyclass(subclass)]` is now required for subclassing from Rust (was previously just required for subclassing from Python). [#1152](https://github.com/PyO3/pyo3/pull/1152) + ## [Unreleased] ### Added - Add support for keyword-only arguments without default values in `#[pyfunction]`. [#1209](https://github.com/PyO3/pyo3/pull/1209) diff --git a/guide/src/migration.md b/guide/src/migration.md index 535bc97a32a..dea3319ef8c 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -3,6 +3,18 @@ This guide can help you upgrade code through breaking changes from one PyO3 version to the next. For a detailed list of all changes, see the [CHANGELOG](changelog.md). +## from 0.12.* to 0.13 + +### Runtime changes to support the CPython limited API + +In PyO3 `0.13` support was added for compiling against the CPython limited API. This had a number of implications for _all_ PyO3 users, described here. + +The largest of these is that all types created from PyO3 are what CPython calls "heap" types. The specific implications of this are: + +- If you wish to subclass one of these types _from Rust_ you must mark it `#[pyclass(subclass)]`, as you would if you wished to allow subclassing it from Python code. +- Type objects are now mutable - Python code can set attributes on them. +- `__module__` on types without `#[pyclass(module="mymodule")]` no longer returns `builtins`, it now raises `AttributeError`. + ## from 0.11.* to 0.12 ### `PyErr` has been reworked diff --git a/src/marshal.rs b/src/marshal.rs index f8bcb6a4ce7..342274189c8 100644 --- a/src/marshal.rs +++ b/src/marshal.rs @@ -1,4 +1,6 @@ #![cfg(not(Py_LIMITED_API))] +//! Support for the Python `marshal` format. Not supported in limited API +//! builds. use crate::ffi; use crate::types::{PyAny, PyBytes}; diff --git a/src/pyclass.rs b/src/pyclass.rs index c3fe0231258..e7f68cd865c 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -258,7 +258,7 @@ fn tp_init_additional(type_object: *mut ffi::PyTypeObject) { // except for that it does and we have tests. // Running this causes PyPy to segfault. - #[cfg(not(PyPy))] + #[cfg(all(not(PyPy), not(Py_3_10)))] if T::DESCRIPTION != "\0" { unsafe { // Until CPython 3.10, tp_doc was treated specially for heap-types, From aabad7cf7f3acd2cd7088dc9ded34b20fa9e9ec8 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 11 Oct 2020 19:51:27 -0400 Subject: [PATCH 29/36] Assorted updates to the abi3 branch from review --- Cargo.toml | 3 ++- examples/rustapi_module/tests/test_datetime.py | 2 +- guide/src/class.md | 2 +- tests/test_text_signature.rs | 4 ++-- tests/ui/abi3_nativetype_inheritance.rs | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 230bd52a3b0..97395ac22b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,8 @@ rustversion = "1.0" [features] default = ["macros"] macros = ["ctor", "indoc", "inventory", "paste", "pyo3cls", "unindent"] -# Use only limited API. See https://www.python.org/dev/peps/pep-0384/ for more. +# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for +# more. abi3 = [] # Optimizes PyObject to Vec conversion and so on. diff --git a/examples/rustapi_module/tests/test_datetime.py b/examples/rustapi_module/tests/test_datetime.py index e44b509081e..6a820fa67a4 100644 --- a/examples/rustapi_module/tests/test_datetime.py +++ b/examples/rustapi_module/tests/test_datetime.py @@ -328,5 +328,5 @@ def test_tz_class_introspection(): tzi = rdt.TzClass() assert tzi.__class__ == rdt.TzClass - # PyPy generate for some reason. + # PyPy generates 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 d4db1106f8b..ecdcc76f919 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -266,7 +266,7 @@ impl SubSubClass { ``` You can also inherit native types such as `PyDict`, if they implement -[`PySizedLayout`](https://docs.rs/pyo3/latest/pyo3/type_object/trait.PySizedLayout.html). +[`PySizedLayout`](https://docs.rs/pyo3/latest/pyo3/type_object/trait.PySizedLayout.html). However, this is not supported when building for the Python limited API (aka the `abi3` feature of PyO3). However, because of some technical problems, we don't currently provide safe upcasting methods for types that inherit native types. Even in such cases, you can unsafely get a base class by raw pointer conversion. diff --git a/tests/test_text_signature.rs b/tests/test_text_signature.rs index feca3f3e3de..3c9abdba83e 100644 --- a/tests/test_text_signature.rs +++ b/tests/test_text_signature.rs @@ -32,7 +32,7 @@ fn class_with_docs() { } #[test] -#[cfg_attr(Py_LIMITED_API, ignore)] +#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)] fn class_with_docs_and_signature() { /// docs line1 #[pyclass] @@ -68,7 +68,7 @@ fn class_with_docs_and_signature() { } #[test] -#[cfg_attr(Py_LIMITED_API, ignore)] +#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)] fn class_with_signature() { #[pyclass] #[text_signature = "(a, b=None, *, c=42)"] diff --git a/tests/ui/abi3_nativetype_inheritance.rs b/tests/ui/abi3_nativetype_inheritance.rs index 6816d2d54f9..80faff1b739 100644 --- a/tests/ui/abi3_nativetype_inheritance.rs +++ b/tests/ui/abi3_nativetype_inheritance.rs @@ -1,4 +1,4 @@ -//! With abi3, we cannot inherite native types. +//! With abi3, we cannot inherit native types. use pyo3::prelude::*; use pyo3::types::PyDict; From 50603791856166f0c92fe090022063c18f0f9806 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 12 Oct 2020 18:11:38 -0400 Subject: [PATCH 30/36] Fix changelog --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc395b2d271..c05b6f6fe8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] -### Fixed -- Fix support for Rust versions 1.39 to 1.44, broken by an incorrect internal update to paste 1.0 which was done in PyO3 0.12.2. [#1234](https://github.com/PyO3/pyo3/pull/1234) - -## [0.12.2] - 2020-10-12 ### Added - Add support for building for CPython limited API. This required a few minor changes to runtime behaviour of of pyo3 `#[pyclass]` types. See the migration guide for full details. [#1152](https://github.com/PyO3/pyo3/pull/1152) @@ -18,6 +14,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `#[pyclass(subclass)]` is now required for subclassing from Rust (was previously just required for subclassing from Python). [#1152](https://github.com/PyO3/pyo3/pull/1152) ## [Unreleased] +### Fixed +- Fix support for Rust versions 1.39 to 1.44, broken by an incorrect internal update to paste 1.0 which was done in PyO3 0.12.2. [#1234](https://github.com/PyO3/pyo3/pull/1234) + +## [0.12.2] - 2020-10-12 ### Added - Add support for keyword-only arguments without default values in `#[pyfunction]`. [#1209](https://github.com/PyO3/pyo3/pull/1209) - Add `Python::check_signals()` as a safe a wrapper for `PyErr_CheckSignals()`. [#1214](https://github.com/PyO3/pyo3/pull/1214) From 2923b4dff14469e6e74fe9703c258433feb3f4c3 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 12 Oct 2020 18:19:50 -0400 Subject: [PATCH 31/36] Fix for MSRV --- src/pyclass.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/pyclass.rs b/src/pyclass.rs index e7f68cd865c..c13f717592a 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -259,16 +259,18 @@ fn tp_init_additional(type_object: *mut ffi::PyTypeObject) { // Running this causes PyPy to segfault. #[cfg(all(not(PyPy), not(Py_3_10)))] - if T::DESCRIPTION != "\0" { - unsafe { - // Until CPython 3.10, tp_doc was treated specially for heap-types, - // and it removed the text_signature value from it. We go in after - // the fact and replace tp_doc with something that _does_ include - // the text_signature value! - ffi::PyObject_Free((*type_object).tp_doc as _); - let data = ffi::PyObject_Malloc(T::DESCRIPTION.len()); - data.copy_from(T::DESCRIPTION.as_ptr() as _, T::DESCRIPTION.len()); - (*type_object).tp_doc = data as _; + { + if T::DESCRIPTION != "\0" { + unsafe { + // Until CPython 3.10, tp_doc was treated specially for + // heap-types, and it removed the text_signature value from it. + // We go in after the fact and replace tp_doc with something + // that _does_ include the text_signature value! + ffi::PyObject_Free((*type_object).tp_doc as _); + let data = ffi::PyObject_Malloc(T::DESCRIPTION.len()); + data.copy_from(T::DESCRIPTION.as_ptr() as _, T::DESCRIPTION.len()); + (*type_object).tp_doc = data as _; + } } } From 265db337c2c5047c06eb0dd9b7a4ef55a1950028 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 18 Oct 2020 11:06:21 -0400 Subject: [PATCH 32/36] Fixes for PyIterator --- CHANGELOG.md | 1 + src/ffi/objectabstract.rs | 5 +++++ src/types/iterator.rs | 14 ++++++++++---- src/types/set.rs | 4 ++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dd5203f688..0c4f4d7ea9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added - Add support for building for CPython limited API. This required a few minor changes to runtime behaviour of of pyo3 `#[pyclass]` types. See the migration guide for full details. [#1152](https://github.com/PyO3/pyo3/pull/1152) - Add argument names to `TypeError` messages generated by pymethod wrappers. [#1212](https://github.com/PyO3/pyo3/pull/1212) diff --git a/src/ffi/objectabstract.rs b/src/ffi/objectabstract.rs index 76154c33dea..bead5ebf326 100644 --- a/src/ffi/objectabstract.rs +++ b/src/ffi/objectabstract.rs @@ -143,6 +143,11 @@ pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int { }) as c_int } +#[cfg(all(Py_LIMITED_API, Py_3_8))] +extern "C" { + pub fn PyIter_Check(obj: *mut PyObject) -> c_int; +} + extern "C" { #[cfg_attr(PyPy, link_name = "PyPyIter_Next")] pub fn PyIter_Next(arg1: *mut PyObject) -> *mut PyObject; diff --git a/src/types/iterator.rs b/src/types/iterator.rs index cc798ebfd6a..ea0f2dc9321 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -2,9 +2,9 @@ // // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython -use crate::{ - ffi, AsPyPointer, PyAny, PyDowncastError, PyErr, PyNativeType, PyResult, PyTryFrom, Python, -}; +use crate::{ffi, AsPyPointer, PyAny, PyErr, PyNativeType, PyResult, Python}; +#[cfg(any(not(Py_LIMITED_API), Py_3_8))] +use crate::{PyDowncastError, PyTryFrom}; /// A Python iterator object. /// @@ -28,6 +28,7 @@ use crate::{ #[repr(transparent)] pub struct PyIterator(PyAny); pyobject_native_type_named!(PyIterator); +#[cfg(any(not(Py_LIMITED_API), Py_3_8))] pyobject_native_type_extract!(PyIterator); impl PyIterator { @@ -67,6 +68,8 @@ impl<'p> Iterator for &'p PyIterator { } } +// PyIter_Check does not exist in the limited API until 3.8 +#[cfg(any(not(Py_LIMITED_API), Py_3_8))] impl<'v> PyTryFrom<'v> for PyIterator { fn try_from>(value: V) -> Result<&'v PyIterator, PyDowncastError<'v>> { let value = value.into(); @@ -96,7 +99,9 @@ mod tests { use crate::exceptions::PyTypeError; use crate::gil::GILPool; use crate::types::{PyDict, PyList}; - use crate::{Py, PyAny, PyTryFrom, Python, ToPyObject}; + #[cfg(any(not(Py_LIMITED_API), Py_3_8))] + use crate::{Py, PyAny, PyTryFrom}; + use crate::{Python, ToPyObject}; use indoc::indoc; #[test] @@ -200,6 +205,7 @@ mod tests { } #[test] + #[cfg(any(not(Py_LIMITED_API), Py_3_8))] fn iterator_try_from() { let gil_guard = Python::acquire_gil(); let py = gil_guard.python(); diff --git a/src/types/set.rs b/src/types/set.rs index d631c0809c2..5593490afd8 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -118,8 +118,8 @@ impl PySet { } #[cfg(Py_LIMITED_API)] -pub struct PySetIterator<'py> { - it: PyIterator<'py>, +pub struct PySetIterator<'p> { + it: &'p PyIterator, } #[cfg(Py_LIMITED_API)] From eb8ff1581b8a84743c01295acd61272eafa4b93c Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sat, 17 Oct 2020 16:59:56 +0900 Subject: [PATCH 33/36] Renew PyProtoMethods for new ABI3-based type construction --- src/class/basic.rs | 137 +++--- src/class/descr.rs | 36 +- src/class/gc.rs | 44 +- src/class/iter.rs | 33 +- src/class/macros.rs | 58 +-- src/class/mapping.rs | 85 ++-- src/class/number.rs | 908 ++++++++++++++++++++----------------- src/class/proto_methods.rs | 175 ++----- src/class/pyasync.rs | 49 +- src/class/sequence.rs | 142 +++--- src/pyclass.rs | 54 +-- 11 files changed, 850 insertions(+), 871 deletions(-) diff --git a/src/class/basic.rs b/src/class/basic.rs index fbcfe5a9eb0..9223b8e7f80 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -133,94 +133,109 @@ pub trait PyObjectRichcmpProtocol<'p>: PyObjectProtocol<'p> { type Result: IntoPyCallbackOutput; } -/// All FFI functions for basic protocols. -#[derive(Default)] -pub struct PyObjectMethods { - pub tp_str: Option, - pub tp_repr: Option, - pub tp_hash: Option, - pub tp_getattro: Option, - pub tp_richcompare: Option, - pub tp_setattro: Option, - pub nb_bool: Option, -} - +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyObjectMethods { - // Set functions used by `#[pyproto]`. - pub fn set_str(&mut self) +pub trait PyBasicSlots { + fn get_str() -> ffi::PyType_Slot where - T: for<'p> PyObjectStrProtocol<'p>, + Self: for<'p> PyObjectStrProtocol<'p>, { - self.tp_str = py_unary_func!(PyObjectStrProtocol, T::__str__); + ffi::PyType_Slot { + slot: ffi::Py_tp_str, + pfunc: py_unary_func!(PyObjectStrProtocol, Self::__str__) as _, + } } - pub fn set_repr(&mut self) + + fn get_repr() -> ffi::PyType_Slot where - T: for<'p> PyObjectReprProtocol<'p>, + Self: for<'p> PyObjectReprProtocol<'p>, { - self.tp_repr = py_unary_func!(PyObjectReprProtocol, T::__repr__); + ffi::PyType_Slot { + slot: ffi::Py_tp_repr, + pfunc: py_unary_func!(PyObjectReprProtocol, Self::__repr__) as _, + } } - pub fn set_hash(&mut self) + + fn get_hash() -> ffi::PyType_Slot where - T: for<'p> PyObjectHashProtocol<'p>, + Self: for<'p> PyObjectHashProtocol<'p>, { - self.tp_hash = py_unary_func!(PyObjectHashProtocol, T::__hash__, ffi::Py_hash_t); + ffi::PyType_Slot { + slot: ffi::Py_tp_hash, + pfunc: py_unary_func!(PyObjectHashProtocol, Self::__hash__, ffi::Py_hash_t) as _, + } } - pub fn set_getattr(&mut self) + + fn get_getattr() -> ffi::PyType_Slot where - T: for<'p> PyObjectGetAttrProtocol<'p>, + Self: for<'p> PyObjectGetAttrProtocol<'p>, { - self.tp_getattro = tp_getattro::(); + ffi::PyType_Slot { + slot: ffi::Py_tp_getattro, + pfunc: tp_getattro::() as _, + } } - pub fn set_richcompare(&mut self) + + fn get_richcompare() -> ffi::PyType_Slot where - T: for<'p> PyObjectRichcmpProtocol<'p>, + Self: for<'p> PyObjectRichcmpProtocol<'p>, { - self.tp_richcompare = tp_richcompare::(); + ffi::PyType_Slot { + slot: ffi::Py_tp_getattro, + pfunc: tp_richcompare::() as _, + } } - pub fn set_setattr(&mut self) + + fn get_setattr() -> ffi::PyType_Slot where - T: for<'p> PyObjectSetAttrProtocol<'p>, + Self: for<'p> PyObjectSetAttrProtocol<'p>, { - self.tp_setattro = py_func_set!(PyObjectSetAttrProtocol, T, __setattr__); + ffi::PyType_Slot { + slot: ffi::Py_tp_setattro, + pfunc: py_func_set!(PyObjectSetAttrProtocol, Self::__setattr__) as _, + } } - pub fn set_delattr(&mut self) + + fn get_delattr() -> ffi::PyType_Slot where - T: for<'p> PyObjectDelAttrProtocol<'p>, + Self: for<'p> PyObjectDelAttrProtocol<'p>, { - self.tp_setattro = py_func_del!(PyObjectDelAttrProtocol, T, __delattr__); + ffi::PyType_Slot { + slot: ffi::Py_tp_setattro, + pfunc: py_func_del!(PyObjectDelAttrProtocol, Self::__delattr__) as _, + } } - pub fn set_setdelattr(&mut self) + + fn get_setdelattr() -> ffi::PyType_Slot where - T: for<'p> PyObjectSetAttrProtocol<'p> + for<'p> PyObjectDelAttrProtocol<'p>, + Self: for<'p> PyObjectSetAttrProtocol<'p> + for<'p> PyObjectDelAttrProtocol<'p>, { - self.tp_setattro = py_func_set_del!( - PyObjectSetAttrProtocol, - PyObjectDelAttrProtocol, - T, - __setattr__, - __delattr__ - ) + ffi::PyType_Slot { + slot: ffi::Py_tp_setattro, + pfunc: py_func_set_del!( + PyObjectSetAttrProtocol, + PyObjectDelAttrProtocol, + Self, + __setattr__, + __delattr__ + ) as _, + } } - pub fn set_bool(&mut self) + + fn get_bool() -> ffi::PyType_Slot where - T: for<'p> PyObjectBoolProtocol<'p>, + Self: for<'p> PyObjectBoolProtocol<'p>, { - self.nb_bool = py_unary_func!(PyObjectBoolProtocol, T::__bool__, c_int); - } - - pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { - slots.maybe_push(ffi::Py_tp_str, self.tp_str.map(|v| v as _)); - slots.maybe_push(ffi::Py_tp_repr, self.tp_repr.map(|v| v as _)); - slots.maybe_push(ffi::Py_tp_hash, self.tp_hash.map(|v| v as _)); - slots.maybe_push(ffi::Py_tp_getattro, self.tp_getattro.map(|v| v as _)); - slots.maybe_push(ffi::Py_tp_richcompare, self.tp_richcompare.map(|v| v as _)); - slots.maybe_push(ffi::Py_tp_setattro, self.tp_setattro.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_bool, self.nb_bool.map(|v| v as _)); + ffi::PyType_Slot { + slot: ffi::Py_nb_bool, + pfunc: py_unary_func!(PyObjectBoolProtocol, Self::__bool__, c_int) as _, + } } } -fn tp_getattro() -> Option +impl<'p, T> PyBasicSlots for T where T: PyObjectProtocol<'p> {} + +fn tp_getattro() -> ffi::binaryfunc where T: for<'p> PyObjectGetAttrProtocol<'p>, { @@ -247,10 +262,10 @@ where call_ref!(slf, __getattr__, arg).convert(py) }) } - Some(wrap::) + wrap:: } -fn tp_richcompare() -> Option +fn tp_richcompare() -> ffi::richcmpfunc where T: for<'p> PyObjectRichcmpProtocol<'p>, { @@ -283,5 +298,5 @@ where slf.try_borrow()?.__richcmp__(arg, op).convert(py) }) } - Some(wrap::) + wrap:: } diff --git a/src/class/descr.rs b/src/class/descr.rs index 88fb4dc50cd..2c6a7009413 100644 --- a/src/class/descr.rs +++ b/src/class/descr.rs @@ -8,7 +8,6 @@ use crate::callback::IntoPyCallbackOutput; use crate::types::PyAny; use crate::{ffi, FromPyObject, PyClass, PyObject}; -use std::os::raw::c_int; /// Descriptor interface #[allow(unused_variables)] @@ -70,29 +69,28 @@ pub trait PyDescrSetNameProtocol<'p>: PyDescrProtocol<'p> { type Result: IntoPyCallbackOutput<()>; } -/// All FFI functions for description protocols. -#[derive(Default)] -pub struct PyDescrMethods { - pub tp_descr_get: Option, - pub tp_descr_set: Option, -} - +/// Extension trait for our proc-macro backend. #[doc(hidden)] -impl PyDescrMethods { - pub fn set_descr_get(&mut self) +pub trait PyDescrSlots { + fn get_descr_get() -> ffi::PyType_Slot where - T: for<'p> PyDescrGetProtocol<'p>, + Self: for<'p> PyDescrGetProtocol<'p>, { - self.tp_descr_get = py_ternarys_func!(PyDescrGetProtocol, T::__get__); + ffi::PyType_Slot { + slot: ffi::Py_tp_descr_get, + pfunc: py_ternarys_func!(PyDescrGetProtocol, Self::__get__) as _, + } } - pub fn set_descr_set(&mut self) + + fn get_descr_set() -> ffi::PyType_Slot where - T: for<'p> PyDescrSetProtocol<'p>, + Self: for<'p> PyDescrSetProtocol<'p>, { - self.tp_descr_set = py_ternarys_func!(PyDescrSetProtocol, T::__set__, c_int); - } - pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { - slots.maybe_push(ffi::Py_tp_descr_get, self.tp_descr_get.map(|v| v as _)); - slots.maybe_push(ffi::Py_tp_descr_set, self.tp_descr_set.map(|v| v as _)); + ffi::PyType_Slot { + slot: ffi::Py_tp_descr_set, + pfunc: py_ternarys_func!(PyDescrSetProtocol, Self::__set__) as _, + } } } + +impl<'p, T> PyDescrSlots for T where T: PyDescrProtocol<'p> {} diff --git a/src/class/gc.rs b/src/class/gc.rs index d600e663f5b..08965df398a 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -18,35 +18,31 @@ pub trait PyGCProtocol<'p>: PyClass { pub trait PyGCTraverseProtocol<'p>: PyGCProtocol<'p> {} pub trait PyGCClearProtocol<'p>: PyGCProtocol<'p> {} -/// All FFI functions for gc protocols. -#[derive(Default)] -pub struct PyGCMethods { - pub tp_traverse: Option, - pub tp_clear: Option, -} - +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyGCMethods { - pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { - slots.maybe_push(ffi::Py_tp_traverse, self.tp_traverse.map(|v| v as _)); - slots.maybe_push(ffi::Py_tp_clear, self.tp_clear.map(|v| v as _)); - } - - pub fn set_traverse(&mut self) +pub trait PyGCSlots { + fn get_traverse() -> ffi::PyType_Slot where - T: for<'p> PyGCTraverseProtocol<'p>, + Self: for<'p> PyGCTraverseProtocol<'p>, { - self.tp_traverse = tp_traverse::(); + ffi::PyType_Slot { + slot: ffi::Py_tp_traverse, + pfunc: tp_traverse::() as _, + } } - - pub fn set_clear(&mut self) + fn get_clear() -> ffi::PyType_Slot where - T: for<'p> PyGCClearProtocol<'p>, + Self: for<'p> PyGCClearProtocol<'p>, { - self.tp_clear = tp_clear::(); + ffi::PyType_Slot { + slot: ffi::Py_tp_clear, + pfunc: tp_clear::() as _, + } } } +impl<'p, T> PyGCSlots for T where T: PyGCProtocol<'p> {} + /// Object visitor for GC. #[derive(Clone)] pub struct PyVisit<'p> { @@ -73,7 +69,7 @@ impl<'p> PyVisit<'p> { } } -fn tp_traverse() -> Option +fn tp_traverse() -> ffi::traverseproc where T: for<'p> PyGCTraverseProtocol<'p>, { @@ -105,10 +101,10 @@ where } } - Some(tp_traverse::) + tp_traverse:: } -fn tp_clear() -> Option +fn tp_clear() -> ffi::inquiry where T: for<'p> PyGCClearProtocol<'p>, { @@ -123,5 +119,5 @@ where slf.borrow_mut().__clear__(); 0 } - Some(tp_clear::) + tp_clear:: } diff --git a/src/class/iter.rs b/src/class/iter.rs index 9f5ffc0d260..e53dcc97638 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -71,32 +71,31 @@ pub trait PyIterNextProtocol<'p>: PyIterProtocol<'p> { type Result: IntoPyCallbackOutput; } -#[derive(Default)] -pub struct PyIterMethods { - pub tp_iter: Option, - pub tp_iternext: Option, -} - +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyIterMethods { - pub fn set_iter(&mut self) +pub trait PyIterSlots { + fn get_iter() -> ffi::PyType_Slot where - T: for<'p> PyIterIterProtocol<'p>, + Self: for<'p> PyIterIterProtocol<'p>, { - self.tp_iter = py_unarys_func!(PyIterIterProtocol, T::__iter__); + ffi::PyType_Slot { + slot: ffi::Py_tp_iter, + pfunc: py_unarys_func!(PyIterIterProtocol, Self::__iter__) as _, + } } - pub fn set_iternext(&mut self) + fn get_iternext() -> ffi::PyType_Slot where - T: for<'p> PyIterNextProtocol<'p>, + Self: for<'p> PyIterNextProtocol<'p>, { - self.tp_iternext = py_unarys_func!(PyIterNextProtocol, T::__next__); - } - pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { - slots.maybe_push(ffi::Py_tp_iter, self.tp_iter.map(|v| v as _)); - slots.maybe_push(ffi::Py_tp_iternext, self.tp_iternext.map(|v| v as _)); + ffi::PyType_Slot { + slot: ffi::Py_tp_iternext, + pfunc: py_unarys_func!(PyIterNextProtocol, Self::__next__) as _, + } } } +impl<'p, T> PyIterSlots for T where T: PyIterProtocol<'p> {} + /// Output of `__next__` which can either `yield` the next value in the iteration, or /// `return` a value to raise `StopIteration` in Python. /// diff --git a/src/class/macros.rs b/src/class/macros.rs index 49db13c775b..98c0a47da74 100644 --- a/src/class/macros.rs +++ b/src/class/macros.rs @@ -11,7 +11,7 @@ macro_rules! py_unary_func { $call!(slf, $f).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; // Use call_ref! by default ($trait:ident, $class:ident :: $f:ident, $ret_type:ty) => { @@ -34,10 +34,10 @@ macro_rules! py_unarys_func { >::try_from_pycell(slf) .map_err(|e| e.into())?; - $class::$f(borrow).convert(py) + T::$f(borrow).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -60,7 +60,7 @@ macro_rules! py_binary_func { $call!(slf, $f, arg).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; ($trait:ident, $class:ident :: $f:ident, $return:ty) => { py_binary_func!($trait, $class::$f, $return, call_ref) @@ -82,10 +82,10 @@ macro_rules! py_binary_num_func { $crate::callback_body!(py, { let lhs = py.from_borrowed_ptr::<$crate::PyAny>(lhs); let rhs = extract_or_return_not_implemented!(py, rhs); - $class::$f(lhs.extract()?, rhs).convert(py) + T::$f(lhs.extract()?, rhs).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -102,10 +102,10 @@ macro_rules! py_binary_reversed_num_func { // Swap lhs <-> rhs let slf: &$crate::PyCell = extract_or_return_not_implemented!(py, rhs); let arg = extract_or_return_not_implemented!(py, lhs); - $class::$f(&*slf.try_borrow()?, arg).convert(py) + T::$f(&*slf.try_borrow()?, arg).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -123,17 +123,17 @@ macro_rules! py_binary_fallback_num_func { let rhs = py.from_borrowed_ptr::<$crate::PyAny>(rhs); // First, try the left hand method (e.g., __add__) match (lhs.extract(), rhs.extract()) { - (Ok(l), Ok(r)) => $class::$lop(l, r).convert(py), + (Ok(l), Ok(r)) => T::$lop(l, r).convert(py), _ => { // Next, try the right hand method (e.g., __radd__) let slf: &$crate::PyCell = extract_or_return_not_implemented!(rhs); let arg = extract_or_return_not_implemented!(lhs); - $class::$rop(&*slf.try_borrow()?, arg).convert(py) + T::$rop(&*slf.try_borrow()?, arg).convert(py) } } }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -155,7 +155,7 @@ macro_rules! py_binary_self_func { Ok::<_, $crate::err::PyErr>(slf) }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -177,7 +177,7 @@ macro_rules! py_ssizearg_func { $call!(slf, $f; arg.into()).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -203,11 +203,11 @@ macro_rules! py_ternarys_func { .from_borrowed_ptr::<$crate::types::PyAny>(arg2) .extract()?; - $class::$f(slf, arg1, arg2).convert(py) + T::$f(slf, arg1, arg2).convert(py) }) } - Some(wrap::) + wrap::<$class> }}; ($trait:ident, $class:ident :: $f:ident) => { py_ternarys_func!($trait, $class::$f, *mut $crate::ffi::PyObject); @@ -215,8 +215,8 @@ macro_rules! py_ternarys_func { } macro_rules! py_func_set { - ($trait_name:ident, $generic:ident, $fn_set:ident) => {{ - unsafe extern "C" fn wrap<$generic>( + ($trait_name:ident, $class:ident :: $fn_set:ident) => {{ + unsafe extern "C" fn wrap( slf: *mut $crate::ffi::PyObject, name: *mut $crate::ffi::PyObject, value: *mut $crate::ffi::PyObject, @@ -225,12 +225,12 @@ macro_rules! py_func_set { T: for<'p> $trait_name<'p>, { $crate::callback_body!(py, { - let slf = py.from_borrowed_ptr::<$crate::PyCell<$generic>>(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); if value.is_null() { Err($crate::exceptions::PyNotImplementedError::new_err(format!( "Subscript deletion not supported by {:?}", - stringify!($generic) + stringify!($class) ))) } else { let name = py.from_borrowed_ptr::<$crate::PyAny>(name); @@ -240,23 +240,23 @@ macro_rules! py_func_set { }) } - Some(wrap::<$generic>) + wrap::<$class> }}; } macro_rules! py_func_del { - ($trait_name:ident, $generic:ident, $fn_del:ident) => {{ - unsafe extern "C" fn wrap( + ($trait_name:ident, $class:ident :: $fn_del:ident) => {{ + unsafe extern "C" fn wrap( slf: *mut $crate::ffi::PyObject, name: *mut $crate::ffi::PyObject, value: *mut $crate::ffi::PyObject, ) -> libc::c_int where - U: for<'p> $trait_name<'p>, + T: for<'p> $trait_name<'p>, { $crate::callback_body!(py, { if value.is_null() { - let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); let name = py .from_borrowed_ptr::<$crate::types::PyAny>(name) .extract()?; @@ -269,13 +269,13 @@ macro_rules! py_func_del { }) } - Some(wrap::<$generic>) + wrap::<$class> }}; } macro_rules! py_func_set_del { - ($trait1:ident, $trait2:ident, $generic:ident, $fn_set:ident, $fn_del:ident) => {{ - unsafe extern "C" fn wrap<$generic>( + ($trait1:ident, $trait2:ident, $class:ident, $fn_set:ident, $fn_del:ident) => {{ + unsafe extern "C" fn wrap( slf: *mut $crate::ffi::PyObject, name: *mut $crate::ffi::PyObject, value: *mut $crate::ffi::PyObject, @@ -284,7 +284,7 @@ macro_rules! py_func_set_del { T: for<'p> $trait1<'p> + for<'p> $trait2<'p>, { $crate::callback_body!(py, { - let slf = py.from_borrowed_ptr::<$crate::PyCell<$generic>>(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); let name = py.from_borrowed_ptr::<$crate::PyAny>(name); if value.is_null() { @@ -295,7 +295,7 @@ macro_rules! py_func_set_del { } }) } - Some(wrap::<$generic>) + wrap::<$class> }}; } diff --git a/src/class/mapping.rs b/src/class/mapping.rs index c9daa7bd9c2..07ee4122262 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -6,17 +6,6 @@ use crate::callback::IntoPyCallbackOutput; use crate::{exceptions, ffi, FromPyObject, PyClass, PyObject}; -#[cfg(Py_LIMITED_API)] -#[derive(Clone, Default)] -pub struct PyMappingMethods { - pub mp_length: Option, - pub mp_subscript: Option, - pub mp_ass_subscript: Option, -} - -#[cfg(not(Py_LIMITED_API))] -pub use ffi::PyMappingMethods; - /// Mapping interface #[allow(unused_variables)] pub trait PyMappingProtocol<'p>: PyClass { @@ -83,50 +72,64 @@ pub trait PyMappingReversedProtocol<'p>: PyMappingProtocol<'p> { type Result: IntoPyCallbackOutput; } +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyMappingMethods { - pub fn set_length(&mut self) +pub trait PyMappingSlots { + fn get_length() -> ffi::PyType_Slot where - T: for<'p> PyMappingLenProtocol<'p>, + Self: for<'p> PyMappingLenProtocol<'p>, { - self.mp_length = py_len_func!(PyMappingLenProtocol, T::__len__); + ffi::PyType_Slot { + slot: ffi::Py_mp_length, + pfunc: py_len_func!(PyMappingLenProtocol, Self::__len__) as _, + } } - pub fn set_getitem(&mut self) + + fn get_getitem() -> ffi::PyType_Slot where - T: for<'p> PyMappingGetItemProtocol<'p>, + Self: for<'p> PyMappingGetItemProtocol<'p>, { - self.mp_subscript = py_binary_func!(PyMappingGetItemProtocol, T::__getitem__); + ffi::PyType_Slot { + slot: ffi::Py_mp_subscript, + pfunc: py_binary_func!(PyMappingGetItemProtocol, Self::__getitem__) as _, + } } - pub fn set_setitem(&mut self) + + fn get_setitem() -> ffi::PyType_Slot where - T: for<'p> PyMappingSetItemProtocol<'p>, + Self: for<'p> PyMappingSetItemProtocol<'p>, { - self.mp_ass_subscript = py_func_set!(PyMappingSetItemProtocol, T, __setitem__); + ffi::PyType_Slot { + slot: ffi::Py_mp_ass_subscript, + pfunc: py_func_set!(PyMappingSetItemProtocol, Self::__setitem__) as _, + } } - pub fn set_delitem(&mut self) + + fn get_delitem() -> ffi::PyType_Slot where - T: for<'p> PyMappingDelItemProtocol<'p>, + Self: for<'p> PyMappingDelItemProtocol<'p>, { - self.mp_ass_subscript = py_func_del!(PyMappingDelItemProtocol, T, __delitem__); + ffi::PyType_Slot { + slot: ffi::Py_mp_ass_subscript, + pfunc: py_func_del!(PyMappingDelItemProtocol, Self::__delitem__) as _, + } } - pub fn set_setdelitem(&mut self) + + fn get_setdelitem() -> ffi::PyType_Slot where - T: for<'p> PyMappingSetItemProtocol<'p> + for<'p> PyMappingDelItemProtocol<'p>, + Self: for<'p> PyMappingSetItemProtocol<'p> + for<'p> PyMappingDelItemProtocol<'p>, { - self.mp_ass_subscript = py_func_set_del!( - PyMappingSetItemProtocol, - PyMappingDelItemProtocol, - T, - __setitem__, - __delitem__ - ); - } - pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { - slots.maybe_push(ffi::Py_mp_length, self.mp_length.map(|v| v as _)); - slots.maybe_push(ffi::Py_mp_subscript, self.mp_subscript.map(|v| v as _)); - slots.maybe_push( - ffi::Py_mp_ass_subscript, - self.mp_ass_subscript.map(|v| v as _), - ); + ffi::PyType_Slot { + slot: ffi::Py_mp_ass_subscript, + pfunc: py_func_set_del!( + PyMappingSetItemProtocol, + PyMappingDelItemProtocol, + Self, + __setitem__, + __delitem__ + ) as _, + } } } + +impl<'p, T> PyMappingSlots for T where T: PyMappingProtocol<'p> {} diff --git a/src/class/number.rs b/src/class/number.rs index 60f7d17eb6a..b095da9a777 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -5,97 +5,6 @@ use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; use crate::{ffi, FromPyObject, PyClass, PyObject}; -#[cfg(Py_LIMITED_API)] -use std::os::raw::c_void; - -#[cfg(Py_LIMITED_API)] -#[derive(Clone)] -pub struct PyNumberMethods { - pub nb_add: Option, - pub nb_subtract: Option, - pub nb_multiply: Option, - pub nb_remainder: Option, - pub nb_divmod: Option, - pub nb_power: Option, - pub nb_negative: Option, - pub nb_positive: Option, - pub nb_absolute: Option, - pub nb_bool: Option, - pub nb_invert: Option, - pub nb_lshift: Option, - pub nb_rshift: Option, - pub nb_and: Option, - pub nb_xor: Option, - pub nb_or: Option, - pub nb_int: Option, - #[allow(dead_code)] - pub nb_reserved: *mut c_void, - pub nb_float: Option, - pub nb_inplace_add: Option, - pub nb_inplace_subtract: Option, - pub nb_inplace_multiply: Option, - pub nb_inplace_remainder: Option, - pub nb_inplace_power: Option, - pub nb_inplace_lshift: Option, - pub nb_inplace_rshift: Option, - pub nb_inplace_and: Option, - pub nb_inplace_xor: Option, - pub nb_inplace_or: Option, - pub nb_floor_divide: Option, - pub nb_true_divide: Option, - pub nb_inplace_floor_divide: Option, - pub nb_inplace_true_divide: Option, - pub nb_index: Option, - pub nb_matrix_multiply: Option, - pub nb_inplace_matrix_multiply: Option, -} - -#[cfg(not(Py_LIMITED_API))] -pub use crate::ffi::PyNumberMethods; - -impl Default for PyNumberMethods { - #[inline] - fn default() -> Self { - Self { - nb_add: None, - nb_subtract: None, - nb_multiply: None, - nb_remainder: None, - nb_divmod: None, - nb_power: None, - nb_negative: None, - nb_positive: None, - nb_absolute: None, - nb_bool: None, - nb_invert: None, - nb_lshift: None, - nb_rshift: None, - nb_and: None, - nb_xor: None, - nb_or: None, - nb_int: None, - nb_reserved: ::std::ptr::null_mut(), - nb_float: None, - nb_inplace_add: None, - nb_inplace_subtract: None, - nb_inplace_multiply: None, - nb_inplace_remainder: None, - nb_inplace_power: None, - nb_inplace_lshift: None, - nb_inplace_rshift: None, - nb_inplace_and: None, - nb_inplace_xor: None, - nb_inplace_or: None, - nb_floor_divide: None, - nb_true_divide: None, - nb_inplace_floor_divide: None, - nb_inplace_true_divide: None, - nb_index: None, - nb_matrix_multiply: None, - nb_inplace_matrix_multiply: None, - } - } -} /// Number interface #[allow(unused_variables)] @@ -669,111 +578,164 @@ pub trait PyNumberIndexProtocol<'p>: PyNumberProtocol<'p> { type Result: IntoPyCallbackOutput; } +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyNumberMethods { - pub fn set_add_radd(&mut self) - where - T: for<'p> PyNumberAddProtocol<'p> + for<'p> PyNumberRAddProtocol<'p>, - { - self.nb_add = py_binary_fallback_num_func!( - T, - PyNumberAddProtocol::__add__, - PyNumberRAddProtocol::__radd__ - ); +pub trait PyNumberSlots { + fn get_add_radd() -> ffi::PyType_Slot + where + Self: for<'p> PyNumberAddProtocol<'p> + for<'p> PyNumberRAddProtocol<'p>, + { + ffi::PyType_Slot { + slot: ffi::Py_nb_add, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberAddProtocol::__add__, + PyNumberRAddProtocol::__radd__ + ) as _, + } } - pub fn set_add(&mut self) + + fn get_add() -> ffi::PyType_Slot where - T: for<'p> PyNumberAddProtocol<'p>, + Self: for<'p> PyNumberAddProtocol<'p>, { - self.nb_add = py_binary_num_func!(PyNumberAddProtocol, T::__add__); + ffi::PyType_Slot { + slot: ffi::Py_nb_add, + pfunc: py_binary_num_func!(PyNumberAddProtocol, Self::__add__) as _, + } } - pub fn set_radd(&mut self) + + fn get_radd() -> ffi::PyType_Slot where - T: for<'p> PyNumberRAddProtocol<'p>, + Self: for<'p> PyNumberRAddProtocol<'p>, { - self.nb_add = py_binary_reversed_num_func!(PyNumberRAddProtocol, T::__radd__); + ffi::PyType_Slot { + slot: ffi::Py_nb_add, + pfunc: py_binary_reversed_num_func!(PyNumberRAddProtocol, Self::__radd__) as _, + } } - pub fn set_sub_rsub(&mut self) + + fn get_sub_rsub() -> ffi::PyType_Slot where - T: for<'p> PyNumberSubProtocol<'p> + for<'p> PyNumberRSubProtocol<'p>, + Self: for<'p> PyNumberSubProtocol<'p> + for<'p> PyNumberRSubProtocol<'p>, { - self.nb_subtract = py_binary_fallback_num_func!( - T, - PyNumberSubProtocol::__sub__, - PyNumberRSubProtocol::__rsub__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_subtract, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberSubProtocol::__sub__, + PyNumberRSubProtocol::__rsub__ + ) as _, + } } - pub fn set_sub(&mut self) + + fn get_sub() -> ffi::PyType_Slot where - T: for<'p> PyNumberSubProtocol<'p>, + Self: for<'p> PyNumberSubProtocol<'p>, { - self.nb_subtract = py_binary_num_func!(PyNumberSubProtocol, T::__sub__); + ffi::PyType_Slot { + slot: ffi::Py_nb_subtract, + pfunc: py_binary_num_func!(PyNumberSubProtocol, Self::__sub__) as _, + } } - pub fn set_rsub(&mut self) + + fn get_rsub() -> ffi::PyType_Slot where - T: for<'p> PyNumberRSubProtocol<'p>, + Self: for<'p> PyNumberRSubProtocol<'p>, { - self.nb_subtract = py_binary_reversed_num_func!(PyNumberRSubProtocol, T::__rsub__); + ffi::PyType_Slot { + slot: ffi::Py_nb_subtract, + pfunc: py_binary_reversed_num_func!(PyNumberRSubProtocol, Self::__rsub__) as _, + } } - pub fn set_mul_rmul(&mut self) + + fn get_mul_rmul() -> ffi::PyType_Slot where - T: for<'p> PyNumberMulProtocol<'p> + for<'p> PyNumberRMulProtocol<'p>, + Self: for<'p> PyNumberMulProtocol<'p> + for<'p> PyNumberRMulProtocol<'p>, { - self.nb_multiply = py_binary_fallback_num_func!( - T, - PyNumberMulProtocol::__mul__, - PyNumberRMulProtocol::__rmul__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_multiply, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberMulProtocol::__mul__, + PyNumberRMulProtocol::__rmul__ + ) as _, + } } - pub fn set_mul(&mut self) + + fn get_mul() -> ffi::PyType_Slot where - T: for<'p> PyNumberMulProtocol<'p>, + Self: for<'p> PyNumberMulProtocol<'p>, { - self.nb_multiply = py_binary_num_func!(PyNumberMulProtocol, T::__mul__); + ffi::PyType_Slot { + slot: ffi::Py_nb_multiply, + pfunc: py_binary_num_func!(PyNumberMulProtocol, Self::__mul__) as _, + } } - pub fn set_rmul(&mut self) + + fn get_rmul() -> ffi::PyType_Slot where - T: for<'p> PyNumberRMulProtocol<'p>, + Self: for<'p> PyNumberRMulProtocol<'p>, { - self.nb_multiply = py_binary_reversed_num_func!(PyNumberRMulProtocol, T::__rmul__); + ffi::PyType_Slot { + slot: ffi::Py_nb_multiply, + pfunc: py_binary_reversed_num_func!(PyNumberRMulProtocol, Self::__rmul__) as _, + } } - pub fn set_mod(&mut self) + + fn get_mod() -> ffi::PyType_Slot where - T: for<'p> PyNumberModProtocol<'p>, + Self: for<'p> PyNumberModProtocol<'p>, { - self.nb_remainder = py_binary_num_func!(PyNumberModProtocol, T::__mod__); + ffi::PyType_Slot { + slot: ffi::Py_nb_remainder, + pfunc: py_binary_num_func!(PyNumberModProtocol, Self::__mod__) as _, + } } - pub fn set_divmod_rdivmod(&mut self) + + fn get_divmod_rdivmod() -> ffi::PyType_Slot where - T: for<'p> PyNumberDivmodProtocol<'p> + for<'p> PyNumberRDivmodProtocol<'p>, + Self: for<'p> PyNumberDivmodProtocol<'p> + for<'p> PyNumberRDivmodProtocol<'p>, { - self.nb_divmod = py_binary_fallback_num_func!( - T, - PyNumberDivmodProtocol::__divmod__, - PyNumberRDivmodProtocol::__rdivmod__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_divmod, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberDivmodProtocol::__divmod__, + PyNumberRDivmodProtocol::__rdivmod__ + ) as _, + } } - pub fn set_divmod(&mut self) + + fn get_divmod() -> ffi::PyType_Slot where - T: for<'p> PyNumberDivmodProtocol<'p>, + Self: for<'p> PyNumberDivmodProtocol<'p>, { - self.nb_divmod = py_binary_num_func!(PyNumberDivmodProtocol, T::__divmod__); + ffi::PyType_Slot { + slot: ffi::Py_nb_divmod, + pfunc: py_binary_num_func!(PyNumberDivmodProtocol, Self::__divmod__) as _, + } } - pub fn set_rdivmod(&mut self) + + fn get_rdivmod() -> ffi::PyType_Slot where - T: for<'p> PyNumberRDivmodProtocol<'p>, + Self: for<'p> PyNumberRDivmodProtocol<'p>, { - self.nb_divmod = py_binary_reversed_num_func!(PyNumberRDivmodProtocol, T::__rdivmod__); + ffi::PyType_Slot { + slot: ffi::Py_nb_divmod, + pfunc: py_binary_reversed_num_func!(PyNumberRDivmodProtocol, Self::__rdivmod__) as _, + } } - pub fn set_pow_rpow(&mut self) + + fn get_pow_rpow() -> ffi::PyType_Slot where - T: for<'p> PyNumberPowProtocol<'p> + for<'p> PyNumberRPowProtocol<'p>, + Self: for<'p> PyNumberPowProtocol<'p> + for<'p> PyNumberRPowProtocol<'p>, { unsafe extern "C" fn wrap_pow_and_rpow( - lhs: *mut crate::ffi::PyObject, - rhs: *mut crate::ffi::PyObject, - modulo: *mut crate::ffi::PyObject, - ) -> *mut crate::ffi::PyObject + lhs: *mut ffi::PyObject, + rhs: *mut ffi::PyObject, + modulo: *mut ffi::PyObject, + ) -> *mut ffi::PyObject where T: for<'p> PyNumberPowProtocol<'p> + for<'p> PyNumberRPowProtocol<'p>, { @@ -794,17 +756,22 @@ impl PyNumberMethods { } }) } - self.nb_power = Some(wrap_pow_and_rpow::); + + ffi::PyType_Slot { + slot: ffi::Py_nb_power, + pfunc: wrap_pow_and_rpow:: as _, + } } - pub fn set_pow(&mut self) + + fn get_pow() -> ffi::PyType_Slot where - T: for<'p> PyNumberPowProtocol<'p>, + Self: for<'p> PyNumberPowProtocol<'p>, { unsafe extern "C" fn wrap_pow( - lhs: *mut crate::ffi::PyObject, - rhs: *mut crate::ffi::PyObject, - modulo: *mut crate::ffi::PyObject, - ) -> *mut crate::ffi::PyObject + lhs: *mut ffi::PyObject, + rhs: *mut ffi::PyObject, + modulo: *mut ffi::PyObject, + ) -> *mut ffi::PyObject where T: for<'p> PyNumberPowProtocol<'p>, { @@ -815,17 +782,22 @@ impl PyNumberMethods { T::__pow__(lhs, rhs, modulo).convert(py) }) } - self.nb_power = Some(wrap_pow::); + + ffi::PyType_Slot { + slot: ffi::Py_nb_power, + pfunc: wrap_pow:: as _, + } } - pub fn set_rpow(&mut self) + + fn get_rpow() -> ffi::PyType_Slot where - T: for<'p> PyNumberRPowProtocol<'p>, + Self: for<'p> PyNumberRPowProtocol<'p>, { unsafe extern "C" fn wrap_rpow( - arg: *mut crate::ffi::PyObject, - slf: *mut crate::ffi::PyObject, - modulo: *mut crate::ffi::PyObject, - ) -> *mut crate::ffi::PyObject + arg: *mut ffi::PyObject, + slf: *mut ffi::PyObject, + modulo: *mut ffi::PyObject, + ) -> *mut ffi::PyObject where T: for<'p> PyNumberRPowProtocol<'p>, { @@ -836,189 +808,294 @@ impl PyNumberMethods { slf.try_borrow()?.__rpow__(arg, modulo).convert(py) }) } - self.nb_power = Some(wrap_rpow::); + + ffi::PyType_Slot { + slot: ffi::Py_nb_power, + pfunc: wrap_rpow:: as _, + } } - pub fn set_neg(&mut self) + + fn get_neg() -> ffi::PyType_Slot where - T: for<'p> PyNumberNegProtocol<'p>, + Self: for<'p> PyNumberNegProtocol<'p>, { - self.nb_negative = py_unary_func!(PyNumberNegProtocol, T::__neg__); + ffi::PyType_Slot { + slot: ffi::Py_nb_negative, + pfunc: py_unary_func!(PyNumberNegProtocol, Self::__neg__) as _, + } } - pub fn set_pos(&mut self) + + fn get_pos() -> ffi::PyType_Slot where - T: for<'p> PyNumberPosProtocol<'p>, + Self: for<'p> PyNumberPosProtocol<'p>, { - self.nb_positive = py_unary_func!(PyNumberPosProtocol, T::__pos__); + ffi::PyType_Slot { + slot: ffi::Py_nb_absolute, + pfunc: py_unary_func!(PyNumberPosProtocol, Self::__pos__) as _, + } } - pub fn set_abs(&mut self) + + fn get_abs() -> ffi::PyType_Slot where - T: for<'p> PyNumberAbsProtocol<'p>, + Self: for<'p> PyNumberAbsProtocol<'p>, { - self.nb_absolute = py_unary_func!(PyNumberAbsProtocol, T::__abs__); + ffi::PyType_Slot { + slot: ffi::Py_nb_absolute, + pfunc: py_unary_func!(PyNumberAbsProtocol, Self::__abs__) as _, + } } - pub fn set_invert(&mut self) + + fn get_invert() -> ffi::PyType_Slot where - T: for<'p> PyNumberInvertProtocol<'p>, + Self: for<'p> PyNumberInvertProtocol<'p>, { - self.nb_invert = py_unary_func!(PyNumberInvertProtocol, T::__invert__); + ffi::PyType_Slot { + slot: ffi::Py_nb_invert, + pfunc: py_unary_func!(PyNumberInvertProtocol, Self::__invert__) as _, + } } - pub fn set_lshift_rlshift(&mut self) + + fn get_lshift_rlshift() -> ffi::PyType_Slot where - T: for<'p> PyNumberLShiftProtocol<'p> + for<'p> PyNumberRLShiftProtocol<'p>, + Self: for<'p> PyNumberLShiftProtocol<'p> + for<'p> PyNumberRLShiftProtocol<'p>, { - self.nb_lshift = py_binary_fallback_num_func!( - T, - PyNumberLShiftProtocol::__lshift__, - PyNumberRLShiftProtocol::__rlshift__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_lshift, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberLShiftProtocol::__lshift__, + PyNumberRLShiftProtocol::__rlshift__ + ) as _, + } } - pub fn set_lshift(&mut self) + + fn get_lshift() -> ffi::PyType_Slot where - T: for<'p> PyNumberLShiftProtocol<'p>, + Self: for<'p> PyNumberLShiftProtocol<'p>, { - self.nb_lshift = py_binary_num_func!(PyNumberLShiftProtocol, T::__lshift__); + ffi::PyType_Slot { + slot: ffi::Py_nb_lshift, + pfunc: py_binary_num_func!(PyNumberLShiftProtocol, Self::__lshift__) as _, + } } - pub fn set_rlshift(&mut self) + + fn get_rlshift() -> ffi::PyType_Slot where - T: for<'p> PyNumberRLShiftProtocol<'p>, + Self: for<'p> PyNumberRLShiftProtocol<'p>, { - self.nb_lshift = py_binary_reversed_num_func!(PyNumberRLShiftProtocol, T::__rlshift__); + ffi::PyType_Slot { + slot: ffi::Py_nb_lshift, + pfunc: py_binary_reversed_num_func!(PyNumberRLShiftProtocol, Self::__rlshift__) as _, + } } - pub fn set_rshift_rrshift(&mut self) + + fn get_rshift_rrshift() -> ffi::PyType_Slot where - T: for<'p> PyNumberRShiftProtocol<'p> + for<'p> PyNumberRRShiftProtocol<'p>, + Self: for<'p> PyNumberRShiftProtocol<'p> + for<'p> PyNumberRRShiftProtocol<'p>, { - self.nb_rshift = py_binary_fallback_num_func!( - T, - PyNumberRShiftProtocol::__rshift__, - PyNumberRRShiftProtocol::__rrshift__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_rshift, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberRShiftProtocol::__rshift__, + PyNumberRRShiftProtocol::__rrshift__ + ) as _, + } } - pub fn set_rshift(&mut self) + + fn get_rshift() -> ffi::PyType_Slot where - T: for<'p> PyNumberRShiftProtocol<'p>, + Self: for<'p> PyNumberRShiftProtocol<'p>, { - self.nb_rshift = py_binary_num_func!(PyNumberRShiftProtocol, T::__rshift__); + ffi::PyType_Slot { + slot: ffi::Py_nb_rshift, + pfunc: py_binary_num_func!(PyNumberRShiftProtocol, Self::__rshift__) as _, + } } - pub fn set_rrshift(&mut self) + + fn get_rrshift() -> ffi::PyType_Slot where - T: for<'p> PyNumberRRShiftProtocol<'p>, + Self: for<'p> PyNumberRRShiftProtocol<'p>, { - self.nb_rshift = py_binary_reversed_num_func!(PyNumberRRShiftProtocol, T::__rrshift__); + ffi::PyType_Slot { + slot: ffi::Py_nb_rshift, + pfunc: py_binary_reversed_num_func!(PyNumberRRShiftProtocol, Self::__rrshift__) as _, + } } - pub fn set_and_rand(&mut self) + + fn get_and_rand() -> ffi::PyType_Slot where - T: for<'p> PyNumberAndProtocol<'p> + for<'p> PyNumberRAndProtocol<'p>, + Self: for<'p> PyNumberAndProtocol<'p> + for<'p> PyNumberRAndProtocol<'p>, { - self.nb_and = py_binary_fallback_num_func!( - T, - PyNumberAndProtocol::__and__, - PyNumberRAndProtocol::__rand__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_and, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberAndProtocol::__and__, + PyNumberRAndProtocol::__rand__ + ) as _, + } } - pub fn set_and(&mut self) + + fn get_and() -> ffi::PyType_Slot where - T: for<'p> PyNumberAndProtocol<'p>, + Self: for<'p> PyNumberAndProtocol<'p>, { - self.nb_and = py_binary_num_func!(PyNumberAndProtocol, T::__and__); + ffi::PyType_Slot { + slot: ffi::Py_nb_and, + pfunc: py_binary_num_func!(PyNumberAndProtocol, Self::__and__) as _, + } } - pub fn set_rand(&mut self) + + fn get_rand() -> ffi::PyType_Slot where - T: for<'p> PyNumberRAndProtocol<'p>, + Self: for<'p> PyNumberRAndProtocol<'p>, { - self.nb_and = py_binary_reversed_num_func!(PyNumberRAndProtocol, T::__rand__); + ffi::PyType_Slot { + slot: ffi::Py_nb_and, + pfunc: py_binary_reversed_num_func!(PyNumberRAndProtocol, Self::__rand__) as _, + } } - pub fn set_xor_rxor(&mut self) + + fn get_xor_rxor() -> ffi::PyType_Slot where - T: for<'p> PyNumberXorProtocol<'p> + for<'p> PyNumberRXorProtocol<'p>, + Self: for<'p> PyNumberXorProtocol<'p> + for<'p> PyNumberRXorProtocol<'p>, { - self.nb_xor = py_binary_fallback_num_func!( - T, - PyNumberXorProtocol::__xor__, - PyNumberRXorProtocol::__rxor__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_xor, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberXorProtocol::__xor__, + PyNumberRXorProtocol::__rxor__ + ) as _, + } } - pub fn set_xor(&mut self) + + fn get_xor() -> ffi::PyType_Slot where - T: for<'p> PyNumberXorProtocol<'p>, + Self: for<'p> PyNumberXorProtocol<'p>, { - self.nb_xor = py_binary_num_func!(PyNumberXorProtocol, T::__xor__); + ffi::PyType_Slot { + slot: ffi::Py_nb_xor, + pfunc: py_binary_num_func!(PyNumberXorProtocol, Self::__xor__) as _, + } } - pub fn set_rxor(&mut self) + + fn get_rxor() -> ffi::PyType_Slot where - T: for<'p> PyNumberRXorProtocol<'p>, + Self: for<'p> PyNumberRXorProtocol<'p>, { - self.nb_xor = py_binary_reversed_num_func!(PyNumberRXorProtocol, T::__rxor__); + ffi::PyType_Slot { + slot: ffi::Py_nb_xor, + pfunc: py_binary_reversed_num_func!(PyNumberRXorProtocol, Self::__rxor__) as _, + } } - pub fn set_or_ror(&mut self) + + fn get_or_ror() -> ffi::PyType_Slot where - T: for<'p> PyNumberOrProtocol<'p> + for<'p> PyNumberROrProtocol<'p>, + Self: for<'p> PyNumberOrProtocol<'p> + for<'p> PyNumberROrProtocol<'p>, { - self.nb_or = py_binary_fallback_num_func!( - T, - PyNumberOrProtocol::__or__, - PyNumberROrProtocol::__ror__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_or, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberOrProtocol::__or__, + PyNumberROrProtocol::__ror__ + ) as _, + } } - pub fn set_or(&mut self) + + fn get_or() -> ffi::PyType_Slot where - T: for<'p> PyNumberOrProtocol<'p>, + Self: for<'p> PyNumberOrProtocol<'p>, { - self.nb_or = py_binary_num_func!(PyNumberOrProtocol, T::__or__); + ffi::PyType_Slot { + slot: ffi::Py_nb_or, + pfunc: py_binary_num_func!(PyNumberOrProtocol, Self::__or__) as _, + } } - pub fn set_ror(&mut self) + + fn get_ror() -> ffi::PyType_Slot where - T: for<'p> PyNumberROrProtocol<'p>, + Self: for<'p> PyNumberROrProtocol<'p>, { - self.nb_or = py_binary_reversed_num_func!(PyNumberROrProtocol, T::__ror__); + ffi::PyType_Slot { + slot: ffi::Py_nb_or, + pfunc: py_binary_reversed_num_func!(PyNumberROrProtocol, Self::__ror__) as _, + } } - pub fn set_int(&mut self) + + fn get_int() -> ffi::PyType_Slot where - T: for<'p> PyNumberIntProtocol<'p>, + Self: for<'p> PyNumberIntProtocol<'p>, { - self.nb_int = py_unary_func!(PyNumberIntProtocol, T::__int__); + ffi::PyType_Slot { + slot: ffi::Py_nb_int, + pfunc: py_unary_func!(PyNumberIntProtocol, Self::__int__) as _, + } } - pub fn set_float(&mut self) + + fn get_float() -> ffi::PyType_Slot where - T: for<'p> PyNumberFloatProtocol<'p>, + Self: for<'p> PyNumberFloatProtocol<'p>, { - self.nb_float = py_unary_func!(PyNumberFloatProtocol, T::__float__); + ffi::PyType_Slot { + slot: ffi::Py_nb_float, + pfunc: py_unary_func!(PyNumberFloatProtocol, Self::__float__) as _, + } } - pub fn set_iadd(&mut self) + + fn get_iadd() -> ffi::PyType_Slot where - T: for<'p> PyNumberIAddProtocol<'p>, + Self: for<'p> PyNumberIAddProtocol<'p>, { - self.nb_inplace_add = py_binary_self_func!(PyNumberIAddProtocol, T::__iadd__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_add, + pfunc: py_binary_self_func!(PyNumberIAddProtocol, Self::__iadd__) as _, + } } - pub fn set_isub(&mut self) + + fn get_isub() -> ffi::PyType_Slot where - T: for<'p> PyNumberISubProtocol<'p>, + Self: for<'p> PyNumberISubProtocol<'p>, { - self.nb_inplace_subtract = py_binary_self_func!(PyNumberISubProtocol, T::__isub__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_subtract, + pfunc: py_binary_self_func!(PyNumberISubProtocol, Self::__isub__) as _, + } } - pub fn set_imul(&mut self) + + fn get_imul() -> ffi::PyType_Slot where - T: for<'p> PyNumberIMulProtocol<'p>, + Self: for<'p> PyNumberIMulProtocol<'p>, { - self.nb_inplace_multiply = py_binary_self_func!(PyNumberIMulProtocol, T::__imul__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_multiply, + pfunc: py_binary_self_func!(PyNumberIMulProtocol, Self::__imul__) as _, + } } - pub fn set_imod(&mut self) + + fn get_imod() -> ffi::PyType_Slot where - T: for<'p> PyNumberIModProtocol<'p>, + Self: for<'p> PyNumberIModProtocol<'p>, { - self.nb_inplace_remainder = py_binary_self_func!(PyNumberIModProtocol, T::__imod__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_remainder, + pfunc: py_binary_self_func!(PyNumberIModProtocol, Self::__imod__) as _, + } } - pub fn set_ipow(&mut self) + + fn get_ipow() -> ffi::PyType_Slot where - T: for<'p> PyNumberIPowProtocol<'p>, + Self: for<'p> PyNumberIPowProtocol<'p>, { // NOTE: Somehow __ipow__ causes SIGSEGV in Python < 3.8 when we extract, // so we ignore it. It's the same as what CPython does. unsafe extern "C" fn wrap_ipow( - slf: *mut crate::ffi::PyObject, - other: *mut crate::ffi::PyObject, - _modulo: *mut crate::ffi::PyObject, - ) -> *mut crate::ffi::PyObject + slf: *mut ffi::PyObject, + other: *mut ffi::PyObject, + _modulo: *mut ffi::PyObject, + ) -> *mut ffi::PyObject where T: for<'p> PyNumberIPowProtocol<'p>, { @@ -1030,202 +1107,205 @@ impl PyNumberMethods { Ok::<_, PyErr>(slf) }) } - self.nb_inplace_power = Some(wrap_ipow::); + + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_power, + pfunc: wrap_ipow:: as _, + } } - pub fn set_ilshift(&mut self) + + fn get_ilshift() -> ffi::PyType_Slot where - T: for<'p> PyNumberILShiftProtocol<'p>, + Self: for<'p> PyNumberILShiftProtocol<'p>, { - self.nb_inplace_lshift = py_binary_self_func!(PyNumberILShiftProtocol, T::__ilshift__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_lshift, + pfunc: py_binary_self_func!(PyNumberILShiftProtocol, Self::__ilshift__) as _, + } } - pub fn set_irshift(&mut self) + + fn get_irshift() -> ffi::PyType_Slot where - T: for<'p> PyNumberIRShiftProtocol<'p>, + Self: for<'p> PyNumberIRShiftProtocol<'p>, { - self.nb_inplace_rshift = py_binary_self_func!(PyNumberIRShiftProtocol, T::__irshift__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_rshift, + pfunc: py_binary_self_func!(PyNumberIRShiftProtocol, Self::__irshift__) as _, + } } - pub fn set_iand(&mut self) + + fn get_iand() -> ffi::PyType_Slot where - T: for<'p> PyNumberIAndProtocol<'p>, + Self: for<'p> PyNumberIAndProtocol<'p>, { - self.nb_inplace_and = py_binary_self_func!(PyNumberIAndProtocol, T::__iand__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_and, + pfunc: py_binary_self_func!(PyNumberIAndProtocol, Self::__iand__) as _, + } } - pub fn set_ixor(&mut self) + + fn get_ixor() -> ffi::PyType_Slot where - T: for<'p> PyNumberIXorProtocol<'p>, + Self: for<'p> PyNumberIXorProtocol<'p>, { - self.nb_inplace_xor = py_binary_self_func!(PyNumberIXorProtocol, T::__ixor__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_xor, + pfunc: py_binary_self_func!(PyNumberIXorProtocol, Self::__ixor__) as _, + } } - pub fn set_ior(&mut self) + + fn get_ior() -> ffi::PyType_Slot where - T: for<'p> PyNumberIOrProtocol<'p>, + Self: for<'p> PyNumberIOrProtocol<'p>, { - self.nb_inplace_or = py_binary_self_func!(PyNumberIOrProtocol, T::__ior__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_or, + pfunc: py_binary_self_func!(PyNumberIOrProtocol, Self::__ior__) as _, + } } - pub fn set_floordiv_rfloordiv(&mut self) + + fn get_floordiv_rfloordiv() -> ffi::PyType_Slot where - T: for<'p> PyNumberFloordivProtocol<'p> + for<'p> PyNumberRFloordivProtocol<'p>, + Self: for<'p> PyNumberFloordivProtocol<'p> + for<'p> PyNumberRFloordivProtocol<'p>, { - self.nb_floor_divide = py_binary_fallback_num_func!( - T, - PyNumberFloordivProtocol::__floordiv__, - PyNumberRFloordivProtocol::__rfloordiv__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_floor_divide, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberFloordivProtocol::__floordiv__, + PyNumberRFloordivProtocol::__rfloordiv__ + ) as _, + } } - pub fn set_floordiv(&mut self) + + fn get_floordiv() -> ffi::PyType_Slot where - T: for<'p> PyNumberFloordivProtocol<'p>, + Self: for<'p> PyNumberFloordivProtocol<'p>, { - self.nb_floor_divide = py_binary_num_func!(PyNumberFloordivProtocol, T::__floordiv__); + ffi::PyType_Slot { + slot: ffi::Py_nb_floor_divide, + pfunc: py_binary_num_func!(PyNumberFloordivProtocol, Self::__floordiv__) as _, + } } - pub fn set_rfloordiv(&mut self) + + fn get_rfloordiv() -> ffi::PyType_Slot where - T: for<'p> PyNumberRFloordivProtocol<'p>, + Self: for<'p> PyNumberRFloordivProtocol<'p>, { - self.nb_floor_divide = - py_binary_reversed_num_func!(PyNumberRFloordivProtocol, T::__rfloordiv__); + ffi::PyType_Slot { + slot: ffi::Py_nb_floor_divide, + pfunc: py_binary_reversed_num_func!(PyNumberRFloordivProtocol, Self::__rfloordiv__) + as _, + } } - pub fn set_truediv_rtruediv(&mut self) + + fn get_truediv_rtruediv() -> ffi::PyType_Slot where - T: for<'p> PyNumberTruedivProtocol<'p> + for<'p> PyNumberRTruedivProtocol<'p>, + Self: for<'p> PyNumberTruedivProtocol<'p> + for<'p> PyNumberRTruedivProtocol<'p>, { - self.nb_true_divide = py_binary_fallback_num_func!( - T, - PyNumberTruedivProtocol::__truediv__, - PyNumberRTruedivProtocol::__rtruediv__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_true_divide, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberTruedivProtocol::__truediv__, + PyNumberRTruedivProtocol::__rtruediv__ + ) as _, + } } - pub fn set_truediv(&mut self) + + fn get_truediv() -> ffi::PyType_Slot where - T: for<'p> PyNumberTruedivProtocol<'p>, + Self: for<'p> PyNumberTruedivProtocol<'p>, { - self.nb_true_divide = py_binary_num_func!(PyNumberTruedivProtocol, T::__truediv__); + ffi::PyType_Slot { + slot: ffi::Py_nb_true_divide, + pfunc: py_binary_num_func!(PyNumberTruedivProtocol, Self::__truediv__) as _, + } } - pub fn set_rtruediv(&mut self) + + fn get_rtruediv() -> ffi::PyType_Slot where - T: for<'p> PyNumberRTruedivProtocol<'p>, + Self: for<'p> PyNumberRTruedivProtocol<'p>, { - self.nb_true_divide = - py_binary_reversed_num_func!(PyNumberRTruedivProtocol, T::__rtruediv__); + ffi::PyType_Slot { + slot: ffi::Py_nb_true_divide, + pfunc: py_binary_reversed_num_func!(PyNumberRTruedivProtocol, Self::__rtruediv__) as _, + } } - pub fn set_ifloordiv(&mut self) + + fn get_ifloordiv() -> ffi::PyType_Slot where - T: for<'p> PyNumberIFloordivProtocol<'p>, + Self: for<'p> PyNumberIFloordivProtocol<'p>, { - self.nb_inplace_floor_divide = - py_binary_self_func!(PyNumberIFloordivProtocol, T::__ifloordiv__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_floor_divide, + pfunc: py_binary_self_func!(PyNumberIFloordivProtocol, Self::__ifloordiv__) as _, + } } - pub fn set_itruediv(&mut self) + + fn get_itruediv() -> ffi::PyType_Slot where - T: for<'p> PyNumberITruedivProtocol<'p>, + Self: for<'p> PyNumberITruedivProtocol<'p>, { - self.nb_inplace_true_divide = - py_binary_self_func!(PyNumberITruedivProtocol, T::__itruediv__); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_true_divide, + pfunc: py_binary_self_func!(PyNumberITruedivProtocol, Self::__itruediv__) as _, + } } - pub fn set_index(&mut self) + + fn get_index() -> ffi::PyType_Slot where - T: for<'p> PyNumberIndexProtocol<'p>, + Self: for<'p> PyNumberIndexProtocol<'p>, { - self.nb_index = py_unary_func!(PyNumberIndexProtocol, T::__index__); + ffi::PyType_Slot { + slot: ffi::Py_nb_index, + pfunc: py_unary_func!(PyNumberIndexProtocol, Self::__index__) as _, + } } - pub fn set_matmul_rmatmul(&mut self) + + fn get_matmul_rmatmul() -> ffi::PyType_Slot where - T: for<'p> PyNumberMatmulProtocol<'p> + for<'p> PyNumberRMatmulProtocol<'p>, + Self: for<'p> PyNumberMatmulProtocol<'p> + for<'p> PyNumberRMatmulProtocol<'p>, { - self.nb_matrix_multiply = py_binary_fallback_num_func!( - T, - PyNumberMatmulProtocol::__matmul__, - PyNumberRMatmulProtocol::__rmatmul__ - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_matrix_multiply, + pfunc: py_binary_fallback_num_func!( + Self, + PyNumberMatmulProtocol::__matmul__, + PyNumberRMatmulProtocol::__rmatmul__ + ) as _, + } } - pub fn set_matmul(&mut self) + + fn get_matmul() -> ffi::PyType_Slot where - T: for<'p> PyNumberMatmulProtocol<'p>, + Self: for<'p> PyNumberMatmulProtocol<'p>, { - self.nb_matrix_multiply = py_binary_num_func!(PyNumberMatmulProtocol, T::__matmul__); + ffi::PyType_Slot { + slot: ffi::Py_nb_matrix_multiply, + pfunc: py_binary_num_func!(PyNumberMatmulProtocol, Self::__matmul__) as _, + } } - pub fn set_rmatmul(&mut self) + + fn get_rmatmul() -> ffi::PyType_Slot where - T: for<'p> PyNumberRMatmulProtocol<'p>, + Self: for<'p> PyNumberRMatmulProtocol<'p>, { - self.nb_matrix_multiply = - py_binary_reversed_num_func!(PyNumberRMatmulProtocol, T::__rmatmul__); + ffi::PyType_Slot { + slot: ffi::Py_nb_matrix_multiply, + pfunc: py_binary_reversed_num_func!(PyNumberRMatmulProtocol, Self::__rmatmul__) as _, + } } - pub fn set_imatmul(&mut self) + + fn get_imatmul() -> ffi::PyType_Slot where - T: for<'p> PyNumberIMatmulProtocol<'p>, + Self: for<'p> PyNumberIMatmulProtocol<'p>, { - self.nb_inplace_matrix_multiply = - py_binary_self_func!(PyNumberIMatmulProtocol, T::__imatmul__); - } - pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { - slots.maybe_push(ffi::Py_nb_add, self.nb_add.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_subtract, self.nb_subtract.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_multiply, self.nb_multiply.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_remainder, self.nb_remainder.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_divmod, self.nb_divmod.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_power, self.nb_power.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_negative, self.nb_negative.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_positive, self.nb_positive.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_absolute, self.nb_absolute.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_bool, self.nb_bool.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_invert, self.nb_invert.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_lshift, self.nb_lshift.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_rshift, self.nb_rshift.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_and, self.nb_and.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_xor, self.nb_xor.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_or, self.nb_or.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_int, self.nb_int.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_float, self.nb_float.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_inplace_add, self.nb_inplace_add.map(|v| v as _)); - slots.maybe_push( - ffi::Py_nb_inplace_subtract, - self.nb_inplace_subtract.map(|v| v as _), - ); - slots.maybe_push( - ffi::Py_nb_inplace_multiply, - self.nb_inplace_multiply.map(|v| v as _), - ); - slots.maybe_push( - ffi::Py_nb_inplace_remainder, - self.nb_inplace_remainder.map(|v| v as _), - ); - slots.maybe_push( - ffi::Py_nb_inplace_power, - self.nb_inplace_power.map(|v| v as _), - ); - slots.maybe_push( - ffi::Py_nb_inplace_lshift, - self.nb_inplace_lshift.map(|v| v as _), - ); - slots.maybe_push( - ffi::Py_nb_inplace_rshift, - self.nb_inplace_rshift.map(|v| v as _), - ); - slots.maybe_push(ffi::Py_nb_inplace_and, self.nb_inplace_and.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_inplace_xor, self.nb_inplace_xor.map(|v| v as _)); - slots.maybe_push(ffi::Py_nb_inplace_or, self.nb_inplace_or.map(|v| v as _)); - slots.maybe_push( - ffi::Py_nb_floor_divide, - self.nb_floor_divide.map(|v| v as _), - ); - slots.maybe_push(ffi::Py_nb_true_divide, self.nb_true_divide.map(|v| v as _)); - slots.maybe_push( - ffi::Py_nb_inplace_floor_divide, - self.nb_inplace_floor_divide.map(|v| v as _), - ); - slots.maybe_push( - ffi::Py_nb_inplace_true_divide, - self.nb_inplace_true_divide.map(|v| v as _), - ); - slots.maybe_push(ffi::Py_nb_index, self.nb_index.map(|v| v as _)); - slots.maybe_push( - ffi::Py_nb_matrix_multiply, - self.nb_matrix_multiply.map(|v| v as _), - ); - slots.maybe_push( - ffi::Py_nb_inplace_matrix_multiply, - self.nb_inplace_matrix_multiply.map(|v| v as _), - ); + ffi::PyType_Slot { + slot: ffi::Py_nb_inplace_matrix_multiply, + pfunc: py_binary_self_func!(PyNumberIMatmulProtocol, Self::__imatmul__) as _, + } } } + +impl<'p, T> PyNumberSlots for T where T: PyNumberProtocol<'p> {} diff --git a/src/class/proto_methods.rs b/src/class/proto_methods.rs index e9018789cfa..6093018b74f 100644 --- a/src/class/proto_methods.rs +++ b/src/class/proto_methods.rs @@ -1,160 +1,55 @@ -#[cfg(not(Py_LIMITED_API))] use crate::class::buffer::PyBufferProcs; -use crate::class::{ - basic::PyObjectMethods, descr::PyDescrMethods, gc::PyGCMethods, iter::PyIterMethods, - mapping::PyMappingMethods, number::PyNumberMethods, pyasync::PyAsyncMethods, - sequence::PySequenceMethods, -}; -use std::{ - ptr::{self, NonNull}, - sync::atomic::{AtomicPtr, Ordering}, -}; +use crate::ffi; -/// Defines all method tables we need for object protocols. // Note(kngwyu): default implementations are for rust-numpy. Please don't remove them. pub trait PyProtoMethods { - fn async_methods() -> Option> { - None - } - fn basic_methods() -> Option> { - None - } - #[cfg(not(Py_LIMITED_API))] - fn buffer_methods() -> Option> { - None - } - fn descr_methods() -> Option> { - None - } - fn gc_methods() -> Option> { - None + fn get_type_slots() -> Vec { + vec![] } - fn mapping_methods() -> Option> { - None - } - fn number_methods() -> Option> { - None - } - fn iter_methods() -> Option> { - None - } - fn sequence_methods() -> Option> { + fn get_buffer() -> Option { None } } -/// Indicates that a type has a protocol registry. Implemented by `#[pyclass]`. #[doc(hidden)] -pub trait HasProtoRegistry: Sized + 'static { - fn registry() -> &'static PyProtoRegistry; +pub enum PyProtoMethodDef { + Slots(Vec), + Buffer(PyBufferProcs), } -impl PyProtoMethods for T { - fn async_methods() -> Option> { - NonNull::new(Self::registry().async_methods.load(Ordering::Relaxed)) - } - fn basic_methods() -> Option> { - NonNull::new(Self::registry().basic_methods.load(Ordering::Relaxed)) - } - #[cfg(not(Py_LIMITED_API))] - fn buffer_methods() -> Option> { - NonNull::new(Self::registry().buffer_methods.load(Ordering::Relaxed)) - } - fn descr_methods() -> Option> { - NonNull::new(Self::registry().descr_methods.load(Ordering::Relaxed)) - } - fn gc_methods() -> Option> { - NonNull::new(Self::registry().gc_methods.load(Ordering::Relaxed)) - } - fn mapping_methods() -> Option> { - NonNull::new(Self::registry().mapping_methods.load(Ordering::Relaxed)) - } - fn number_methods() -> Option> { - NonNull::new(Self::registry().number_methods.load(Ordering::Relaxed)) - } - fn iter_methods() -> Option> { - NonNull::new(Self::registry().iter_methods.load(Ordering::Relaxed)) - } - fn sequence_methods() -> Option> { - NonNull::new(Self::registry().sequence_methods.load(Ordering::Relaxed)) - } +#[doc(hidden)] +#[cfg(feature = "macros")] +pub trait PyProtoMethodsInventory: inventory::Collect { + fn new(methods: PyProtoMethodDef) -> Self; + fn get(&'static self) -> &'static PyProtoMethodDef; } -/// Stores all method protocols. -/// Used in the proc-macro code as a static variable. #[doc(hidden)] -pub struct PyProtoRegistry { - /// Async protocols. - async_methods: AtomicPtr, - /// Basic protocols. - basic_methods: AtomicPtr, - /// Buffer protocols. - #[cfg(not(Py_LIMITED_API))] - buffer_methods: AtomicPtr, - /// Descr pProtocols. - descr_methods: AtomicPtr, - /// GC protocols. - gc_methods: AtomicPtr, - /// Mapping protocols. - mapping_methods: AtomicPtr, - /// Number protocols. - number_methods: AtomicPtr, - /// Iterator protocols. - iter_methods: AtomicPtr, - /// Sequence protocols. - sequence_methods: AtomicPtr, +#[cfg(feature = "macros")] +pub trait HasProtoMethodsInventory { + type ProtoMethods: PyProtoMethodsInventory; } -impl PyProtoRegistry { - pub const fn new() -> Self { - PyProtoRegistry { - async_methods: AtomicPtr::new(ptr::null_mut()), - basic_methods: AtomicPtr::new(ptr::null_mut()), - #[cfg(not(Py_LIMITED_API))] - buffer_methods: AtomicPtr::new(ptr::null_mut()), - descr_methods: AtomicPtr::new(ptr::null_mut()), - gc_methods: AtomicPtr::new(ptr::null_mut()), - mapping_methods: AtomicPtr::new(ptr::null_mut()), - number_methods: AtomicPtr::new(ptr::null_mut()), - iter_methods: AtomicPtr::new(ptr::null_mut()), - sequence_methods: AtomicPtr::new(ptr::null_mut()), - } - } - pub fn set_async_methods(&self, methods: PyAsyncMethods) { - self.async_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) +#[cfg(feature = "macros")] +impl PyProtoMethods for T { + fn get_type_slots() -> Vec { + inventory::iter:: + .into_iter() + .filter_map(|def| match def.get() { + PyProtoMethodDef::Slots(slots) => Some(slots), + PyProtoMethodDef::Buffer(_) => None, + }) + .flatten() + .cloned() + .collect() } - pub fn set_basic_methods(&self, methods: PyObjectMethods) { - self.basic_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - #[cfg(not(Py_LIMITED_API))] - pub fn set_buffer_methods(&self, methods: PyBufferProcs) { - self.buffer_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_descr_methods(&self, methods: PyDescrMethods) { - self.descr_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_gc_methods(&self, methods: PyGCMethods) { - self.gc_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_mapping_methods(&self, methods: PyMappingMethods) { - self.mapping_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_number_methods(&self, methods: PyNumberMethods) { - self.number_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_iter_methods(&self, methods: PyIterMethods) { - self.iter_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_sequence_methods(&self, methods: PySequenceMethods) { - self.sequence_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) + + fn get_buffer() -> Option { + inventory::iter:: + .into_iter() + .find_map(|def| match def.get() { + PyProtoMethodDef::Slots(_) => None, + PyProtoMethodDef::Buffer(buf) => Some(buf.clone()), + }) } } diff --git a/src/class/pyasync.rs b/src/class/pyasync.rs index 4f6998b1267..78cc552e322 100644 --- a/src/class/pyasync.rs +++ b/src/class/pyasync.rs @@ -96,34 +96,43 @@ pub trait PyAsyncAexitProtocol<'p>: PyAsyncProtocol<'p> { type Result: IntoPyCallbackOutput; } +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyAsyncMethods { - pub fn set_await(&mut self) +pub trait PyAsyncSlots { + fn get_await() -> ffi::PyType_Slot where - T: for<'p> PyAsyncAwaitProtocol<'p>, + Self: for<'p> PyAsyncAwaitProtocol<'p>, { - self.am_await = py_unarys_func!(PyAsyncAwaitProtocol, T::__await__); + ffi::PyType_Slot { + slot: ffi::Py_am_await, + pfunc: py_unarys_func!(PyAsyncAwaitProtocol, Self::__await__) as _, + } } - pub fn set_aiter(&mut self) + + fn get_aiter() -> ffi::PyType_Slot where - T: for<'p> PyAsyncAiterProtocol<'p>, + Self: for<'p> PyAsyncAiterProtocol<'p>, { - self.am_aiter = py_unarys_func!(PyAsyncAiterProtocol, T::__aiter__); + ffi::PyType_Slot { + slot: ffi::Py_am_await, + pfunc: py_unarys_func!(PyAsyncAiterProtocol, Self::__aiter__) as _, + } } - pub fn set_anext(&mut self) + + fn get_anext() -> ffi::PyType_Slot where - T: for<'p> PyAsyncAnextProtocol<'p>, + Self: for<'p> PyAsyncAnextProtocol<'p>, { - self.am_anext = am_anext::(); - } - - pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { - slots.maybe_push(ffi::Py_am_await, self.am_await.map(|v| v as _)); - slots.maybe_push(ffi::Py_am_aiter, self.am_aiter.map(|v| v as _)); - slots.maybe_push(ffi::Py_am_anext, self.am_anext.map(|v| v as _)); + ffi::PyType_Slot { + slot: ffi::Py_am_anext, + pfunc: py_unarys_func!(PyAsyncAnextProtocol, Self::__anext__) as _, + } } } +impl<'p, T> PyAsyncSlots for T where T: PyAsyncProtocol<'p> {} + +/// Output of `__anext__`. pub enum IterANextOutput { Yield(T), Return(U), @@ -166,11 +175,3 @@ where } } } - -#[inline] -fn am_anext() -> Option -where - T: for<'p> PyAsyncAnextProtocol<'p>, -{ - py_unarys_func!(PyAsyncAnextProtocol, T::__anext__) -} diff --git a/src/class/sequence.rs b/src/class/sequence.rs index bc9f2da4937..ccbc35ace88 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -167,103 +167,119 @@ pub trait PySequenceInplaceRepeatProtocol<'p>: type Result: IntoPyCallbackOutput; } +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PySequenceMethods { - pub fn set_len(&mut self) +pub trait PySequenceSlots { + fn get_len() -> ffi::PyType_Slot where - T: for<'p> PySequenceLenProtocol<'p>, + Self: for<'p> PySequenceLenProtocol<'p>, { - self.sq_length = py_len_func!(PySequenceLenProtocol, T::__len__); + ffi::PyType_Slot { + slot: ffi::Py_sq_length, + pfunc: py_len_func!(PySequenceLenProtocol, Self::__len__) as _, + } } - pub fn set_concat(&mut self) + fn get_concat() -> ffi::PyType_Slot where - T: for<'p> PySequenceConcatProtocol<'p>, + Self: for<'p> PySequenceConcatProtocol<'p>, { - self.sq_concat = py_binary_func!(PySequenceConcatProtocol, T::__concat__); + ffi::PyType_Slot { + slot: ffi::Py_sq_length, + pfunc: py_binary_func!(PySequenceConcatProtocol, Self::__concat__) as _, + } } - pub fn set_repeat(&mut self) + fn get_repeat() -> ffi::PyType_Slot where - T: for<'p> PySequenceRepeatProtocol<'p>, + Self: for<'p> PySequenceRepeatProtocol<'p>, { - self.sq_repeat = py_ssizearg_func!(PySequenceRepeatProtocol, T::__repeat__); + ffi::PyType_Slot { + slot: ffi::Py_sq_length, + pfunc: py_ssizearg_func!(PySequenceRepeatProtocol, Self::__repeat__) as _, + } } - pub fn set_getitem(&mut self) + fn get_getitem() -> ffi::PyType_Slot where - T: for<'p> PySequenceGetItemProtocol<'p>, + Self: for<'p> PySequenceGetItemProtocol<'p>, { - self.sq_item = py_ssizearg_func!(PySequenceGetItemProtocol, T::__getitem__); + ffi::PyType_Slot { + slot: ffi::Py_sq_length, + pfunc: py_ssizearg_func!(PySequenceGetItemProtocol, Self::__getitem__) as _, + } } - pub fn set_setitem(&mut self) + fn get_setitem() -> ffi::PyType_Slot where - T: for<'p> PySequenceSetItemProtocol<'p>, + Self: for<'p> PySequenceSetItemProtocol<'p>, { - self.sq_ass_item = sq_ass_item_impl::set_item::(); + ffi::PyType_Slot { + slot: ffi::Py_sq_length, + pfunc: sq_ass_item_impl::set_item::() as _, + } } - pub fn set_delitem(&mut self) + fn get_delitem() -> ffi::PyType_Slot where - T: for<'p> PySequenceDelItemProtocol<'p>, + Self: for<'p> PySequenceDelItemProtocol<'p>, { - self.sq_ass_item = sq_ass_item_impl::del_item::(); + ffi::PyType_Slot { + slot: ffi::Py_sq_length, + pfunc: sq_ass_item_impl::del_item::() as _, + } } - pub fn set_setdelitem(&mut self) + fn get_setdelitem() -> ffi::PyType_Slot where - T: for<'p> PySequenceDelItemProtocol<'p> + for<'p> PySequenceSetItemProtocol<'p>, + Self: for<'p> PySequenceDelItemProtocol<'p> + for<'p> PySequenceSetItemProtocol<'p>, { - self.sq_ass_item = sq_ass_item_impl::set_del_item::(); + ffi::PyType_Slot { + slot: ffi::Py_sq_length, + pfunc: sq_ass_item_impl::set_del_item::() as _, + } } - pub fn set_contains(&mut self) + fn get_contains() -> ffi::PyType_Slot where - T: for<'p> PySequenceContainsProtocol<'p>, + Self: for<'p> PySequenceContainsProtocol<'p>, { - self.sq_contains = py_binary_func!(PySequenceContainsProtocol, T::__contains__, c_int); + ffi::PyType_Slot { + slot: ffi::Py_sq_length, + pfunc: py_binary_func!(PySequenceContainsProtocol, Self::__contains__, c_int) as _, + } } - pub fn set_inplace_concat(&mut self) + fn get_inplace_concat() -> ffi::PyType_Slot where - T: for<'p> PySequenceInplaceConcatProtocol<'p>, + Self: for<'p> PySequenceInplaceConcatProtocol<'p>, { - self.sq_inplace_concat = py_binary_func!( - PySequenceInplaceConcatProtocol, - T::__inplace_concat__, - *mut ffi::PyObject, - call_mut - ) + ffi::PyType_Slot { + slot: ffi::Py_sq_length, + pfunc: py_binary_func!( + PySequenceInplaceConcatProtocol, + Self::__inplace_concat__, + *mut ffi::PyObject, + call_mut + ) as _, + } } - pub fn set_inplace_repeat(&mut self) + fn get_inplace_repeat() -> ffi::PyType_Slot where - T: for<'p> PySequenceInplaceRepeatProtocol<'p>, + Self: for<'p> PySequenceInplaceRepeatProtocol<'p>, { - self.sq_inplace_repeat = py_ssizearg_func!( - PySequenceInplaceRepeatProtocol, - T::__inplace_repeat__, - call_mut - ) - } - - pub(crate) fn update_slots(&self, slots: &mut crate::pyclass::TypeSlots) { - slots.maybe_push(ffi::Py_sq_length, self.sq_length.map(|v| v as _)); - slots.maybe_push(ffi::Py_sq_concat, self.sq_concat.map(|v| v as _)); - slots.maybe_push(ffi::Py_sq_repeat, self.sq_repeat.map(|v| v as _)); - slots.maybe_push(ffi::Py_sq_item, self.sq_item.map(|v| v as _)); - slots.maybe_push(ffi::Py_sq_ass_item, self.sq_ass_item.map(|v| v as _)); - slots.maybe_push(ffi::Py_sq_contains, self.sq_contains.map(|v| v as _)); - slots.maybe_push( - ffi::Py_sq_inplace_concat, - self.sq_inplace_concat.map(|v| v as _), - ); - slots.maybe_push( - ffi::Py_sq_inplace_repeat, - self.sq_inplace_repeat.map(|v| v as _), - ); + ffi::PyType_Slot { + slot: ffi::Py_sq_length, + pfunc: py_ssizearg_func!( + PySequenceInplaceRepeatProtocol, + Self::__inplace_repeat__, + call_mut + ) as _, + } } } +impl<'p, T> PySequenceSlots for T where T: PySequenceProtocol<'p> {} + /// It can be possible to delete and set items (PySequenceSetItemProtocol and /// PySequenceDelItemProtocol implemented), only to delete (PySequenceDelItemProtocol implemented) /// or no deleting or setting is possible mod sq_ass_item_impl { use super::*; - pub(super) fn set_item() -> Option + pub(super) fn set_item() -> ffi::ssizeobjargproc where T: for<'p> PySequenceSetItemProtocol<'p>, { @@ -291,10 +307,10 @@ mod sq_ass_item_impl { crate::callback::convert(py, slf.__setitem__(key.into(), value)) }) } - Some(wrap::) + wrap:: } - pub(super) fn del_item() -> Option + pub(super) fn del_item() -> ffi::ssizeobjargproc where T: for<'p> PySequenceDelItemProtocol<'p>, { @@ -319,10 +335,10 @@ mod sq_ass_item_impl { } }) } - Some(wrap::) + wrap:: } - pub(super) fn set_del_item() -> Option + pub(super) fn set_del_item() -> ffi::ssizeobjargproc where T: for<'p> PySequenceSetItemProtocol<'p> + for<'p> PySequenceDelItemProtocol<'p>, { @@ -347,6 +363,6 @@ mod sq_ass_item_impl { } }) } - Some(wrap::) + wrap:: } } diff --git a/src/pyclass.rs b/src/pyclass.rs index c13f717592a..739e1528d13 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -147,8 +147,8 @@ impl TypeSlots { self.0.push(ffi::PyType_Slot { slot, pfunc }); } pub(crate) fn maybe_push(&mut self, slot: c_int, value: Option<*mut c_void>) { - if let Some(v) = value { - self.push(slot, v); + if let Some(pfunc) = value { + self.push(slot, pfunc); } } } @@ -189,6 +189,7 @@ where let (new, call, methods) = py_class_method_defs::(); slots.maybe_push(ffi::Py_tp_new, new.map(|v| v as _)); slots.maybe_push(ffi::Py_tp_call, call.map(|v| v as _)); + // normal methods if !methods.is_empty() { slots.push(ffi::Py_tp_methods, into_raw(methods)); @@ -200,37 +201,12 @@ where slots.push(ffi::Py_tp_getset, into_raw(props)); } - // basic methods - if let Some(basic) = T::basic_methods() { - unsafe { basic.as_ref() }.update_slots(&mut slots); - } - // number methods - if let Some(number) = T::number_methods() { - unsafe { number.as_ref() }.update_slots(&mut slots); - } - // iterator methods - if let Some(iter) = T::iter_methods() { - unsafe { iter.as_ref() }.update_slots(&mut slots); - } - // mapping methods - if let Some(mapping) = T::mapping_methods() { - unsafe { mapping.as_ref() }.update_slots(&mut slots); - } - // sequence methods - if let Some(sequence) = T::sequence_methods() { - unsafe { sequence.as_ref() }.update_slots(&mut slots); - } - // descriptor protocol - if let Some(descr) = T::descr_methods() { - unsafe { descr.as_ref() }.update_slots(&mut slots); - } - // async methods - if let Some(asnc) = T::async_methods() { - unsafe { asnc.as_ref() }.update_slots(&mut slots); - } - // gc methods - if let Some(gc) = T::gc_methods() { - unsafe { gc.as_ref() }.update_slots(&mut slots); + // protocol methods + let mut has_gc_methods = false; + for slot in T::get_type_slots() { + has_gc_methods |= slot.slot == ffi::Py_tp_clear; + has_gc_methods |= slot.slot == ffi::Py_tp_traverse; + slots.0.push(slot); } slots.push(0, ptr::null_mut()); @@ -238,7 +214,7 @@ where name: get_type_name::(module_name)?, basicsize: std::mem::size_of::() as c_int, itemsize: 0, - flags: py_class_flags::(), + flags: py_class_flags::(has_gc_methods), slots: slots.0.as_mut_slice().as_mut_ptr(), }; @@ -274,10 +250,10 @@ fn tp_init_additional(type_object: *mut ffi::PyTypeObject) { } } - if let Some(buffer) = T::buffer_methods() { + if let Some(buffer) = T::get_buffer() { 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; + (*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer; + (*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer; } } // __dict__ support @@ -297,8 +273,8 @@ fn tp_init_additional(type_object: *mut ffi::PyTypeObject) { #[cfg(Py_LIMITED_API)] fn tp_init_additional(_type_object: *mut ffi::PyTypeObject) {} -fn py_class_flags() -> c_uint { - let mut flags = if T::gc_methods().is_some() || T::FLAGS & type_flags::GC != 0 { +fn py_class_flags(has_gc_methods: bool) -> c_uint { + let mut flags = if has_gc_methods || T::FLAGS & type_flags::GC != 0 { ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC } else { ffi::Py_TPFLAGS_DEFAULT From 6627658127cfe529f83c4c7c4bd6d1b8fcbd4527 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Tue, 20 Oct 2020 00:24:45 +0900 Subject: [PATCH 34/36] Renew proc-macros for new `#[pyproto]` backend --- guide/src/class.md | 20 ++- pyo3-derive-backend/src/defs.rs | 270 ++++++++++++++--------------- pyo3-derive-backend/src/pyclass.rs | 33 +++- pyo3-derive-backend/src/pyproto.rs | 70 ++++---- src/class/basic.rs | 2 +- src/class/buffer.rs | 85 ++++----- src/class/descr.rs | 3 +- src/class/number.rs | 8 +- src/class/proto_methods.rs | 29 +++- src/class/pyasync.rs | 13 +- src/class/sequence.rs | 57 +----- 11 files changed, 279 insertions(+), 311 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index ecdcc76f919..bd3ad360b78 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -772,13 +772,23 @@ impl pyo3::class::methods::HasMethodsInventory for MyClass { } pyo3::inventory::collect!(Pyo3MethodsInventoryForMyClass); -impl pyo3::class::proto_methods::HasProtoRegistry for MyClass { - fn registry() -> &'static pyo3::class::proto_methods::PyProtoRegistry { - static REGISTRY: pyo3::class::proto_methods::PyProtoRegistry - = pyo3::class::proto_methods::PyProtoRegistry::new(); - ®ISTRY + +pub struct Pyo3ProtoInventoryForMyClass { + def: pyo3::class::proto_methods::PyProtoMethodDef, +} +impl pyo3::class::proto_methods::PyProtoInventory for Pyo3ProtoInventoryForMyClass { + fn new(def: pyo3::class::proto_methods::PyProtoMethodDef) -> Self { + Self { def } + } + fn get(&'static self) -> &'static pyo3::class::proto_methods::PyProtoMethodDef { + &self.def } } +impl pyo3::class::proto_methods::HasProtoInventory for MyClass { + type ProtoMethods = Pyo3ProtoInventoryForMyClass; +} +pyo3::inventory::collect!(Pyo3ProtoInventoryForMyClass); + impl pyo3::pyclass::PyClassSend for MyClass { type ThreadChecker = pyo3::pyclass::ThreadCheckerStub; diff --git a/pyo3-derive-backend/src/defs.rs b/pyo3-derive-backend/src/defs.rs index 6199a722b8c..6a1d628c8b6 100644 --- a/pyo3-derive-backend/src/defs.rs +++ b/pyo3-derive-backend/src/defs.rs @@ -6,16 +6,14 @@ use std::collections::HashSet; pub struct Proto { /// The name of this protocol. E.g., Iter. pub name: &'static str, - /// The name of slot table. E.g., PyIterMethods. - pub slot_table: &'static str, - /// The name of the setter used to set the table to `PyProtoRegistry`. - pub set_slot_table: &'static str, + /// Extension trait that has `get_*` methods + pub extension_trait: &'static str, /// All methods. pub methods: &'static [MethodProto], /// All methods registered as normal methods like `#[pymethods]`. pub py_methods: &'static [PyMethod], /// All methods registered to the slot table. - slot_setters: &'static [SlotSetter], + slot_getters: &'static [SlotGetter], } impl Proto { @@ -32,13 +30,13 @@ impl Proto { self.py_methods.iter().find(|m| query == m.name) } // Since the order matters, we expose only the iterator instead of the slice. - pub(crate) fn setters( + pub(crate) fn slot_getters( &self, mut implemented_protocols: HashSet, ) -> impl Iterator { - self.slot_setters.iter().filter_map(move |setter| { + self.slot_getters.iter().filter_map(move |getter| { // If any required method is not implemented, we skip this setter. - if setter + if getter .proto_names .iter() .any(|name| !implemented_protocols.contains(*name)) @@ -47,10 +45,10 @@ impl Proto { } // To use 'paired' setter in priority, we remove used protocols. // For example, if set_add_radd is already used, we shouldn't use set_add and set_radd. - for name in setter.proto_names { + for name in getter.proto_names { implemented_protocols.remove(*name); } - Some(setter.set_function) + Some(getter.get_function) }) } } @@ -82,27 +80,26 @@ impl PyMethod { } /// Represents a setter used to register a method to the method table. -struct SlotSetter { +struct SlotGetter { /// Protocols necessary for invoking this setter. /// E.g., we need `__setattr__` and `__delattr__` for invoking `set_setdelitem`. pub proto_names: &'static [&'static str], /// The name of the setter called to the method table. - pub set_function: &'static str, + pub get_function: &'static str, } -impl SlotSetter { - const fn new(names: &'static [&'static str], set_function: &'static str) -> Self { - SlotSetter { +impl SlotGetter { + const fn new(names: &'static [&'static str], get_function: &'static str) -> Self { + SlotGetter { proto_names: names, - set_function, + get_function, } } } pub const OBJECT: Proto = Proto { name: "Object", - slot_table: "pyo3::class::basic::PyObjectMethods", - set_slot_table: "set_basic_methods", + extension_trait: "pyo3::class::basic::PyBasicSlots", methods: &[ MethodProto::Binary { name: "__getattr__", @@ -156,23 +153,22 @@ pub const OBJECT: Proto = Proto { PyMethod::new("__bytes__", "pyo3::class::basic::BytesProtocolImpl"), PyMethod::new("__unicode__", "pyo3::class::basic::UnicodeProtocolImpl"), ], - slot_setters: &[ - SlotSetter::new(&["__str__"], "set_str"), - SlotSetter::new(&["__repr__"], "set_repr"), - SlotSetter::new(&["__hash__"], "set_hash"), - SlotSetter::new(&["__getattr__"], "set_getattr"), - SlotSetter::new(&["__richcmp__"], "set_richcompare"), - SlotSetter::new(&["__setattr__", "__delattr__"], "set_setdelattr"), - SlotSetter::new(&["__setattr__"], "set_setattr"), - SlotSetter::new(&["__delattr__"], "set_delattr"), - SlotSetter::new(&["__bool__"], "set_bool"), + slot_getters: &[ + SlotGetter::new(&["__str__"], "get_str"), + SlotGetter::new(&["__repr__"], "get_repr"), + SlotGetter::new(&["__hash__"], "get_hash"), + SlotGetter::new(&["__getattr__"], "get_getattr"), + SlotGetter::new(&["__richcmp__"], "get_richcompare"), + SlotGetter::new(&["__setattr__", "__delattr__"], "get_setdelattr"), + SlotGetter::new(&["__setattr__"], "get_setattr"), + SlotGetter::new(&["__delattr__"], "get_delattr"), + SlotGetter::new(&["__bool__"], "get_bool"), ], }; pub const ASYNC: Proto = Proto { name: "Async", - slot_table: "pyo3::class::pyasync::PyAsyncMethods", - set_slot_table: "set_async_methods", + extension_trait: "pyo3::class::pyasync::PyAsyncSlots", methods: &[ MethodProto::UnaryS { name: "__await__", @@ -211,17 +207,16 @@ pub const ASYNC: Proto = Proto { "pyo3::class::pyasync::PyAsyncAexitProtocolImpl", ), ], - slot_setters: &[ - SlotSetter::new(&["__await__"], "set_await"), - SlotSetter::new(&["__aiter__"], "set_aiter"), - SlotSetter::new(&["__anext__"], "set_anext"), + slot_getters: &[ + SlotGetter::new(&["__await__"], "get_await"), + SlotGetter::new(&["__aiter__"], "get_aiter"), + SlotGetter::new(&["__anext__"], "get_anext"), ], }; pub const BUFFER: Proto = Proto { name: "Buffer", - slot_table: "pyo3::class::buffer::PyBufferProcs", - set_slot_table: "set_buffer_methods", + extension_trait: "pyo3::class::buffer::PyBufferSlots", methods: &[ MethodProto::Unary { name: "bf_getbuffer", @@ -233,16 +228,15 @@ pub const BUFFER: Proto = Proto { }, ], py_methods: &[], - slot_setters: &[ - SlotSetter::new(&["bf_getbuffer"], "set_getbuffer"), - SlotSetter::new(&["bf_releasebuffer"], "set_releasebuffer"), + slot_getters: &[ + SlotGetter::new(&["bf_getbuffer"], "get_getbuffer"), + SlotGetter::new(&["bf_releasebuffer"], "get_releasebuffer"), ], }; pub const CONTEXT: Proto = Proto { name: "Context", - slot_table: "", - set_slot_table: "", + extension_trait: "", methods: &[ MethodProto::Unary { name: "__enter__", @@ -266,13 +260,12 @@ pub const CONTEXT: Proto = Proto { "pyo3::class::context::PyContextExitProtocolImpl", ), ], - slot_setters: &[], + slot_getters: &[], }; pub const GC: Proto = Proto { name: "GC", - slot_table: "pyo3::class::gc::PyGCMethods", - set_slot_table: "set_gc_methods", + extension_trait: "pyo3::class::gc::PyGCSlots", methods: &[ MethodProto::Free { name: "__traverse__", @@ -284,16 +277,15 @@ pub const GC: Proto = Proto { }, ], py_methods: &[], - slot_setters: &[ - SlotSetter::new(&["__traverse__"], "set_traverse"), - SlotSetter::new(&["__clear__"], "set_clear"), + slot_getters: &[ + SlotGetter::new(&["__traverse__"], "get_traverse"), + SlotGetter::new(&["__clear__"], "get_clear"), ], }; pub const DESCR: Proto = Proto { name: "Descriptor", - slot_table: "pyo3::class::descr::PyDescrMethods", - set_slot_table: "set_descr_methods", + extension_trait: "pyo3::class::descr::PyDescrSlots", methods: &[ MethodProto::TernaryS { name: "__get__", @@ -327,16 +319,15 @@ pub const DESCR: Proto = Proto { "pyo3::class::context::PyDescrNameProtocolImpl", ), ], - slot_setters: &[ - SlotSetter::new(&["__get__"], "set_descr_get"), - SlotSetter::new(&["__set__"], "set_descr_set"), + slot_getters: &[ + SlotGetter::new(&["__get__"], "get_descr_get"), + SlotGetter::new(&["__set__"], "get_descr_set"), ], }; pub const ITER: Proto = Proto { name: "Iter", - slot_table: "pyo3::class::iter::PyIterMethods", - set_slot_table: "set_iter_methods", + extension_trait: "pyo3::class::iter::PyIterSlots", py_methods: &[], methods: &[ MethodProto::UnaryS { @@ -350,16 +341,15 @@ pub const ITER: Proto = Proto { proto: "pyo3::class::iter::PyIterNextProtocol", }, ], - slot_setters: &[ - SlotSetter::new(&["__iter__"], "set_iter"), - SlotSetter::new(&["__next__"], "set_iternext"), + slot_getters: &[ + SlotGetter::new(&["__iter__"], "get_iter"), + SlotGetter::new(&["__next__"], "get_iternext"), ], }; pub const MAPPING: Proto = Proto { name: "Mapping", - slot_table: "pyo3::class::mapping::PyMappingMethods", - set_slot_table: "set_mapping_methods", + extension_trait: "pyo3::class::mapping::PyMappingSlots", methods: &[ MethodProto::Unary { name: "__len__", @@ -390,19 +380,18 @@ pub const MAPPING: Proto = Proto { "__reversed__", "pyo3::class::mapping::PyMappingReversedProtocolImpl", )], - slot_setters: &[ - SlotSetter::new(&["__len__"], "set_length"), - SlotSetter::new(&["__getitem__"], "set_getitem"), - SlotSetter::new(&["__setitem__", "__delitem__"], "set_setdelitem"), - SlotSetter::new(&["__setitem__"], "set_setitem"), - SlotSetter::new(&["__delitem__"], "set_delitem"), + slot_getters: &[ + SlotGetter::new(&["__len__"], "get_length"), + SlotGetter::new(&["__getitem__"], "get_getitem"), + SlotGetter::new(&["__setitem__", "__delitem__"], "get_setdelitem"), + SlotGetter::new(&["__setitem__"], "get_setitem"), + SlotGetter::new(&["__delitem__"], "get_delitem"), ], }; pub const SEQ: Proto = Proto { name: "Sequence", - slot_table: "pyo3::class::sequence::PySequenceMethods", - set_slot_table: "set_sequence_methods", + extension_trait: "pyo3::class::sequence::PySequenceSlots", methods: &[ MethodProto::Unary { name: "__len__", @@ -451,24 +440,23 @@ pub const SEQ: Proto = Proto { }, ], py_methods: &[], - slot_setters: &[ - SlotSetter::new(&["__len__"], "set_len"), - SlotSetter::new(&["__concat__"], "set_concat"), - SlotSetter::new(&["__repeat__"], "set_repeat"), - SlotSetter::new(&["__getitem__"], "set_getitem"), - SlotSetter::new(&["__setitem__", "__delitem__"], "set_setdelitem"), - SlotSetter::new(&["__setitem__"], "set_setitem"), - SlotSetter::new(&["__delitem__"], "set_delitem"), - SlotSetter::new(&["__contains__"], "set_contains"), - SlotSetter::new(&["__inplace_concat__"], "set_inplace_concat"), - SlotSetter::new(&["__inplace_repeat__"], "set_inplace_repeat"), + slot_getters: &[ + SlotGetter::new(&["__len__"], "get_len"), + SlotGetter::new(&["__concat__"], "get_concat"), + SlotGetter::new(&["__repeat__"], "get_repeat"), + SlotGetter::new(&["__getitem__"], "get_getitem"), + SlotGetter::new(&["__setitem__", "__delitem__"], "get_setdelitem"), + SlotGetter::new(&["__setitem__"], "get_setitem"), + SlotGetter::new(&["__delitem__"], "get_delitem"), + SlotGetter::new(&["__contains__"], "get_contains"), + SlotGetter::new(&["__inplace_concat__"], "get_inplace_concat"), + SlotGetter::new(&["__inplace_repeat__"], "get_inplace_repeat"), ], }; pub const NUM: Proto = Proto { name: "Number", - slot_table: "pyo3::class::number::PyNumberMethods", - set_slot_table: "set_number_methods", + extension_trait: "pyo3::class::number::PyNumberSlots", methods: &[ MethodProto::BinaryS { name: "__add__", @@ -771,66 +759,66 @@ pub const NUM: Proto = Proto { "pyo3::class::number::PyNumberRoundProtocolImpl", ), ], - slot_setters: &[ - SlotSetter::new(&["__add__", "__radd__"], "set_add_radd"), - SlotSetter::new(&["__add__"], "set_add"), - SlotSetter::new(&["__radd__"], "set_radd"), - SlotSetter::new(&["__sub__", "__rsub__"], "set_sub_rsub"), - SlotSetter::new(&["__sub__"], "set_sub"), - SlotSetter::new(&["__rsub__"], "set_rsub"), - SlotSetter::new(&["__mul__", "__rmul__"], "set_mul_rmul"), - SlotSetter::new(&["__mul__"], "set_mul"), - SlotSetter::new(&["__rmul__"], "set_rmul"), - SlotSetter::new(&["__mod__"], "set_mod"), - SlotSetter::new(&["__divmod__", "__rdivmod__"], "set_divmod_rdivmod"), - SlotSetter::new(&["__divmod__"], "set_divmod"), - SlotSetter::new(&["__rdivmod__"], "set_rdivmod"), - SlotSetter::new(&["__pow__", "__rpow__"], "set_pow_rpow"), - SlotSetter::new(&["__pow__"], "set_pow"), - SlotSetter::new(&["__rpow__"], "set_rpow"), - SlotSetter::new(&["__neg__"], "set_neg"), - SlotSetter::new(&["__pos__"], "set_pos"), - SlotSetter::new(&["__abs__"], "set_abs"), - SlotSetter::new(&["__invert__"], "set_invert"), - SlotSetter::new(&["__lshift__", "__rlshift__"], "set_lshift_rlshift"), - SlotSetter::new(&["__lshift__"], "set_lshift"), - SlotSetter::new(&["__rlshift__"], "set_rlshift"), - SlotSetter::new(&["__rshift__", "__rrshift__"], "set_rshift_rrshift"), - SlotSetter::new(&["__rshift__"], "set_rshift"), - SlotSetter::new(&["__rrshift__"], "set_rrshift"), - SlotSetter::new(&["__and__", "__rand__"], "set_and_rand"), - SlotSetter::new(&["__and__"], "set_and"), - SlotSetter::new(&["__rand__"], "set_rand"), - SlotSetter::new(&["__xor__", "__rxor__"], "set_xor_rxor"), - SlotSetter::new(&["__xor__"], "set_xor"), - SlotSetter::new(&["__rxor__"], "set_rxor"), - SlotSetter::new(&["__or__", "__ror__"], "set_or_ror"), - SlotSetter::new(&["__or__"], "set_or"), - SlotSetter::new(&["__ror__"], "set_ror"), - SlotSetter::new(&["__int__"], "set_int"), - SlotSetter::new(&["__float__"], "set_float"), - SlotSetter::new(&["__iadd__"], "set_iadd"), - SlotSetter::new(&["__isub__"], "set_isub"), - SlotSetter::new(&["__imul__"], "set_imul"), - SlotSetter::new(&["__imod__"], "set_imod"), - SlotSetter::new(&["__ipow__"], "set_ipow"), - SlotSetter::new(&["__ilshift__"], "set_ilshift"), - SlotSetter::new(&["__irshift__"], "set_irshift"), - SlotSetter::new(&["__iand__"], "set_iand"), - SlotSetter::new(&["__ixor__"], "set_ixor"), - SlotSetter::new(&["__ior__"], "set_ior"), - SlotSetter::new(&["__floordiv__", "__rfloordiv__"], "set_floordiv_rfloordiv"), - SlotSetter::new(&["__floordiv__"], "set_floordiv"), - SlotSetter::new(&["__rfloordiv__"], "set_rfloordiv"), - SlotSetter::new(&["__truediv__", "__rtruediv__"], "set_truediv_rtruediv"), - SlotSetter::new(&["__truediv__"], "set_truediv"), - SlotSetter::new(&["__rtruediv__"], "set_rtruediv"), - SlotSetter::new(&["__ifloordiv__"], "set_ifloordiv"), - SlotSetter::new(&["__itruediv__"], "set_itruediv"), - SlotSetter::new(&["__index__"], "set_index"), - SlotSetter::new(&["__matmul__", "__rmatmul__"], "set_matmul_rmatmul"), - SlotSetter::new(&["__matmul__"], "set_matmul"), - SlotSetter::new(&["__rmatmul__"], "set_rmatmul"), - SlotSetter::new(&["__imatmul__"], "set_imatmul"), + slot_getters: &[ + SlotGetter::new(&["__add__", "__radd__"], "get_add_radd"), + SlotGetter::new(&["__add__"], "get_add"), + SlotGetter::new(&["__radd__"], "get_radd"), + SlotGetter::new(&["__sub__", "__rsub__"], "get_sub_rsub"), + SlotGetter::new(&["__sub__"], "get_sub"), + SlotGetter::new(&["__rsub__"], "get_rsub"), + SlotGetter::new(&["__mul__", "__rmul__"], "get_mul_rmul"), + SlotGetter::new(&["__mul__"], "get_mul"), + SlotGetter::new(&["__rmul__"], "get_rmul"), + SlotGetter::new(&["__mod__"], "get_mod"), + SlotGetter::new(&["__divmod__", "__rdivmod__"], "get_divmod_rdivmod"), + SlotGetter::new(&["__divmod__"], "get_divmod"), + SlotGetter::new(&["__rdivmod__"], "get_rdivmod"), + SlotGetter::new(&["__pow__", "__rpow__"], "get_pow_rpow"), + SlotGetter::new(&["__pow__"], "get_pow"), + SlotGetter::new(&["__rpow__"], "get_rpow"), + SlotGetter::new(&["__neg__"], "get_neg"), + SlotGetter::new(&["__pos__"], "get_pos"), + SlotGetter::new(&["__abs__"], "get_abs"), + SlotGetter::new(&["__invert__"], "get_invert"), + SlotGetter::new(&["__lshift__", "__rlshift__"], "get_lshift_rlshift"), + SlotGetter::new(&["__lshift__"], "get_lshift"), + SlotGetter::new(&["__rlshift__"], "get_rlshift"), + SlotGetter::new(&["__rshift__", "__rrshift__"], "get_rshift_rrshift"), + SlotGetter::new(&["__rshift__"], "get_rshift"), + SlotGetter::new(&["__rrshift__"], "get_rrshift"), + SlotGetter::new(&["__and__", "__rand__"], "get_and_rand"), + SlotGetter::new(&["__and__"], "get_and"), + SlotGetter::new(&["__rand__"], "get_rand"), + SlotGetter::new(&["__xor__", "__rxor__"], "get_xor_rxor"), + SlotGetter::new(&["__xor__"], "get_xor"), + SlotGetter::new(&["__rxor__"], "get_rxor"), + SlotGetter::new(&["__or__", "__ror__"], "get_or_ror"), + SlotGetter::new(&["__or__"], "get_or"), + SlotGetter::new(&["__ror__"], "get_ror"), + SlotGetter::new(&["__int__"], "get_int"), + SlotGetter::new(&["__float__"], "get_float"), + SlotGetter::new(&["__iadd__"], "get_iadd"), + SlotGetter::new(&["__isub__"], "get_isub"), + SlotGetter::new(&["__imul__"], "get_imul"), + SlotGetter::new(&["__imod__"], "get_imod"), + SlotGetter::new(&["__ipow__"], "get_ipow"), + SlotGetter::new(&["__ilshift__"], "get_ilshift"), + SlotGetter::new(&["__irshift__"], "get_irshift"), + SlotGetter::new(&["__iand__"], "get_iand"), + SlotGetter::new(&["__ixor__"], "get_ixor"), + SlotGetter::new(&["__ior__"], "get_ior"), + SlotGetter::new(&["__floordiv__", "__rfloordiv__"], "get_floordiv_rfloordiv"), + SlotGetter::new(&["__floordiv__"], "get_floordiv"), + SlotGetter::new(&["__rfloordiv__"], "get_rfloordiv"), + SlotGetter::new(&["__truediv__", "__rtruediv__"], "get_truediv_rtruediv"), + SlotGetter::new(&["__truediv__"], "get_truediv"), + SlotGetter::new(&["__rtruediv__"], "get_rtruediv"), + SlotGetter::new(&["__ifloordiv__"], "get_ifloordiv"), + SlotGetter::new(&["__itruediv__"], "get_itruediv"), + SlotGetter::new(&["__index__"], "get_index"), + SlotGetter::new(&["__matmul__", "__rmatmul__"], "get_matmul_rmatmul"), + SlotGetter::new(&["__matmul__"], "get_matmul"), + SlotGetter::new(&["__rmatmul__"], "get_rmatmul"), + SlotGetter::new(&["__imatmul__"], "get_imatmul"), ], }; diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index 032935d5776..aca7e7d46c7 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -234,16 +234,31 @@ fn impl_methods_inventory(cls: &syn::Ident) -> TokenStream { } } -/// Implement `HasProtoRegistry` for the class for lazy protocol initialization. -fn impl_proto_registry(cls: &syn::Ident) -> TokenStream { +/// Implement `HasProtoInventory` for the class for lazy protocol initialization. +fn impl_proto_inventory(cls: &syn::Ident) -> TokenStream { + // Try to build a unique type for better error messages + let name = format!("Pyo3ProtoInventoryFor{}", cls); + let inventory_cls = syn::Ident::new(&name, Span::call_site()); + quote! { - impl pyo3::class::proto_methods::HasProtoRegistry for #cls { - fn registry() -> &'static pyo3::class::proto_methods::PyProtoRegistry { - static REGISTRY: pyo3::class::proto_methods::PyProtoRegistry - = pyo3::class::proto_methods::PyProtoRegistry::new(); - ®ISTRY + #[doc(hidden)] + pub struct #inventory_cls { + def: pyo3::class::proto_methods::PyProtoMethodDef, + } + impl pyo3::class::proto_methods::PyProtoInventory for #inventory_cls { + fn new(def: pyo3::class::proto_methods::PyProtoMethodDef) -> Self { + Self { def } } + fn get(&'static self) -> &'static pyo3::class::proto_methods::PyProtoMethodDef { + &self.def + } + } + + impl pyo3::class::proto_methods::HasProtoInventory for #cls { + type ProtoMethods = #inventory_cls; } + + pyo3::inventory::collect!(#inventory_cls); } } @@ -351,7 +366,7 @@ fn impl_class( }; let impl_inventory = impl_methods_inventory(&cls); - let impl_proto_registry = impl_proto_registry(&cls); + let impl_proto_inventory = impl_proto_inventory(&cls); let base = &attr.base; let flags = &attr.flags; @@ -440,7 +455,7 @@ fn impl_class( #impl_inventory - #impl_proto_registry + #impl_proto_inventory #extra diff --git a/pyo3-derive-backend/src/pyproto.rs b/pyo3-derive-backend/src/pyproto.rs index 7c6cbf1a7ae..fe60e7a14fa 100644 --- a/pyo3-derive-backend/src/pyproto.rs +++ b/pyo3-derive-backend/src/pyproto.rs @@ -106,16 +106,16 @@ fn impl_proto_impl( } } } - let inventory_submission = inventory_submission(py_methods, ty); - let slot_initialization = slot_initialization(method_names, ty, proto)?; + let normal_methods = submit_normal_methods(py_methods, ty); + let protocol_methods = submit_protocol_methods(method_names, ty, proto)?; Ok(quote! { #trait_impls - #inventory_submission - #slot_initialization + #normal_methods + #protocol_methods }) } -fn inventory_submission(py_methods: Vec, ty: &syn::Type) -> TokenStream { +fn submit_normal_methods(py_methods: Vec, ty: &syn::Type) -> TokenStream { if py_methods.is_empty() { return quote! {}; } @@ -129,42 +129,46 @@ fn inventory_submission(py_methods: Vec, ty: &syn::Type) -> TokenSt } } -fn slot_initialization( +fn submit_protocol_methods( method_names: HashSet, ty: &syn::Type, proto: &defs::Proto, ) -> syn::Result { - // Collect initializers - let mut initializers: Vec = vec![]; - for setter in proto.setters(method_names) { - // Add slot methods to PyProtoRegistry - let set = syn::Ident::new(setter, Span::call_site()); - initializers.push(quote! { table.#set::<#ty>(); }); + if proto.extension_trait == "" { + return Ok(quote! {}); + } + let ext_trait: syn::Path = syn::parse_str(proto.extension_trait)?; + let mut tokens: Vec = vec![]; + if proto.name == "Buffer" { + // For buffer, we construct `PyProtoMethods` from PyBufferProcs + tokens.push(quote! { + let mut proto_methods = pyo3::ffi::PyBufferProcs::default(); + }); + for getter in proto.slot_getters(method_names) { + let get = syn::Ident::new(getter, Span::call_site()); + let field = syn::Ident::new(&format!("bf_{}", &getter[4..]), Span::call_site()); + tokens.push(quote! { proto_methods.#field = Some(<#ty as #ext_trait>::#get()); }); + } + } else { + // For other protocols, we construct `PyProtoMethods` from Vec + tokens.push(quote! { let mut proto_methods = vec![]; }); + for getter in proto.slot_getters(method_names) { + let get = syn::Ident::new(getter, Span::call_site()); + tokens.push(quote! { proto_methods.push(<#ty as #ext_trait>::#get()); }); + } } - if initializers.is_empty() { + if tokens.len() <= 1 { return Ok(quote! {}); } - let table: syn::Path = syn::parse_str(proto.slot_table)?; - let set = syn::Ident::new(proto.set_slot_table, Span::call_site()); - let ty_hash = typename_hash(ty); - let init = syn::Ident::new( - &format!("__init_{}_{}", proto.name, ty_hash), - Span::call_site(), - ); Ok(quote! { - #[allow(non_snake_case)] - #[pyo3::ctor::ctor] - fn #init() { - let mut table = #table::default(); - #(#initializers)* - <#ty as pyo3::class::proto_methods::HasProtoRegistry>::registry().#set(table); + pyo3::inventory::submit! { + #![crate = pyo3] { + type Inventory = + <#ty as pyo3::class::proto_methods::HasProtoInventory>::ProtoMethods; + ::new( + { #(#tokens)* proto_methods.into() } + ) + } } }) } - -fn typename_hash(ty: &syn::Type) -> u64 { - use std::hash::{Hash, Hasher}; - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - ty.hash(&mut hasher); - hasher.finish() -} diff --git a/src/class/basic.rs b/src/class/basic.rs index 9223b8e7f80..24ff4135eb6 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -181,7 +181,7 @@ pub trait PyBasicSlots { Self: for<'p> PyObjectRichcmpProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_tp_getattro, + slot: ffi::Py_tp_richcompare, pfunc: tp_richcompare::() as _, } } diff --git a/src/class/buffer.rs b/src/class/buffer.rs index 99a382020c0..89b0a880bbe 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -8,16 +8,6 @@ use crate::callback::IntoPyCallbackOutput; use crate::{ffi, PyCell, PyClass, PyRefMut}; use std::os::raw::c_int; -#[cfg(Py_LIMITED_API)] -#[derive(Clone, Default)] -pub struct PyBufferProcs { - pub bf_getbuffer: Option, - pub bf_releasebuffer: Option, -} - -#[cfg(not(Py_LIMITED_API))] -pub use ffi::PyBufferProcs; - /// Buffer protocol interface /// /// For more information check [buffer protocol](https://docs.python.org/3/c-api/buffer.html) @@ -47,55 +37,46 @@ pub trait PyBufferReleaseBufferProtocol<'p>: PyBufferProtocol<'p> { type Result: IntoPyCallbackOutput<()>; } -/// Set functions used by `#[pyproto]`. +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyBufferProcs { - pub fn set_getbuffer(&mut self) +pub trait PyBufferSlots { + fn get_getbuffer() -> ffi::getbufferproc where - T: for<'p> PyBufferGetBufferProtocol<'p>, + Self: for<'p> PyBufferGetBufferProtocol<'p>, { - self.bf_getbuffer = bf_getbuffer::(); - } - pub fn set_releasebuffer(&mut self) - where - T: for<'p> PyBufferReleaseBufferProtocol<'p>, - { - self.bf_releasebuffer = bf_releasebuffer::(); - } -} + unsafe extern "C" fn wrap( + slf: *mut ffi::PyObject, + arg1: *mut ffi::Py_buffer, + arg2: c_int, + ) -> c_int + where + T: for<'p> PyBufferGetBufferProtocol<'p>, + { + crate::callback_body!(py, { + let slf = py.from_borrowed_ptr::>(slf); + T::bf_getbuffer(slf.try_borrow_mut()?, arg1, arg2).convert(py) + }) + } -fn bf_getbuffer() -> Option -where - T: for<'p> PyBufferGetBufferProtocol<'p>, -{ - unsafe extern "C" fn wrap( - slf: *mut ffi::PyObject, - arg1: *mut ffi::Py_buffer, - arg2: c_int, - ) -> c_int - where - T: for<'p> PyBufferGetBufferProtocol<'p>, - { - crate::callback_body!(py, { - let slf = py.from_borrowed_ptr::>(slf); - T::bf_getbuffer(slf.try_borrow_mut()?, arg1, arg2).convert(py) - }) + wrap:: } - Some(wrap::) -} -fn bf_releasebuffer() -> Option -where - T: for<'p> PyBufferReleaseBufferProtocol<'p>, -{ - unsafe extern "C" fn wrap(slf: *mut ffi::PyObject, arg1: *mut ffi::Py_buffer) + fn get_releasebuffer() -> ffi::releasebufferproc where - T: for<'p> PyBufferReleaseBufferProtocol<'p>, + Self: for<'p> PyBufferReleaseBufferProtocol<'p>, { - crate::callback_body!(py, { - let slf = py.from_borrowed_ptr::>(slf); - T::bf_releasebuffer(slf.try_borrow_mut()?, arg1).convert(py) - }) + unsafe extern "C" fn wrap(slf: *mut ffi::PyObject, arg1: *mut ffi::Py_buffer) + where + T: for<'p> PyBufferReleaseBufferProtocol<'p>, + { + crate::callback_body!(py, { + let slf = py.from_borrowed_ptr::>(slf); + T::bf_releasebuffer(slf.try_borrow_mut()?, arg1).convert(py) + }) + } + + wrap:: } - Some(wrap::) } + +impl<'p, T> PyBufferSlots for T where T: PyBufferProtocol<'p> {} diff --git a/src/class/descr.rs b/src/class/descr.rs index 2c6a7009413..6f65dfee0af 100644 --- a/src/class/descr.rs +++ b/src/class/descr.rs @@ -8,6 +8,7 @@ use crate::callback::IntoPyCallbackOutput; use crate::types::PyAny; use crate::{ffi, FromPyObject, PyClass, PyObject}; +use std::os::raw::c_int; /// Descriptor interface #[allow(unused_variables)] @@ -88,7 +89,7 @@ pub trait PyDescrSlots { { ffi::PyType_Slot { slot: ffi::Py_tp_descr_set, - pfunc: py_ternarys_func!(PyDescrSetProtocol, Self::__set__) as _, + pfunc: py_ternarys_func!(PyDescrSetProtocol, Self::__set__, c_int) as _, } } } diff --git a/src/class/number.rs b/src/class/number.rs index b095da9a777..edc5ae63c43 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -825,17 +825,17 @@ pub trait PyNumberSlots { } } - fn get_pos() -> ffi::PyType_Slot + fn get_pos() -> ffi::PyType_Slot where Self: for<'p> PyNumberPosProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_nb_absolute, + slot: ffi::Py_nb_positive, pfunc: py_unary_func!(PyNumberPosProtocol, Self::__pos__) as _, } } - fn get_abs() -> ffi::PyType_Slot + fn get_abs() -> ffi::PyType_Slot where Self: for<'p> PyNumberAbsProtocol<'p>, { @@ -845,7 +845,7 @@ pub trait PyNumberSlots { } } - fn get_invert() -> ffi::PyType_Slot + fn get_invert() -> ffi::PyType_Slot where Self: for<'p> PyNumberInvertProtocol<'p>, { diff --git a/src/class/proto_methods.rs b/src/class/proto_methods.rs index 6093018b74f..09a6d966b77 100644 --- a/src/class/proto_methods.rs +++ b/src/class/proto_methods.rs @@ -1,5 +1,12 @@ -use crate::class::buffer::PyBufferProcs; use crate::ffi; +#[cfg(not(Py_LIMITED_API))] +use crate::ffi::PyBufferProcs; + +/// ABI3 doesn't have buffer APIs, so here we define the empty one. +#[cfg(Py_LIMITED_API)] +#[doc(hidden)] +#[derive(Clone)] +pub struct PyBufferProcs; // Note(kngwyu): default implementations are for rust-numpy. Please don't remove them. pub trait PyProtoMethods { @@ -17,21 +24,33 @@ pub enum PyProtoMethodDef { Buffer(PyBufferProcs), } +impl From> for PyProtoMethodDef { + fn from(slots: Vec) -> Self { + PyProtoMethodDef::Slots(slots) + } +} + +impl From for PyProtoMethodDef { + fn from(buffer_procs: PyBufferProcs) -> Self { + PyProtoMethodDef::Buffer(buffer_procs) + } +} + #[doc(hidden)] #[cfg(feature = "macros")] -pub trait PyProtoMethodsInventory: inventory::Collect { +pub trait PyProtoInventory: inventory::Collect { fn new(methods: PyProtoMethodDef) -> Self; fn get(&'static self) -> &'static PyProtoMethodDef; } #[doc(hidden)] #[cfg(feature = "macros")] -pub trait HasProtoMethodsInventory { - type ProtoMethods: PyProtoMethodsInventory; +pub trait HasProtoInventory { + type ProtoMethods: PyProtoInventory; } #[cfg(feature = "macros")] -impl PyProtoMethods for T { +impl PyProtoMethods for T { fn get_type_slots() -> Vec { inventory::iter:: .into_iter() diff --git a/src/class/pyasync.rs b/src/class/pyasync.rs index 78cc552e322..b474e5d955f 100644 --- a/src/class/pyasync.rs +++ b/src/class/pyasync.rs @@ -13,17 +13,6 @@ use crate::derive_utils::TryFromPyCell; use crate::err::PyResult; use crate::{ffi, IntoPy, IntoPyPointer, PyClass, PyObject, Python}; -#[cfg(Py_LIMITED_API)] -#[derive(Clone, Default)] -pub struct PyAsyncMethods { - pub am_await: Option, - pub am_aiter: Option, - pub am_anext: Option, -} - -#[cfg(not(Py_LIMITED_API))] -pub use ffi::PyAsyncMethods; - /// Python Async/Await support interface. /// /// Each method in this trait corresponds to Python async/await implementation. @@ -114,7 +103,7 @@ pub trait PyAsyncSlots { Self: for<'p> PyAsyncAiterProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_am_await, + slot: ffi::Py_am_aiter, pfunc: py_unarys_func!(PyAsyncAiterProtocol, Self::__aiter__) as _, } } diff --git a/src/class/sequence.rs b/src/class/sequence.rs index ccbc35ace88..449d0fbc069 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -8,45 +8,6 @@ use crate::conversion::{FromPyObject, IntoPy}; use crate::err::PyErr; use crate::{exceptions, ffi, PyAny, PyCell, PyClass, PyObject}; use std::os::raw::c_int; -#[cfg(Py_LIMITED_API)] -use std::os::raw::c_void; - -#[cfg(Py_LIMITED_API)] -#[derive(Clone)] -pub struct PySequenceMethods { - pub sq_length: Option, - pub sq_concat: Option, - pub sq_repeat: Option, - pub sq_item: Option, - #[allow(dead_code)] - pub was_sq_slice: *mut c_void, - pub sq_ass_item: Option, - #[allow(dead_code)] - pub was_sq_ass_slice: *mut c_void, - pub sq_contains: Option, - pub sq_inplace_concat: Option, - pub sq_inplace_repeat: Option, -} - -#[cfg(not(Py_LIMITED_API))] -pub use ffi::PySequenceMethods; - -impl Default for PySequenceMethods { - fn default() -> Self { - Self { - sq_length: None, - sq_concat: None, - sq_repeat: None, - sq_item: None, - was_sq_slice: std::ptr::null_mut(), - sq_ass_item: None, - was_sq_ass_slice: std::ptr::null_mut(), - sq_contains: None, - sq_inplace_concat: None, - sq_inplace_repeat: None, - } - } -} /// Sequence interface #[allow(unused_variables)] @@ -184,7 +145,7 @@ pub trait PySequenceSlots { Self: for<'p> PySequenceConcatProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_sq_length, + slot: ffi::Py_sq_concat, pfunc: py_binary_func!(PySequenceConcatProtocol, Self::__concat__) as _, } } @@ -193,7 +154,7 @@ pub trait PySequenceSlots { Self: for<'p> PySequenceRepeatProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_sq_length, + slot: ffi::Py_sq_repeat, pfunc: py_ssizearg_func!(PySequenceRepeatProtocol, Self::__repeat__) as _, } } @@ -202,7 +163,7 @@ pub trait PySequenceSlots { Self: for<'p> PySequenceGetItemProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_sq_length, + slot: ffi::Py_sq_item, pfunc: py_ssizearg_func!(PySequenceGetItemProtocol, Self::__getitem__) as _, } } @@ -211,7 +172,7 @@ pub trait PySequenceSlots { Self: for<'p> PySequenceSetItemProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_sq_length, + slot: ffi::Py_sq_ass_item, pfunc: sq_ass_item_impl::set_item::() as _, } } @@ -220,7 +181,7 @@ pub trait PySequenceSlots { Self: for<'p> PySequenceDelItemProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_sq_length, + slot: ffi::Py_sq_ass_item, pfunc: sq_ass_item_impl::del_item::() as _, } } @@ -229,7 +190,7 @@ pub trait PySequenceSlots { Self: for<'p> PySequenceDelItemProtocol<'p> + for<'p> PySequenceSetItemProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_sq_length, + slot: ffi::Py_sq_ass_item, pfunc: sq_ass_item_impl::set_del_item::() as _, } } @@ -238,7 +199,7 @@ pub trait PySequenceSlots { Self: for<'p> PySequenceContainsProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_sq_length, + slot: ffi::Py_sq_contains, pfunc: py_binary_func!(PySequenceContainsProtocol, Self::__contains__, c_int) as _, } } @@ -247,7 +208,7 @@ pub trait PySequenceSlots { Self: for<'p> PySequenceInplaceConcatProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_sq_length, + slot: ffi::Py_sq_inplace_concat, pfunc: py_binary_func!( PySequenceInplaceConcatProtocol, Self::__inplace_concat__, @@ -261,7 +222,7 @@ pub trait PySequenceSlots { Self: for<'p> PySequenceInplaceRepeatProtocol<'p>, { ffi::PyType_Slot { - slot: ffi::Py_sq_length, + slot: ffi::Py_sq_inplace_repeat, pfunc: py_ssizearg_func!( PySequenceInplaceRepeatProtocol, Self::__inplace_repeat__, From 16ad3bf676e0a8d103dd241d3cd121df63061b18 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Tue, 27 Oct 2020 00:43:26 +0900 Subject: [PATCH 35/36] Use TypedSlot as internal representation of ffi::PyType_Slot --- pyo3-derive-backend/src/defs.rs | 4 +- pyo3-derive-backend/src/pyproto.rs | 9 +- src/class/basic.rs | 201 +++++----- src/class/descr.rs | 21 +- src/class/gc.rs | 104 ++--- src/class/iter.rs | 21 +- src/class/mapping.rs | 53 +-- src/class/number.rs | 614 ++++++++++++++--------------- src/class/proto_methods.rs | 4 + src/class/pyasync.rs | 31 +- src/class/sequence.rs | 178 ++++----- 11 files changed, 589 insertions(+), 651 deletions(-) diff --git a/pyo3-derive-backend/src/defs.rs b/pyo3-derive-backend/src/defs.rs index 6a1d628c8b6..eda87b5f013 100644 --- a/pyo3-derive-backend/src/defs.rs +++ b/pyo3-derive-backend/src/defs.rs @@ -158,7 +158,7 @@ pub const OBJECT: Proto = Proto { SlotGetter::new(&["__repr__"], "get_repr"), SlotGetter::new(&["__hash__"], "get_hash"), SlotGetter::new(&["__getattr__"], "get_getattr"), - SlotGetter::new(&["__richcmp__"], "get_richcompare"), + SlotGetter::new(&["__richcmp__"], "get_richcmp"), SlotGetter::new(&["__setattr__", "__delattr__"], "get_setdelattr"), SlotGetter::new(&["__setattr__"], "get_setattr"), SlotGetter::new(&["__delattr__"], "get_delattr"), @@ -381,7 +381,7 @@ pub const MAPPING: Proto = Proto { "pyo3::class::mapping::PyMappingReversedProtocolImpl", )], slot_getters: &[ - SlotGetter::new(&["__len__"], "get_length"), + SlotGetter::new(&["__len__"], "get_len"), SlotGetter::new(&["__getitem__"], "get_getitem"), SlotGetter::new(&["__setitem__", "__delitem__"], "get_setdelitem"), SlotGetter::new(&["__setitem__"], "get_setitem"), diff --git a/pyo3-derive-backend/src/pyproto.rs b/pyo3-derive-backend/src/pyproto.rs index fe60e7a14fa..249301af0cc 100644 --- a/pyo3-derive-backend/src/pyproto.rs +++ b/pyo3-derive-backend/src/pyproto.rs @@ -138,7 +138,7 @@ fn submit_protocol_methods( return Ok(quote! {}); } let ext_trait: syn::Path = syn::parse_str(proto.extension_trait)?; - let mut tokens: Vec = vec![]; + let mut tokens = vec![]; if proto.name == "Buffer" { // For buffer, we construct `PyProtoMethods` from PyBufferProcs tokens.push(quote! { @@ -154,9 +154,12 @@ fn submit_protocol_methods( tokens.push(quote! { let mut proto_methods = vec![]; }); for getter in proto.slot_getters(method_names) { let get = syn::Ident::new(getter, Span::call_site()); - tokens.push(quote! { proto_methods.push(<#ty as #ext_trait>::#get()); }); + tokens.push(quote! { + let slot = <#ty as #ext_trait>::#get(); + proto_methods.push(pyo3::ffi::PyType_Slot { slot: slot.0, pfunc: slot.1 as _ }); + }); } - } + }; if tokens.len() <= 1 { return Ok(quote! {}); } diff --git a/src/class/basic.rs b/src/class/basic.rs index 24ff4135eb6..505946d59ef 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -8,6 +8,7 @@ //! Parts of the documentation are copied from the respective methods from the //! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html) +use super::proto_methods::TypedSlot; use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput}; use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject, PyResult}; use std::os::raw::c_int; @@ -136,167 +137,147 @@ pub trait PyObjectRichcmpProtocol<'p>: PyObjectProtocol<'p> { /// Extension trait for proc-macro backend. #[doc(hidden)] pub trait PyBasicSlots { - fn get_str() -> ffi::PyType_Slot + fn get_str() -> TypedSlot where Self: for<'p> PyObjectStrProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_str, - pfunc: py_unary_func!(PyObjectStrProtocol, Self::__str__) as _, - } + TypedSlot( + ffi::Py_tp_str, + py_unary_func!(PyObjectStrProtocol, Self::__str__), + ) } - fn get_repr() -> ffi::PyType_Slot + fn get_repr() -> TypedSlot where Self: for<'p> PyObjectReprProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_repr, - pfunc: py_unary_func!(PyObjectReprProtocol, Self::__repr__) as _, - } + TypedSlot( + ffi::Py_tp_repr, + py_unary_func!(PyObjectReprProtocol, Self::__repr__), + ) } - fn get_hash() -> ffi::PyType_Slot + fn get_hash() -> TypedSlot where Self: for<'p> PyObjectHashProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_hash, - pfunc: py_unary_func!(PyObjectHashProtocol, Self::__hash__, ffi::Py_hash_t) as _, - } + TypedSlot( + ffi::Py_tp_hash, + py_unary_func!(PyObjectHashProtocol, Self::__hash__, ffi::Py_hash_t), + ) } - fn get_getattr() -> ffi::PyType_Slot + fn get_getattr() -> TypedSlot where Self: for<'p> PyObjectGetAttrProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_getattro, - pfunc: tp_getattro::() as _, + unsafe extern "C" fn wrap( + slf: *mut ffi::PyObject, + arg: *mut ffi::PyObject, + ) -> *mut ffi::PyObject + where + T: for<'p> PyObjectGetAttrProtocol<'p>, + { + crate::callback_body!(py, { + // Behave like python's __getattr__ (as opposed to __getattribute__) and check + // for existing fields and methods first + let existing = ffi::PyObject_GenericGetAttr(slf, arg); + if existing.is_null() { + // PyObject_HasAttr also tries to get an object and clears the error if it fails + ffi::PyErr_Clear(); + } else { + return Ok(existing); + } + + let slf = py.from_borrowed_ptr::>(slf); + let arg = py.from_borrowed_ptr::(arg); + call_ref!(slf, __getattr__, arg).convert(py) + }) } + TypedSlot(ffi::Py_tp_getattro, wrap::) } - fn get_richcompare() -> ffi::PyType_Slot + fn get_richcmp() -> TypedSlot where Self: for<'p> PyObjectRichcmpProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_richcompare, - pfunc: tp_richcompare::() as _, + fn extract_op(op: c_int) -> PyResult { + match op { + ffi::Py_LT => Ok(CompareOp::Lt), + ffi::Py_LE => Ok(CompareOp::Le), + ffi::Py_EQ => Ok(CompareOp::Eq), + ffi::Py_NE => Ok(CompareOp::Ne), + ffi::Py_GT => Ok(CompareOp::Gt), + ffi::Py_GE => Ok(CompareOp::Ge), + _ => Err(exceptions::PyValueError::new_err( + "tp_richcompare called with invalid comparison operator", + )), + } + } + unsafe extern "C" fn wrap( + slf: *mut ffi::PyObject, + arg: *mut ffi::PyObject, + op: c_int, + ) -> *mut ffi::PyObject + where + T: for<'p> PyObjectRichcmpProtocol<'p>, + { + crate::callback_body!(py, { + let slf = py.from_borrowed_ptr::>(slf); + let arg = extract_or_return_not_implemented!(py, arg); + let op = extract_op(op)?; + + slf.try_borrow()?.__richcmp__(arg, op).convert(py) + }) } + TypedSlot(ffi::Py_tp_richcompare, wrap::) } - fn get_setattr() -> ffi::PyType_Slot + fn get_setattr() -> TypedSlot where Self: for<'p> PyObjectSetAttrProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_setattro, - pfunc: py_func_set!(PyObjectSetAttrProtocol, Self::__setattr__) as _, - } + TypedSlot( + ffi::Py_tp_setattro, + py_func_set!(PyObjectSetAttrProtocol, Self::__setattr__), + ) } - fn get_delattr() -> ffi::PyType_Slot + fn get_delattr() -> TypedSlot where Self: for<'p> PyObjectDelAttrProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_setattro, - pfunc: py_func_del!(PyObjectDelAttrProtocol, Self::__delattr__) as _, - } + TypedSlot( + ffi::Py_tp_setattro, + py_func_del!(PyObjectDelAttrProtocol, Self::__delattr__), + ) } - fn get_setdelattr() -> ffi::PyType_Slot + fn get_setdelattr() -> TypedSlot where Self: for<'p> PyObjectSetAttrProtocol<'p> + for<'p> PyObjectDelAttrProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_setattro, - pfunc: py_func_set_del!( + TypedSlot( + ffi::Py_tp_setattro, + py_func_set_del!( PyObjectSetAttrProtocol, PyObjectDelAttrProtocol, Self, __setattr__, __delattr__ - ) as _, - } + ), + ) } - fn get_bool() -> ffi::PyType_Slot + fn get_bool() -> TypedSlot where Self: for<'p> PyObjectBoolProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_bool, - pfunc: py_unary_func!(PyObjectBoolProtocol, Self::__bool__, c_int) as _, - } + TypedSlot( + ffi::Py_nb_bool, + py_unary_func!(PyObjectBoolProtocol, Self::__bool__, c_int), + ) } } impl<'p, T> PyBasicSlots for T where T: PyObjectProtocol<'p> {} - -fn tp_getattro() -> ffi::binaryfunc -where - T: for<'p> PyObjectGetAttrProtocol<'p>, -{ - unsafe extern "C" fn wrap( - slf: *mut ffi::PyObject, - arg: *mut ffi::PyObject, - ) -> *mut ffi::PyObject - where - T: for<'p> PyObjectGetAttrProtocol<'p>, - { - crate::callback_body!(py, { - // Behave like python's __getattr__ (as opposed to __getattribute__) and check - // for existing fields and methods first - let existing = ffi::PyObject_GenericGetAttr(slf, arg); - if existing.is_null() { - // PyObject_HasAttr also tries to get an object and clears the error if it fails - ffi::PyErr_Clear(); - } else { - return Ok(existing); - } - - let slf = py.from_borrowed_ptr::>(slf); - let arg = py.from_borrowed_ptr::(arg); - call_ref!(slf, __getattr__, arg).convert(py) - }) - } - wrap:: -} - -fn tp_richcompare() -> ffi::richcmpfunc -where - T: for<'p> PyObjectRichcmpProtocol<'p>, -{ - fn extract_op(op: c_int) -> PyResult { - match op { - ffi::Py_LT => Ok(CompareOp::Lt), - ffi::Py_LE => Ok(CompareOp::Le), - ffi::Py_EQ => Ok(CompareOp::Eq), - ffi::Py_NE => Ok(CompareOp::Ne), - ffi::Py_GT => Ok(CompareOp::Gt), - ffi::Py_GE => Ok(CompareOp::Ge), - _ => Err(exceptions::PyValueError::new_err( - "tp_richcompare called with invalid comparison operator", - )), - } - } - unsafe extern "C" fn wrap( - slf: *mut ffi::PyObject, - arg: *mut ffi::PyObject, - op: c_int, - ) -> *mut ffi::PyObject - where - T: for<'p> PyObjectRichcmpProtocol<'p>, - { - crate::callback_body!(py, { - let slf = py.from_borrowed_ptr::>(slf); - let arg = extract_or_return_not_implemented!(py, arg); - let op = extract_op(op)?; - - slf.try_borrow()?.__richcmp__(arg, op).convert(py) - }) - } - wrap:: -} diff --git a/src/class/descr.rs b/src/class/descr.rs index 6f65dfee0af..036ff881990 100644 --- a/src/class/descr.rs +++ b/src/class/descr.rs @@ -5,6 +5,7 @@ //! [Python information]( //! https://docs.python.org/3/reference/datamodel.html#implementing-descriptors) +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::types::PyAny; use crate::{ffi, FromPyObject, PyClass, PyObject}; @@ -73,24 +74,24 @@ pub trait PyDescrSetNameProtocol<'p>: PyDescrProtocol<'p> { /// Extension trait for our proc-macro backend. #[doc(hidden)] pub trait PyDescrSlots { - fn get_descr_get() -> ffi::PyType_Slot + fn get_descr_get() -> TypedSlot where Self: for<'p> PyDescrGetProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_descr_get, - pfunc: py_ternarys_func!(PyDescrGetProtocol, Self::__get__) as _, - } + TypedSlot( + ffi::Py_tp_descr_get, + py_ternarys_func!(PyDescrGetProtocol, Self::__get__), + ) } - fn get_descr_set() -> ffi::PyType_Slot + fn get_descr_set() -> TypedSlot where Self: for<'p> PyDescrSetProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_descr_set, - pfunc: py_ternarys_func!(PyDescrSetProtocol, Self::__set__, c_int) as _, - } + TypedSlot( + ffi::Py_tp_descr_set, + py_ternarys_func!(PyDescrSetProtocol, Self::__set__, c_int), + ) } } diff --git a/src/class/gc.rs b/src/class/gc.rs index 08965df398a..9d8db1e096e 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -3,6 +3,7 @@ //! Python GC support //! +use super::proto_methods::TypedSlot; use crate::{ffi, AsPyPointer, PyCell, PyClass, Python}; use std::os::raw::{c_int, c_void}; @@ -21,23 +22,57 @@ pub trait PyGCClearProtocol<'p>: PyGCProtocol<'p> {} /// Extension trait for proc-macro backend. #[doc(hidden)] pub trait PyGCSlots { - fn get_traverse() -> ffi::PyType_Slot + fn get_traverse() -> TypedSlot where Self: for<'p> PyGCTraverseProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_traverse, - pfunc: tp_traverse::() as _, + unsafe extern "C" fn wrap( + slf: *mut ffi::PyObject, + visit: ffi::visitproc, + arg: *mut c_void, + ) -> c_int + where + T: for<'p> PyGCTraverseProtocol<'p>, + { + let pool = crate::GILPool::new(); + let py = pool.python(); + let slf = py.from_borrowed_ptr::>(slf); + + let visit = PyVisit { + visit, + arg, + _py: py, + }; + let borrow = slf.try_borrow(); + if let Ok(borrow) = borrow { + match borrow.__traverse__(visit) { + Ok(()) => 0, + Err(PyTraverseError(code)) => code, + } + } else { + 0 + } } + + TypedSlot(ffi::Py_tp_traverse, wrap::) } - fn get_clear() -> ffi::PyType_Slot + + fn get_clear() -> TypedSlot where Self: for<'p> PyGCClearProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_clear, - pfunc: tp_clear::() as _, + unsafe extern "C" fn wrap(slf: *mut ffi::PyObject) -> c_int + where + T: for<'p> PyGCClearProtocol<'p>, + { + let pool = crate::GILPool::new(); + let slf = pool.python().from_borrowed_ptr::>(slf); + + slf.borrow_mut().__clear__(); + 0 } + + TypedSlot(ffi::Py_tp_clear, wrap::) } } @@ -68,56 +103,3 @@ impl<'p> PyVisit<'p> { } } } - -fn tp_traverse() -> ffi::traverseproc -where - T: for<'p> PyGCTraverseProtocol<'p>, -{ - unsafe extern "C" fn tp_traverse( - slf: *mut ffi::PyObject, - visit: ffi::visitproc, - arg: *mut c_void, - ) -> c_int - where - T: for<'p> PyGCTraverseProtocol<'p>, - { - let pool = crate::GILPool::new(); - let py = pool.python(); - let slf = py.from_borrowed_ptr::>(slf); - - let visit = PyVisit { - visit, - arg, - _py: py, - }; - let borrow = slf.try_borrow(); - if let Ok(borrow) = borrow { - match borrow.__traverse__(visit) { - Ok(()) => 0, - Err(PyTraverseError(code)) => code, - } - } else { - 0 - } - } - - tp_traverse:: -} - -fn tp_clear() -> ffi::inquiry -where - T: for<'p> PyGCClearProtocol<'p>, -{ - unsafe extern "C" fn tp_clear(slf: *mut ffi::PyObject) -> c_int - where - T: for<'p> PyGCClearProtocol<'p>, - { - let pool = crate::GILPool::new(); - let py = pool.python(); - let slf = py.from_borrowed_ptr::>(slf); - - slf.borrow_mut().__clear__(); - 0 - } - tp_clear:: -} diff --git a/src/class/iter.rs b/src/class/iter.rs index e53dcc97638..c08bef0a215 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -2,6 +2,7 @@ //! Python Iterator Interface. //! Trait and support implementation for implementing iterators +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::derive_utils::TryFromPyCell; use crate::err::PyResult; @@ -74,23 +75,23 @@ pub trait PyIterNextProtocol<'p>: PyIterProtocol<'p> { /// Extension trait for proc-macro backend. #[doc(hidden)] pub trait PyIterSlots { - fn get_iter() -> ffi::PyType_Slot + fn get_iter() -> TypedSlot where Self: for<'p> PyIterIterProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_iter, - pfunc: py_unarys_func!(PyIterIterProtocol, Self::__iter__) as _, - } + TypedSlot( + ffi::Py_tp_iter, + py_unarys_func!(PyIterIterProtocol, Self::__iter__), + ) } - fn get_iternext() -> ffi::PyType_Slot + fn get_iternext() -> TypedSlot where Self: for<'p> PyIterNextProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_tp_iternext, - pfunc: py_unarys_func!(PyIterNextProtocol, Self::__next__) as _, - } + TypedSlot( + ffi::Py_tp_iternext, + py_unarys_func!(PyIterNextProtocol, Self::__next__), + ) } } diff --git a/src/class/mapping.rs b/src/class/mapping.rs index 07ee4122262..df3cfe14828 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -3,6 +3,7 @@ //! Python Mapping Interface //! Trait and support implementation for implementing mapping support +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::{exceptions, ffi, FromPyObject, PyClass, PyObject}; @@ -75,60 +76,60 @@ pub trait PyMappingReversedProtocol<'p>: PyMappingProtocol<'p> { /// Extension trait for proc-macro backend. #[doc(hidden)] pub trait PyMappingSlots { - fn get_length() -> ffi::PyType_Slot + fn get_len() -> TypedSlot where Self: for<'p> PyMappingLenProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_mp_length, - pfunc: py_len_func!(PyMappingLenProtocol, Self::__len__) as _, - } + TypedSlot( + ffi::Py_mp_length, + py_len_func!(PyMappingLenProtocol, Self::__len__), + ) } - fn get_getitem() -> ffi::PyType_Slot + fn get_getitem() -> TypedSlot where Self: for<'p> PyMappingGetItemProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_mp_subscript, - pfunc: py_binary_func!(PyMappingGetItemProtocol, Self::__getitem__) as _, - } + TypedSlot( + ffi::Py_mp_subscript, + py_binary_func!(PyMappingGetItemProtocol, Self::__getitem__), + ) } - fn get_setitem() -> ffi::PyType_Slot + fn get_setitem() -> TypedSlot where Self: for<'p> PyMappingSetItemProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_mp_ass_subscript, - pfunc: py_func_set!(PyMappingSetItemProtocol, Self::__setitem__) as _, - } + TypedSlot( + ffi::Py_mp_ass_subscript, + py_func_set!(PyMappingSetItemProtocol, Self::__setitem__), + ) } - fn get_delitem() -> ffi::PyType_Slot + fn get_delitem() -> TypedSlot where Self: for<'p> PyMappingDelItemProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_mp_ass_subscript, - pfunc: py_func_del!(PyMappingDelItemProtocol, Self::__delitem__) as _, - } + TypedSlot( + ffi::Py_mp_ass_subscript, + py_func_del!(PyMappingDelItemProtocol, Self::__delitem__), + ) } - fn get_setdelitem() -> ffi::PyType_Slot + fn get_setdelitem() -> TypedSlot where Self: for<'p> PyMappingSetItemProtocol<'p> + for<'p> PyMappingDelItemProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_mp_ass_subscript, - pfunc: py_func_set_del!( + TypedSlot( + ffi::Py_mp_ass_subscript, + py_func_set_del!( PyMappingSetItemProtocol, PyMappingDelItemProtocol, Self, __setitem__, __delitem__ - ) as _, - } + ), + ) } } diff --git a/src/class/number.rs b/src/class/number.rs index edc5ae63c43..cdcea6b0a58 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -2,6 +2,7 @@ //! Python Number Interface //! Trait and support implementation for implementing number protocol +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; use crate::{ffi, FromPyObject, PyClass, PyObject}; @@ -581,153 +582,153 @@ pub trait PyNumberIndexProtocol<'p>: PyNumberProtocol<'p> { /// Extension trait for proc-macro backend. #[doc(hidden)] pub trait PyNumberSlots { - fn get_add_radd() -> ffi::PyType_Slot + fn get_add_radd() -> TypedSlot where Self: for<'p> PyNumberAddProtocol<'p> + for<'p> PyNumberRAddProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_add, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_add, + py_binary_fallback_num_func!( Self, PyNumberAddProtocol::__add__, PyNumberRAddProtocol::__radd__ - ) as _, - } + ), + ) } - fn get_add() -> ffi::PyType_Slot + fn get_add() -> TypedSlot where Self: for<'p> PyNumberAddProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_add, - pfunc: py_binary_num_func!(PyNumberAddProtocol, Self::__add__) as _, - } + TypedSlot( + ffi::Py_nb_add, + py_binary_num_func!(PyNumberAddProtocol, Self::__add__), + ) } - fn get_radd() -> ffi::PyType_Slot + fn get_radd() -> TypedSlot where Self: for<'p> PyNumberRAddProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_add, - pfunc: py_binary_reversed_num_func!(PyNumberRAddProtocol, Self::__radd__) as _, - } + TypedSlot( + ffi::Py_nb_add, + py_binary_reversed_num_func!(PyNumberRAddProtocol, Self::__radd__), + ) } - fn get_sub_rsub() -> ffi::PyType_Slot + fn get_sub_rsub() -> TypedSlot where Self: for<'p> PyNumberSubProtocol<'p> + for<'p> PyNumberRSubProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_subtract, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_subtract, + py_binary_fallback_num_func!( Self, PyNumberSubProtocol::__sub__, PyNumberRSubProtocol::__rsub__ - ) as _, - } + ), + ) } - fn get_sub() -> ffi::PyType_Slot + fn get_sub() -> TypedSlot where Self: for<'p> PyNumberSubProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_subtract, - pfunc: py_binary_num_func!(PyNumberSubProtocol, Self::__sub__) as _, - } + TypedSlot( + ffi::Py_nb_subtract, + py_binary_num_func!(PyNumberSubProtocol, Self::__sub__), + ) } - fn get_rsub() -> ffi::PyType_Slot + fn get_rsub() -> TypedSlot where Self: for<'p> PyNumberRSubProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_subtract, - pfunc: py_binary_reversed_num_func!(PyNumberRSubProtocol, Self::__rsub__) as _, - } + TypedSlot( + ffi::Py_nb_subtract, + py_binary_reversed_num_func!(PyNumberRSubProtocol, Self::__rsub__), + ) } - fn get_mul_rmul() -> ffi::PyType_Slot + fn get_mul_rmul() -> TypedSlot where Self: for<'p> PyNumberMulProtocol<'p> + for<'p> PyNumberRMulProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_multiply, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_multiply, + py_binary_fallback_num_func!( Self, PyNumberMulProtocol::__mul__, PyNumberRMulProtocol::__rmul__ - ) as _, - } + ), + ) } - fn get_mul() -> ffi::PyType_Slot + fn get_mul() -> TypedSlot where Self: for<'p> PyNumberMulProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_multiply, - pfunc: py_binary_num_func!(PyNumberMulProtocol, Self::__mul__) as _, - } + TypedSlot( + ffi::Py_nb_multiply, + py_binary_num_func!(PyNumberMulProtocol, Self::__mul__), + ) } - fn get_rmul() -> ffi::PyType_Slot + fn get_rmul() -> TypedSlot where Self: for<'p> PyNumberRMulProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_multiply, - pfunc: py_binary_reversed_num_func!(PyNumberRMulProtocol, Self::__rmul__) as _, - } + TypedSlot( + ffi::Py_nb_multiply, + py_binary_reversed_num_func!(PyNumberRMulProtocol, Self::__rmul__), + ) } - fn get_mod() -> ffi::PyType_Slot + fn get_mod() -> TypedSlot where Self: for<'p> PyNumberModProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_remainder, - pfunc: py_binary_num_func!(PyNumberModProtocol, Self::__mod__) as _, - } + TypedSlot( + ffi::Py_nb_remainder, + py_binary_num_func!(PyNumberModProtocol, Self::__mod__), + ) } - fn get_divmod_rdivmod() -> ffi::PyType_Slot + fn get_divmod_rdivmod() -> TypedSlot where Self: for<'p> PyNumberDivmodProtocol<'p> + for<'p> PyNumberRDivmodProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_divmod, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_divmod, + py_binary_fallback_num_func!( Self, PyNumberDivmodProtocol::__divmod__, PyNumberRDivmodProtocol::__rdivmod__ - ) as _, - } + ), + ) } - fn get_divmod() -> ffi::PyType_Slot + fn get_divmod() -> TypedSlot where Self: for<'p> PyNumberDivmodProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_divmod, - pfunc: py_binary_num_func!(PyNumberDivmodProtocol, Self::__divmod__) as _, - } + TypedSlot( + ffi::Py_nb_divmod, + py_binary_num_func!(PyNumberDivmodProtocol, Self::__divmod__), + ) } - fn get_rdivmod() -> ffi::PyType_Slot + fn get_rdivmod() -> TypedSlot where Self: for<'p> PyNumberRDivmodProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_divmod, - pfunc: py_binary_reversed_num_func!(PyNumberRDivmodProtocol, Self::__rdivmod__) as _, - } + TypedSlot( + ffi::Py_nb_divmod, + py_binary_reversed_num_func!(PyNumberRDivmodProtocol, Self::__rdivmod__), + ) } - fn get_pow_rpow() -> ffi::PyType_Slot + fn get_pow_rpow() -> TypedSlot where Self: for<'p> PyNumberPowProtocol<'p> + for<'p> PyNumberRPowProtocol<'p>, { @@ -757,13 +758,10 @@ pub trait PyNumberSlots { }) } - ffi::PyType_Slot { - slot: ffi::Py_nb_power, - pfunc: wrap_pow_and_rpow:: as _, - } + TypedSlot(ffi::Py_nb_power, wrap_pow_and_rpow::) } - fn get_pow() -> ffi::PyType_Slot + fn get_pow() -> TypedSlot where Self: for<'p> PyNumberPowProtocol<'p>, { @@ -783,13 +781,10 @@ pub trait PyNumberSlots { }) } - ffi::PyType_Slot { - slot: ffi::Py_nb_power, - pfunc: wrap_pow:: as _, - } + TypedSlot(ffi::Py_nb_power, wrap_pow::) } - fn get_rpow() -> ffi::PyType_Slot + fn get_rpow() -> TypedSlot where Self: for<'p> PyNumberRPowProtocol<'p>, { @@ -809,283 +804,280 @@ pub trait PyNumberSlots { }) } - ffi::PyType_Slot { - slot: ffi::Py_nb_power, - pfunc: wrap_rpow:: as _, - } + TypedSlot(ffi::Py_nb_power, wrap_rpow::) } - fn get_neg() -> ffi::PyType_Slot + fn get_neg() -> TypedSlot where Self: for<'p> PyNumberNegProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_negative, - pfunc: py_unary_func!(PyNumberNegProtocol, Self::__neg__) as _, - } + TypedSlot( + ffi::Py_nb_negative, + py_unary_func!(PyNumberNegProtocol, Self::__neg__), + ) } - fn get_pos() -> ffi::PyType_Slot + fn get_pos() -> TypedSlot where Self: for<'p> PyNumberPosProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_positive, - pfunc: py_unary_func!(PyNumberPosProtocol, Self::__pos__) as _, - } + TypedSlot( + ffi::Py_nb_positive, + py_unary_func!(PyNumberPosProtocol, Self::__pos__), + ) } - fn get_abs() -> ffi::PyType_Slot + fn get_abs() -> TypedSlot where Self: for<'p> PyNumberAbsProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_absolute, - pfunc: py_unary_func!(PyNumberAbsProtocol, Self::__abs__) as _, - } + TypedSlot( + ffi::Py_nb_absolute, + py_unary_func!(PyNumberAbsProtocol, Self::__abs__), + ) } - fn get_invert() -> ffi::PyType_Slot + fn get_invert() -> TypedSlot where Self: for<'p> PyNumberInvertProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_invert, - pfunc: py_unary_func!(PyNumberInvertProtocol, Self::__invert__) as _, - } + TypedSlot( + ffi::Py_nb_invert, + py_unary_func!(PyNumberInvertProtocol, Self::__invert__), + ) } - fn get_lshift_rlshift() -> ffi::PyType_Slot + fn get_lshift_rlshift() -> TypedSlot where Self: for<'p> PyNumberLShiftProtocol<'p> + for<'p> PyNumberRLShiftProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_lshift, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_lshift, + py_binary_fallback_num_func!( Self, PyNumberLShiftProtocol::__lshift__, PyNumberRLShiftProtocol::__rlshift__ - ) as _, - } + ), + ) } - fn get_lshift() -> ffi::PyType_Slot + fn get_lshift() -> TypedSlot where Self: for<'p> PyNumberLShiftProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_lshift, - pfunc: py_binary_num_func!(PyNumberLShiftProtocol, Self::__lshift__) as _, - } + TypedSlot( + ffi::Py_nb_lshift, + py_binary_num_func!(PyNumberLShiftProtocol, Self::__lshift__), + ) } - fn get_rlshift() -> ffi::PyType_Slot + fn get_rlshift() -> TypedSlot where Self: for<'p> PyNumberRLShiftProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_lshift, - pfunc: py_binary_reversed_num_func!(PyNumberRLShiftProtocol, Self::__rlshift__) as _, - } + TypedSlot( + ffi::Py_nb_lshift, + py_binary_reversed_num_func!(PyNumberRLShiftProtocol, Self::__rlshift__), + ) } - fn get_rshift_rrshift() -> ffi::PyType_Slot + fn get_rshift_rrshift() -> TypedSlot where Self: for<'p> PyNumberRShiftProtocol<'p> + for<'p> PyNumberRRShiftProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_rshift, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_rshift, + py_binary_fallback_num_func!( Self, PyNumberRShiftProtocol::__rshift__, PyNumberRRShiftProtocol::__rrshift__ - ) as _, - } + ), + ) } - fn get_rshift() -> ffi::PyType_Slot + fn get_rshift() -> TypedSlot where Self: for<'p> PyNumberRShiftProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_rshift, - pfunc: py_binary_num_func!(PyNumberRShiftProtocol, Self::__rshift__) as _, - } + TypedSlot( + ffi::Py_nb_rshift, + py_binary_num_func!(PyNumberRShiftProtocol, Self::__rshift__), + ) } - fn get_rrshift() -> ffi::PyType_Slot + fn get_rrshift() -> TypedSlot where Self: for<'p> PyNumberRRShiftProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_rshift, - pfunc: py_binary_reversed_num_func!(PyNumberRRShiftProtocol, Self::__rrshift__) as _, - } + TypedSlot( + ffi::Py_nb_rshift, + py_binary_reversed_num_func!(PyNumberRRShiftProtocol, Self::__rrshift__), + ) } - fn get_and_rand() -> ffi::PyType_Slot + fn get_and_rand() -> TypedSlot where Self: for<'p> PyNumberAndProtocol<'p> + for<'p> PyNumberRAndProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_and, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_and, + py_binary_fallback_num_func!( Self, PyNumberAndProtocol::__and__, PyNumberRAndProtocol::__rand__ - ) as _, - } + ), + ) } - fn get_and() -> ffi::PyType_Slot + fn get_and() -> TypedSlot where Self: for<'p> PyNumberAndProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_and, - pfunc: py_binary_num_func!(PyNumberAndProtocol, Self::__and__) as _, - } + TypedSlot( + ffi::Py_nb_and, + py_binary_num_func!(PyNumberAndProtocol, Self::__and__), + ) } - fn get_rand() -> ffi::PyType_Slot + fn get_rand() -> TypedSlot where Self: for<'p> PyNumberRAndProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_and, - pfunc: py_binary_reversed_num_func!(PyNumberRAndProtocol, Self::__rand__) as _, - } + TypedSlot( + ffi::Py_nb_and, + py_binary_reversed_num_func!(PyNumberRAndProtocol, Self::__rand__), + ) } - fn get_xor_rxor() -> ffi::PyType_Slot + fn get_xor_rxor() -> TypedSlot where Self: for<'p> PyNumberXorProtocol<'p> + for<'p> PyNumberRXorProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_xor, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_xor, + py_binary_fallback_num_func!( Self, PyNumberXorProtocol::__xor__, PyNumberRXorProtocol::__rxor__ - ) as _, - } + ), + ) } - fn get_xor() -> ffi::PyType_Slot + fn get_xor() -> TypedSlot where Self: for<'p> PyNumberXorProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_xor, - pfunc: py_binary_num_func!(PyNumberXorProtocol, Self::__xor__) as _, - } + TypedSlot( + ffi::Py_nb_xor, + py_binary_num_func!(PyNumberXorProtocol, Self::__xor__), + ) } - fn get_rxor() -> ffi::PyType_Slot + fn get_rxor() -> TypedSlot where Self: for<'p> PyNumberRXorProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_xor, - pfunc: py_binary_reversed_num_func!(PyNumberRXorProtocol, Self::__rxor__) as _, - } + TypedSlot( + ffi::Py_nb_xor, + py_binary_reversed_num_func!(PyNumberRXorProtocol, Self::__rxor__), + ) } - fn get_or_ror() -> ffi::PyType_Slot + fn get_or_ror() -> TypedSlot where Self: for<'p> PyNumberOrProtocol<'p> + for<'p> PyNumberROrProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_or, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_or, + py_binary_fallback_num_func!( Self, PyNumberOrProtocol::__or__, PyNumberROrProtocol::__ror__ - ) as _, - } + ), + ) } - fn get_or() -> ffi::PyType_Slot + fn get_or() -> TypedSlot where Self: for<'p> PyNumberOrProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_or, - pfunc: py_binary_num_func!(PyNumberOrProtocol, Self::__or__) as _, - } + TypedSlot( + ffi::Py_nb_or, + py_binary_num_func!(PyNumberOrProtocol, Self::__or__), + ) } - fn get_ror() -> ffi::PyType_Slot + fn get_ror() -> TypedSlot where Self: for<'p> PyNumberROrProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_or, - pfunc: py_binary_reversed_num_func!(PyNumberROrProtocol, Self::__ror__) as _, - } + TypedSlot( + ffi::Py_nb_or, + py_binary_reversed_num_func!(PyNumberROrProtocol, Self::__ror__), + ) } - fn get_int() -> ffi::PyType_Slot + fn get_int() -> TypedSlot where Self: for<'p> PyNumberIntProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_int, - pfunc: py_unary_func!(PyNumberIntProtocol, Self::__int__) as _, - } + TypedSlot( + ffi::Py_nb_int, + py_unary_func!(PyNumberIntProtocol, Self::__int__), + ) } - fn get_float() -> ffi::PyType_Slot + fn get_float() -> TypedSlot where Self: for<'p> PyNumberFloatProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_float, - pfunc: py_unary_func!(PyNumberFloatProtocol, Self::__float__) as _, - } + TypedSlot( + ffi::Py_nb_float, + py_unary_func!(PyNumberFloatProtocol, Self::__float__), + ) } - fn get_iadd() -> ffi::PyType_Slot + fn get_iadd() -> TypedSlot where Self: for<'p> PyNumberIAddProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_add, - pfunc: py_binary_self_func!(PyNumberIAddProtocol, Self::__iadd__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_add, + py_binary_self_func!(PyNumberIAddProtocol, Self::__iadd__), + ) } - fn get_isub() -> ffi::PyType_Slot + fn get_isub() -> TypedSlot where Self: for<'p> PyNumberISubProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_subtract, - pfunc: py_binary_self_func!(PyNumberISubProtocol, Self::__isub__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_subtract, + py_binary_self_func!(PyNumberISubProtocol, Self::__isub__), + ) } - fn get_imul() -> ffi::PyType_Slot + fn get_imul() -> TypedSlot where Self: for<'p> PyNumberIMulProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_multiply, - pfunc: py_binary_self_func!(PyNumberIMulProtocol, Self::__imul__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_multiply, + py_binary_self_func!(PyNumberIMulProtocol, Self::__imul__), + ) } - fn get_imod() -> ffi::PyType_Slot + fn get_imod() -> TypedSlot where Self: for<'p> PyNumberIModProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_remainder, - pfunc: py_binary_self_func!(PyNumberIModProtocol, Self::__imod__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_remainder, + py_binary_self_func!(PyNumberIModProtocol, Self::__imod__), + ) } - fn get_ipow() -> ffi::PyType_Slot + fn get_ipow() -> TypedSlot where Self: for<'p> PyNumberIPowProtocol<'p>, { @@ -1108,203 +1100,199 @@ pub trait PyNumberSlots { }) } - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_power, - pfunc: wrap_ipow:: as _, - } + TypedSlot(ffi::Py_nb_inplace_power, wrap_ipow::) } - fn get_ilshift() -> ffi::PyType_Slot + fn get_ilshift() -> TypedSlot where Self: for<'p> PyNumberILShiftProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_lshift, - pfunc: py_binary_self_func!(PyNumberILShiftProtocol, Self::__ilshift__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_lshift, + py_binary_self_func!(PyNumberILShiftProtocol, Self::__ilshift__), + ) } - fn get_irshift() -> ffi::PyType_Slot + fn get_irshift() -> TypedSlot where Self: for<'p> PyNumberIRShiftProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_rshift, - pfunc: py_binary_self_func!(PyNumberIRShiftProtocol, Self::__irshift__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_rshift, + py_binary_self_func!(PyNumberIRShiftProtocol, Self::__irshift__), + ) } - fn get_iand() -> ffi::PyType_Slot + fn get_iand() -> TypedSlot where Self: for<'p> PyNumberIAndProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_and, - pfunc: py_binary_self_func!(PyNumberIAndProtocol, Self::__iand__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_and, + py_binary_self_func!(PyNumberIAndProtocol, Self::__iand__), + ) } - fn get_ixor() -> ffi::PyType_Slot + fn get_ixor() -> TypedSlot where Self: for<'p> PyNumberIXorProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_xor, - pfunc: py_binary_self_func!(PyNumberIXorProtocol, Self::__ixor__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_xor, + py_binary_self_func!(PyNumberIXorProtocol, Self::__ixor__), + ) } - fn get_ior() -> ffi::PyType_Slot + fn get_ior() -> TypedSlot where Self: for<'p> PyNumberIOrProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_or, - pfunc: py_binary_self_func!(PyNumberIOrProtocol, Self::__ior__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_or, + py_binary_self_func!(PyNumberIOrProtocol, Self::__ior__), + ) } - fn get_floordiv_rfloordiv() -> ffi::PyType_Slot + fn get_floordiv_rfloordiv() -> TypedSlot where Self: for<'p> PyNumberFloordivProtocol<'p> + for<'p> PyNumberRFloordivProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_floor_divide, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_floor_divide, + py_binary_fallback_num_func!( Self, PyNumberFloordivProtocol::__floordiv__, PyNumberRFloordivProtocol::__rfloordiv__ - ) as _, - } + ), + ) } - fn get_floordiv() -> ffi::PyType_Slot + fn get_floordiv() -> TypedSlot where Self: for<'p> PyNumberFloordivProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_floor_divide, - pfunc: py_binary_num_func!(PyNumberFloordivProtocol, Self::__floordiv__) as _, - } + TypedSlot( + ffi::Py_nb_floor_divide, + py_binary_num_func!(PyNumberFloordivProtocol, Self::__floordiv__), + ) } - fn get_rfloordiv() -> ffi::PyType_Slot + fn get_rfloordiv() -> TypedSlot where Self: for<'p> PyNumberRFloordivProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_floor_divide, - pfunc: py_binary_reversed_num_func!(PyNumberRFloordivProtocol, Self::__rfloordiv__) - as _, - } + TypedSlot( + ffi::Py_nb_floor_divide, + py_binary_reversed_num_func!(PyNumberRFloordivProtocol, Self::__rfloordiv__), + ) } - fn get_truediv_rtruediv() -> ffi::PyType_Slot + fn get_truediv_rtruediv() -> TypedSlot where Self: for<'p> PyNumberTruedivProtocol<'p> + for<'p> PyNumberRTruedivProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_true_divide, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_true_divide, + py_binary_fallback_num_func!( Self, PyNumberTruedivProtocol::__truediv__, PyNumberRTruedivProtocol::__rtruediv__ - ) as _, - } + ), + ) } - fn get_truediv() -> ffi::PyType_Slot + fn get_truediv() -> TypedSlot where Self: for<'p> PyNumberTruedivProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_true_divide, - pfunc: py_binary_num_func!(PyNumberTruedivProtocol, Self::__truediv__) as _, - } + TypedSlot( + ffi::Py_nb_true_divide, + py_binary_num_func!(PyNumberTruedivProtocol, Self::__truediv__), + ) } - fn get_rtruediv() -> ffi::PyType_Slot + fn get_rtruediv() -> TypedSlot where Self: for<'p> PyNumberRTruedivProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_true_divide, - pfunc: py_binary_reversed_num_func!(PyNumberRTruedivProtocol, Self::__rtruediv__) as _, - } + TypedSlot( + ffi::Py_nb_true_divide, + py_binary_reversed_num_func!(PyNumberRTruedivProtocol, Self::__rtruediv__), + ) } - fn get_ifloordiv() -> ffi::PyType_Slot + fn get_ifloordiv() -> TypedSlot where Self: for<'p> PyNumberIFloordivProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_floor_divide, - pfunc: py_binary_self_func!(PyNumberIFloordivProtocol, Self::__ifloordiv__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_floor_divide, + py_binary_self_func!(PyNumberIFloordivProtocol, Self::__ifloordiv__), + ) } - fn get_itruediv() -> ffi::PyType_Slot + fn get_itruediv() -> TypedSlot where Self: for<'p> PyNumberITruedivProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_true_divide, - pfunc: py_binary_self_func!(PyNumberITruedivProtocol, Self::__itruediv__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_true_divide, + py_binary_self_func!(PyNumberITruedivProtocol, Self::__itruediv__), + ) } - fn get_index() -> ffi::PyType_Slot + fn get_index() -> TypedSlot where Self: for<'p> PyNumberIndexProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_index, - pfunc: py_unary_func!(PyNumberIndexProtocol, Self::__index__) as _, - } + TypedSlot( + ffi::Py_nb_index, + py_unary_func!(PyNumberIndexProtocol, Self::__index__), + ) } - fn get_matmul_rmatmul() -> ffi::PyType_Slot + fn get_matmul_rmatmul() -> TypedSlot where Self: for<'p> PyNumberMatmulProtocol<'p> + for<'p> PyNumberRMatmulProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_matrix_multiply, - pfunc: py_binary_fallback_num_func!( + TypedSlot( + ffi::Py_nb_matrix_multiply, + py_binary_fallback_num_func!( Self, PyNumberMatmulProtocol::__matmul__, PyNumberRMatmulProtocol::__rmatmul__ - ) as _, - } + ), + ) } - fn get_matmul() -> ffi::PyType_Slot + fn get_matmul() -> TypedSlot where Self: for<'p> PyNumberMatmulProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_matrix_multiply, - pfunc: py_binary_num_func!(PyNumberMatmulProtocol, Self::__matmul__) as _, - } + TypedSlot( + ffi::Py_nb_matrix_multiply, + py_binary_num_func!(PyNumberMatmulProtocol, Self::__matmul__), + ) } - fn get_rmatmul() -> ffi::PyType_Slot + fn get_rmatmul() -> TypedSlot where Self: for<'p> PyNumberRMatmulProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_matrix_multiply, - pfunc: py_binary_reversed_num_func!(PyNumberRMatmulProtocol, Self::__rmatmul__) as _, - } + TypedSlot( + ffi::Py_nb_matrix_multiply, + py_binary_reversed_num_func!(PyNumberRMatmulProtocol, Self::__rmatmul__), + ) } - fn get_imatmul() -> ffi::PyType_Slot + fn get_imatmul() -> TypedSlot where Self: for<'p> PyNumberIMatmulProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_nb_inplace_matrix_multiply, - pfunc: py_binary_self_func!(PyNumberIMatmulProtocol, Self::__imatmul__) as _, - } + TypedSlot( + ffi::Py_nb_inplace_matrix_multiply, + py_binary_self_func!(PyNumberIMatmulProtocol, Self::__imatmul__), + ) } } diff --git a/src/class/proto_methods.rs b/src/class/proto_methods.rs index 09a6d966b77..eff528f0873 100644 --- a/src/class/proto_methods.rs +++ b/src/class/proto_methods.rs @@ -18,6 +18,10 @@ pub trait PyProtoMethods { } } +/// Typed version of `ffi::PyType_Slot` +#[doc(hidden)] +pub struct TypedSlot(pub std::os::raw::c_int, pub T); + #[doc(hidden)] pub enum PyProtoMethodDef { Slots(Vec), diff --git a/src/class/pyasync.rs b/src/class/pyasync.rs index b474e5d955f..226656fd41a 100644 --- a/src/class/pyasync.rs +++ b/src/class/pyasync.rs @@ -8,6 +8,7 @@ //! [PEP-0492](https://www.python.org/dev/peps/pep-0492/) //! +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::derive_utils::TryFromPyCell; use crate::err::PyResult; @@ -88,34 +89,34 @@ pub trait PyAsyncAexitProtocol<'p>: PyAsyncProtocol<'p> { /// Extension trait for proc-macro backend. #[doc(hidden)] pub trait PyAsyncSlots { - fn get_await() -> ffi::PyType_Slot + fn get_await() -> TypedSlot where Self: for<'p> PyAsyncAwaitProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_am_await, - pfunc: py_unarys_func!(PyAsyncAwaitProtocol, Self::__await__) as _, - } + TypedSlot( + ffi::Py_am_await, + py_unarys_func!(PyAsyncAwaitProtocol, Self::__await__), + ) } - fn get_aiter() -> ffi::PyType_Slot + fn get_aiter() -> TypedSlot where Self: for<'p> PyAsyncAiterProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_am_aiter, - pfunc: py_unarys_func!(PyAsyncAiterProtocol, Self::__aiter__) as _, - } + TypedSlot( + ffi::Py_am_aiter, + py_unarys_func!(PyAsyncAiterProtocol, Self::__aiter__), + ) } - fn get_anext() -> ffi::PyType_Slot + fn get_anext() -> TypedSlot where Self: for<'p> PyAsyncAnextProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_am_anext, - pfunc: py_unarys_func!(PyAsyncAnextProtocol, Self::__anext__) as _, - } + TypedSlot( + ffi::Py_am_anext, + py_unarys_func!(PyAsyncAnextProtocol, Self::__anext__), + ) } } diff --git a/src/class/sequence.rs b/src/class/sequence.rs index 449d0fbc069..7ac3ffd9637 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -3,6 +3,7 @@ //! Python Sequence Interface //! Trait and support implementation for implementing sequence +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::conversion::{FromPyObject, IntoPy}; use crate::err::PyErr; @@ -131,118 +132,49 @@ pub trait PySequenceInplaceRepeatProtocol<'p>: /// Extension trait for proc-macro backend. #[doc(hidden)] pub trait PySequenceSlots { - fn get_len() -> ffi::PyType_Slot + fn get_len() -> TypedSlot where Self: for<'p> PySequenceLenProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_sq_length, - pfunc: py_len_func!(PySequenceLenProtocol, Self::__len__) as _, - } + TypedSlot( + ffi::Py_sq_length, + py_len_func!(PySequenceLenProtocol, Self::__len__), + ) } - fn get_concat() -> ffi::PyType_Slot + + fn get_concat() -> TypedSlot where Self: for<'p> PySequenceConcatProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_sq_concat, - pfunc: py_binary_func!(PySequenceConcatProtocol, Self::__concat__) as _, - } + TypedSlot( + ffi::Py_sq_concat, + py_binary_func!(PySequenceConcatProtocol, Self::__concat__), + ) } - fn get_repeat() -> ffi::PyType_Slot + + fn get_repeat() -> TypedSlot where Self: for<'p> PySequenceRepeatProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_sq_repeat, - pfunc: py_ssizearg_func!(PySequenceRepeatProtocol, Self::__repeat__) as _, - } + TypedSlot( + ffi::Py_sq_repeat, + py_ssizearg_func!(PySequenceRepeatProtocol, Self::__repeat__), + ) } - fn get_getitem() -> ffi::PyType_Slot + + fn get_getitem() -> TypedSlot where Self: for<'p> PySequenceGetItemProtocol<'p>, { - ffi::PyType_Slot { - slot: ffi::Py_sq_item, - pfunc: py_ssizearg_func!(PySequenceGetItemProtocol, Self::__getitem__) as _, - } - } - fn get_setitem() -> ffi::PyType_Slot - where - Self: for<'p> PySequenceSetItemProtocol<'p>, - { - ffi::PyType_Slot { - slot: ffi::Py_sq_ass_item, - pfunc: sq_ass_item_impl::set_item::() as _, - } - } - fn get_delitem() -> ffi::PyType_Slot - where - Self: for<'p> PySequenceDelItemProtocol<'p>, - { - ffi::PyType_Slot { - slot: ffi::Py_sq_ass_item, - pfunc: sq_ass_item_impl::del_item::() as _, - } - } - fn get_setdelitem() -> ffi::PyType_Slot - where - Self: for<'p> PySequenceDelItemProtocol<'p> + for<'p> PySequenceSetItemProtocol<'p>, - { - ffi::PyType_Slot { - slot: ffi::Py_sq_ass_item, - pfunc: sq_ass_item_impl::set_del_item::() as _, - } - } - fn get_contains() -> ffi::PyType_Slot - where - Self: for<'p> PySequenceContainsProtocol<'p>, - { - ffi::PyType_Slot { - slot: ffi::Py_sq_contains, - pfunc: py_binary_func!(PySequenceContainsProtocol, Self::__contains__, c_int) as _, - } - } - fn get_inplace_concat() -> ffi::PyType_Slot - where - Self: for<'p> PySequenceInplaceConcatProtocol<'p>, - { - ffi::PyType_Slot { - slot: ffi::Py_sq_inplace_concat, - pfunc: py_binary_func!( - PySequenceInplaceConcatProtocol, - Self::__inplace_concat__, - *mut ffi::PyObject, - call_mut - ) as _, - } - } - fn get_inplace_repeat() -> ffi::PyType_Slot - where - Self: for<'p> PySequenceInplaceRepeatProtocol<'p>, - { - ffi::PyType_Slot { - slot: ffi::Py_sq_inplace_repeat, - pfunc: py_ssizearg_func!( - PySequenceInplaceRepeatProtocol, - Self::__inplace_repeat__, - call_mut - ) as _, - } + TypedSlot( + ffi::Py_sq_item, + py_ssizearg_func!(PySequenceGetItemProtocol, Self::__getitem__), + ) } -} - -impl<'p, T> PySequenceSlots for T where T: PySequenceProtocol<'p> {} -/// It can be possible to delete and set items (PySequenceSetItemProtocol and -/// PySequenceDelItemProtocol implemented), only to delete (PySequenceDelItemProtocol implemented) -/// or no deleting or setting is possible -mod sq_ass_item_impl { - use super::*; - - pub(super) fn set_item() -> ffi::ssizeobjargproc + fn get_setitem() -> TypedSlot where - T: for<'p> PySequenceSetItemProtocol<'p>, + Self: for<'p> PySequenceSetItemProtocol<'p>, { unsafe extern "C" fn wrap( slf: *mut ffi::PyObject, @@ -268,12 +200,13 @@ mod sq_ass_item_impl { crate::callback::convert(py, slf.__setitem__(key.into(), value)) }) } - wrap:: + + TypedSlot(ffi::Py_sq_ass_item, wrap::) } - pub(super) fn del_item() -> ffi::ssizeobjargproc + fn get_delitem() -> TypedSlot where - T: for<'p> PySequenceDelItemProtocol<'p>, + Self: for<'p> PySequenceDelItemProtocol<'p>, { unsafe extern "C" fn wrap( slf: *mut ffi::PyObject, @@ -296,12 +229,13 @@ mod sq_ass_item_impl { } }) } - wrap:: + + TypedSlot(ffi::Py_sq_ass_item, wrap::) } - pub(super) fn set_del_item() -> ffi::ssizeobjargproc + fn get_setdelitem() -> TypedSlot where - T: for<'p> PySequenceSetItemProtocol<'p> + for<'p> PySequenceDelItemProtocol<'p>, + Self: for<'p> PySequenceDelItemProtocol<'p> + for<'p> PySequenceSetItemProtocol<'p>, { unsafe extern "C" fn wrap( slf: *mut ffi::PyObject, @@ -324,6 +258,48 @@ mod sq_ass_item_impl { } }) } - wrap:: + + TypedSlot(ffi::Py_sq_ass_item, wrap::) + } + + fn get_contains() -> TypedSlot + where + Self: for<'p> PySequenceContainsProtocol<'p>, + { + TypedSlot( + ffi::Py_sq_contains, + py_binary_func!(PySequenceContainsProtocol, Self::__contains__, c_int), + ) + } + + fn get_inplace_concat() -> TypedSlot + where + Self: for<'p> PySequenceInplaceConcatProtocol<'p>, + { + TypedSlot( + ffi::Py_sq_inplace_concat, + py_binary_func!( + PySequenceInplaceConcatProtocol, + Self::__inplace_concat__, + *mut ffi::PyObject, + call_mut + ), + ) + } + + fn get_inplace_repeat() -> TypedSlot + where + Self: for<'p> PySequenceInplaceRepeatProtocol<'p>, + { + TypedSlot( + ffi::Py_sq_inplace_repeat, + py_ssizearg_func!( + PySequenceInplaceRepeatProtocol, + Self::__inplace_repeat__, + call_mut + ), + ) } } + +impl<'p, T> PySequenceSlots for T where T: PySequenceProtocol<'p> {} From eb0e6f6b55ea00f0d6be504545e73edc27799216 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Tue, 27 Oct 2020 13:17:26 +0900 Subject: [PATCH 36/36] Note the minimum required version of maturin supporting abi3 --- guide/src/building_and_distribution.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/guide/src/building_and_distribution.md b/guide/src/building_and_distribution.md index 57464b493be..b0ed67c13cc 100644 --- a/guide/src/building_and_distribution.md +++ b/guide/src/building_and_distribution.md @@ -36,12 +36,15 @@ On Linux/macOS you might have to change `LD_LIBRARY_PATH` to include libpython, ## Distribution -There are two ways to distribute your module as a Python package: The old, [setuptools-rust](https://github.com/PyO3/setuptools-rust), and the new, [maturin](https://github.com/pyo3/maturin). setuptools-rust needs several configuration files (`setup.py`, `MANIFEST.in`, `build-wheels.sh`, etc.). maturin doesn't need any configuration files, however it does not support some functionality of setuptools such as package data ([pyo3/maturin#258](https://github.com/PyO3/maturin/issues/258)) and requires a rigid project structure, while setuptools-rust allows (and sometimes requires) configuration with python code. +There are two ways to distribute your module as a Python package: The old, [setuptools-rust], and the new, [maturin]. setuptools-rust needs several configuration files (`setup.py`, `MANIFEST.in`, `build-wheels.sh`, etc.). maturin doesn't need any configuration files, however it does not support some functionality of setuptools such as package data ([pyo3/maturin#258](https://github.com/PyO3/maturin/issues/258)) and requires a rigid project structure, while setuptools-rust allows (and sometimes requires) configuration with python code. ## `Py_LIMITED_API`/`abi3` By default, Python extension modules can only be used with the same Python version they were compiled against -- if you build an extension module with Python 3.5, you can't import it using Python 3.8. [PEP 384](https://www.python.org/dev/peps/pep-0384/) introduced the idea of the limited Python API, which would have a stable ABI enabling extension modules built with it to be used against multiple Python versions. This is also known as `abi3`. +Note that [maturin] >= 0.9.0 or [setuptools-rust] >= 0.12.0 is going to support `abi3` wheels. +See the [corresponding](https://github.com/PyO3/maturin/pull/353) [PRs](https://github.com/PyO3/setuptools-rust/pull/82) for more. + There are three steps involved in making use of `abi3` when building Python packages as wheels: 1. Enable the `abi3` feature in `pyo3`. This ensures `pyo3` only calls Python C-API functions which are part of the stable API, and on Windows also ensures that the project links against the correct shared object (no special behavior is required on other platforms): @@ -51,7 +54,7 @@ There are three steps involved in making use of `abi3` when building Python pack pyo3 = { version = "...", features = ["abi3"]} ``` -2. Ensure that the built shared objects are correctly marked as `abi3`. This is accomplished by telling your build system that you're using the limited API. There is currently a [PR for `setuptools-rust` for this](https://github.com/PyO3/setuptools-rust/pull/82). +2. Ensure that the built shared objects are correctly marked as `abi3`. This is accomplished by telling your build system that you're using the limited API. 3. Ensure that the `.whl` is correctly marked as `abi3`. For projects using `setuptools`, this is accomplished by passing `--py-limited-api=cp3x` (where `x` is the minimum Python version supported by the wheel, e.g. `--py-limited-api=cp35` for Python 3.5) to `setup.py bdist_wheel`. @@ -100,3 +103,8 @@ cargo build --target x86_64-pc-windows-gnu ## Bazel For an example of how to build python extensions using Bazel, see https://github.com/TheButlah/rules_pyo3 + + +[maturin]: https://github.com/PyO3/maturin +[setuptools-rust]: https://github.com/PyO3/setuptools-rust +