use crate::{
exceptions::{PyAttributeError, PyNotImplementedError},
ffi,
impl_::freelist::FreeList,
impl_::pycell::{GetBorrowChecker, PyClassMutability},
pycell::PyCellLayout,
pyclass_init::PyObjectInit,
type_object::PyLayout,
Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python,
};
use std::{
marker::PhantomData,
os::raw::{c_int, c_void},
ptr::NonNull,
thread,
};
mod lazy_type_object;
pub use lazy_type_object::LazyTypeObject;
#[inline]
pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
PyCell::<T>::dict_offset()
}
#[inline]
pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
PyCell::<T>::weaklist_offset()
}
pub trait PyClassDict {
const INIT: Self;
#[inline]
fn clear_dict(&mut self, _py: Python<'_>) {}
private_decl! {}
}
pub trait PyClassWeakRef {
const INIT: Self;
#[inline]
unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python<'_>) {}
private_decl! {}
}
pub struct PyClassDummySlot;
impl PyClassDict for PyClassDummySlot {
private_impl! {}
const INIT: Self = PyClassDummySlot;
}
impl PyClassWeakRef for PyClassDummySlot {
private_impl! {}
const INIT: Self = PyClassDummySlot;
}
#[repr(transparent)]
pub struct PyClassDictSlot(*mut ffi::PyObject);
impl PyClassDict for PyClassDictSlot {
private_impl! {}
const INIT: Self = Self(std::ptr::null_mut());
#[inline]
fn clear_dict(&mut self, _py: Python<'_>) {
if !self.0.is_null() {
unsafe { ffi::PyDict_Clear(self.0) }
}
}
}
#[repr(transparent)]
pub struct PyClassWeakRefSlot(*mut ffi::PyObject);
impl PyClassWeakRef for PyClassWeakRefSlot {
private_impl! {}
const INIT: Self = Self(std::ptr::null_mut());
#[inline]
unsafe fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python<'_>) {
if !self.0.is_null() {
ffi::PyObject_ClearWeakRefs(obj)
}
}
}
pub struct PyClassImplCollector<T>(PhantomData<T>);
impl<T> PyClassImplCollector<T> {
pub fn new() -> Self {
Self(PhantomData)
}
}
impl<T> Default for PyClassImplCollector<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Clone for PyClassImplCollector<T> {
fn clone(&self) -> Self {
Self::new()
}
}
impl<T> Copy for PyClassImplCollector<T> {}
pub struct PyClassItems {
pub methods: &'static [PyMethodDefType],
pub slots: &'static [ffi::PyType_Slot],
}
unsafe impl Sync for PyClassItems {}
pub trait PyClassImpl: Sized + 'static {
const DOC: &'static str = "\0";
const IS_BASETYPE: bool = false;
const IS_SUBCLASS: bool = false;
const IS_MAPPING: bool = false;
const IS_SEQUENCE: bool = false;
type Layout: PyLayout<Self>;
type BaseType: PyTypeInfo + PyClassBaseType;
type PyClassMutability: PyClassMutability + GetBorrowChecker<Self>;
type Dict: PyClassDict;
type WeakRef: PyClassWeakRef;
type BaseNativeType: PyTypeInfo + PyNativeType;
type ThreadChecker: PyClassThreadChecker<Self>;
#[cfg(feature = "multiple-pymethods")]
type Inventory: PyClassInventory;
fn items_iter() -> PyClassItemsIter;
#[inline]
fn dict_offset() -> Option<ffi::Py_ssize_t> {
None
}
#[inline]
fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
None
}
fn lazy_type_object() -> &'static LazyTypeObject<Self>;
}
pub struct PyClassItemsIter {
idx: usize,
pyclass_items: &'static PyClassItems,
#[cfg(not(feature = "multiple-pymethods"))]
pymethods_items: &'static PyClassItems,
#[cfg(feature = "multiple-pymethods")]
pymethods_items: Box<dyn Iterator<Item = &'static PyClassItems>>,
}
impl PyClassItemsIter {
pub fn new(
pyclass_items: &'static PyClassItems,
#[cfg(not(feature = "multiple-pymethods"))] pymethods_items: &'static PyClassItems,
#[cfg(feature = "multiple-pymethods")] pymethods_items: Box<
dyn Iterator<Item = &'static PyClassItems>,
>,
) -> Self {
Self {
idx: 0,
pyclass_items,
pymethods_items,
}
}
}
impl Iterator for PyClassItemsIter {
type Item = &'static PyClassItems;
#[cfg(not(feature = "multiple-pymethods"))]
fn next(&mut self) -> Option<Self::Item> {
match self.idx {
0 => {
self.idx += 1;
Some(self.pyclass_items)
}
1 => {
self.idx += 1;
Some(self.pymethods_items)
}
_ => None,
}
}
#[cfg(feature = "multiple-pymethods")]
fn next(&mut self) -> Option<Self::Item> {
match self.idx {
0 => {
self.idx += 1;
Some(self.pyclass_items)
}
_ => self.pymethods_items.next(),
}
}
}
macro_rules! slot_fragment_trait {
($trait_name:ident, $($default_method:tt)*) => {
#[allow(non_camel_case_types)]
pub trait $trait_name<T>: Sized {
$($default_method)*
}
impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
}
}
slot_fragment_trait! {
PyClass__getattribute__SlotFragment,
#[inline]
unsafe fn __getattribute__(
self,
py: Python<'_>,
slf: *mut ffi::PyObject,
attr: *mut ffi::PyObject,
) -> PyResult<*mut ffi::PyObject> {
let res = ffi::PyObject_GenericGetAttr(slf, attr);
if res.is_null() {
Err(PyErr::fetch(py))
} else {
Ok(res)
}
}
}
slot_fragment_trait! {
PyClass__getattr__SlotFragment,
#[inline]
unsafe fn __getattr__(
self,
py: Python<'_>,
_slf: *mut ffi::PyObject,
attr: *mut ffi::PyObject,
) -> PyResult<*mut ffi::PyObject> {
Err(PyErr::new::<PyAttributeError, _>(
(Py::<PyAny>::from_borrowed_ptr(py, attr),)
))
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! generate_pyclass_getattro_slot {
($cls:ty) => {{
unsafe extern "C" fn __wrap(
_slf: *mut $crate::ffi::PyObject,
attr: *mut $crate::ffi::PyObject,
) -> *mut $crate::ffi::PyObject {
$crate::impl_::trampoline::getattrofunc(_slf, attr, |py, _slf, attr| {
use ::std::result::Result::*;
use $crate::impl_::pyclass::*;
let collector = PyClassImplCollector::<$cls>::new();
match collector.__getattribute__(py, _slf, attr) {
Ok(obj) => Ok(obj),
Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
collector.__getattr__(py, _slf, attr)
}
Err(e) => Err(e),
}
})
}
$crate::ffi::PyType_Slot {
slot: $crate::ffi::Py_tp_getattro,
pfunc: __wrap as $crate::ffi::getattrofunc as _,
}
}};
}
pub use generate_pyclass_getattro_slot;
macro_rules! define_pyclass_setattr_slot {
(
$set_trait:ident,
$del_trait:ident,
$set:ident,
$del:ident,
$set_error:expr,
$del_error:expr,
$generate_macro:ident,
$slot:ident,
$func_ty:ident,
) => {
slot_fragment_trait! {
$set_trait,
#[inline]
unsafe fn $set(
self,
_py: Python<'_>,
_slf: *mut ffi::PyObject,
_attr: *mut ffi::PyObject,
_value: NonNull<ffi::PyObject>,
) -> PyResult<()> {
$set_error
}
}
slot_fragment_trait! {
$del_trait,
#[inline]
unsafe fn $del(
self,
_py: Python<'_>,
_slf: *mut ffi::PyObject,
_attr: *mut ffi::PyObject,
) -> PyResult<()> {
$del_error
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! $generate_macro {
($cls:ty) => {{
unsafe extern "C" fn __wrap(
_slf: *mut $crate::ffi::PyObject,
attr: *mut $crate::ffi::PyObject,
value: *mut $crate::ffi::PyObject,
) -> ::std::os::raw::c_int {
$crate::impl_::trampoline::setattrofunc(
_slf,
attr,
value,
|py, _slf, attr, value| {
use ::std::option::Option::*;
use $crate::callback::IntoPyCallbackOutput;
use $crate::impl_::pyclass::*;
let collector = PyClassImplCollector::<$cls>::new();
if let Some(value) = ::std::ptr::NonNull::new(value) {
collector.$set(py, _slf, attr, value).convert(py)
} else {
collector.$del(py, _slf, attr).convert(py)
}
},
)
}
$crate::ffi::PyType_Slot {
slot: $crate::ffi::$slot,
pfunc: __wrap as $crate::ffi::$func_ty as _,
}
}};
}
pub use $generate_macro;
};
}
define_pyclass_setattr_slot! {
PyClass__setattr__SlotFragment,
PyClass__delattr__SlotFragment,
__setattr__,
__delattr__,
Err(PyAttributeError::new_err("can't set attribute")),
Err(PyAttributeError::new_err("can't delete attribute")),
generate_pyclass_setattr_slot,
Py_tp_setattro,
setattrofunc,
}
define_pyclass_setattr_slot! {
PyClass__set__SlotFragment,
PyClass__delete__SlotFragment,
__set__,
__delete__,
Err(PyNotImplementedError::new_err("can't set descriptor")),
Err(PyNotImplementedError::new_err("can't delete descriptor")),
generate_pyclass_setdescr_slot,
Py_tp_descr_set,
descrsetfunc,
}
define_pyclass_setattr_slot! {
PyClass__setitem__SlotFragment,
PyClass__delitem__SlotFragment,
__setitem__,
__delitem__,
Err(PyNotImplementedError::new_err("can't set item")),
Err(PyNotImplementedError::new_err("can't delete item")),
generate_pyclass_setitem_slot,
Py_mp_ass_subscript,
objobjargproc,
}
macro_rules! define_pyclass_binary_operator_slot {
(
$lhs_trait:ident,
$rhs_trait:ident,
$lhs:ident,
$rhs:ident,
$generate_macro:ident,
$slot:ident,
$func_ty:ident,
) => {
slot_fragment_trait! {
$lhs_trait,
#[inline]
unsafe fn $lhs(
self,
_py: Python<'_>,
_slf: *mut ffi::PyObject,
_other: *mut ffi::PyObject,
) -> PyResult<*mut ffi::PyObject> {
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
}
}
slot_fragment_trait! {
$rhs_trait,
#[inline]
unsafe fn $rhs(
self,
_py: Python<'_>,
_slf: *mut ffi::PyObject,
_other: *mut ffi::PyObject,
) -> PyResult<*mut ffi::PyObject> {
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! $generate_macro {
($cls:ty) => {{
unsafe extern "C" fn __wrap(
_slf: *mut $crate::ffi::PyObject,
_other: *mut $crate::ffi::PyObject,
) -> *mut $crate::ffi::PyObject {
$crate::impl_::trampoline::binaryfunc(_slf, _other, |py, _slf, _other| {
use $crate::impl_::pyclass::*;
let collector = PyClassImplCollector::<$cls>::new();
let lhs_result = collector.$lhs(py, _slf, _other)?;
if lhs_result == $crate::ffi::Py_NotImplemented() {
$crate::ffi::Py_DECREF(lhs_result);
collector.$rhs(py, _other, _slf)
} else {
::std::result::Result::Ok(lhs_result)
}
})
}
$crate::ffi::PyType_Slot {
slot: $crate::ffi::$slot,
pfunc: __wrap as $crate::ffi::$func_ty as _,
}
}};
}
pub use $generate_macro;
};
}
define_pyclass_binary_operator_slot! {
PyClass__add__SlotFragment,
PyClass__radd__SlotFragment,
__add__,
__radd__,
generate_pyclass_add_slot,
Py_nb_add,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__sub__SlotFragment,
PyClass__rsub__SlotFragment,
__sub__,
__rsub__,
generate_pyclass_sub_slot,
Py_nb_subtract,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__mul__SlotFragment,
PyClass__rmul__SlotFragment,
__mul__,
__rmul__,
generate_pyclass_mul_slot,
Py_nb_multiply,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__mod__SlotFragment,
PyClass__rmod__SlotFragment,
__mod__,
__rmod__,
generate_pyclass_mod_slot,
Py_nb_remainder,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__divmod__SlotFragment,
PyClass__rdivmod__SlotFragment,
__divmod__,
__rdivmod__,
generate_pyclass_divmod_slot,
Py_nb_divmod,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__lshift__SlotFragment,
PyClass__rlshift__SlotFragment,
__lshift__,
__rlshift__,
generate_pyclass_lshift_slot,
Py_nb_lshift,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__rshift__SlotFragment,
PyClass__rrshift__SlotFragment,
__rshift__,
__rrshift__,
generate_pyclass_rshift_slot,
Py_nb_rshift,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__and__SlotFragment,
PyClass__rand__SlotFragment,
__and__,
__rand__,
generate_pyclass_and_slot,
Py_nb_and,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__or__SlotFragment,
PyClass__ror__SlotFragment,
__or__,
__ror__,
generate_pyclass_or_slot,
Py_nb_or,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__xor__SlotFragment,
PyClass__rxor__SlotFragment,
__xor__,
__rxor__,
generate_pyclass_xor_slot,
Py_nb_xor,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__matmul__SlotFragment,
PyClass__rmatmul__SlotFragment,
__matmul__,
__rmatmul__,
generate_pyclass_matmul_slot,
Py_nb_matrix_multiply,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__truediv__SlotFragment,
PyClass__rtruediv__SlotFragment,
__truediv__,
__rtruediv__,
generate_pyclass_truediv_slot,
Py_nb_true_divide,
binaryfunc,
}
define_pyclass_binary_operator_slot! {
PyClass__floordiv__SlotFragment,
PyClass__rfloordiv__SlotFragment,
__floordiv__,
__rfloordiv__,
generate_pyclass_floordiv_slot,
Py_nb_floor_divide,
binaryfunc,
}
slot_fragment_trait! {
PyClass__pow__SlotFragment,
#[inline]
unsafe fn __pow__(
self,
_py: Python<'_>,
_slf: *mut ffi::PyObject,
_other: *mut ffi::PyObject,
_mod: *mut ffi::PyObject,
) -> PyResult<*mut ffi::PyObject> {
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
}
}
slot_fragment_trait! {
PyClass__rpow__SlotFragment,
#[inline]
unsafe fn __rpow__(
self,
_py: Python<'_>,
_slf: *mut ffi::PyObject,
_other: *mut ffi::PyObject,
_mod: *mut ffi::PyObject,
) -> PyResult<*mut ffi::PyObject> {
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! generate_pyclass_pow_slot {
($cls:ty) => {{
unsafe extern "C" fn __wrap(
_slf: *mut $crate::ffi::PyObject,
_other: *mut $crate::ffi::PyObject,
_mod: *mut $crate::ffi::PyObject,
) -> *mut $crate::ffi::PyObject {
$crate::impl_::trampoline::ternaryfunc(_slf, _other, _mod, |py, _slf, _other, _mod| {
use $crate::impl_::pyclass::*;
let collector = PyClassImplCollector::<$cls>::new();
let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
if lhs_result == $crate::ffi::Py_NotImplemented() {
$crate::ffi::Py_DECREF(lhs_result);
collector.__rpow__(py, _other, _slf, _mod)
} else {
::std::result::Result::Ok(lhs_result)
}
})
}
$crate::ffi::PyType_Slot {
slot: $crate::ffi::Py_nb_power,
pfunc: __wrap as $crate::ffi::ternaryfunc as _,
}
}};
}
pub use generate_pyclass_pow_slot;
pub trait PyClassWithFreeList: PyClass {
fn get_free_list(py: Python<'_>) -> &mut FreeList<*mut ffi::PyObject>;
}
pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
subtype: *mut ffi::PyTypeObject,
nitems: ffi::Py_ssize_t,
) -> *mut ffi::PyObject {
let py = Python::assume_gil_acquired();
#[cfg(not(Py_3_8))]
bpo_35810_workaround(py, subtype);
let self_type = T::type_object_raw(py);
if nitems == 0 && subtype == self_type {
if let Some(obj) = T::get_free_list(py).pop() {
ffi::PyObject_Init(obj, subtype);
return obj as _;
}
}
ffi::PyType_GenericAlloc(subtype, nitems)
}
pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
let obj = obj as *mut ffi::PyObject;
debug_assert_eq!(
T::type_object_raw(Python::assume_gil_acquired()),
ffi::Py_TYPE(obj)
);
if let Some(obj) = T::get_free_list(Python::assume_gil_acquired()).insert(obj) {
let ty = ffi::Py_TYPE(obj);
let free = if ffi::PyType_IS_GC(ty) != 0 {
ffi::PyObject_GC_Del
} else {
ffi::PyObject_Free
};
free(obj as *mut c_void);
#[cfg(Py_3_8)]
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}
#[inline]
#[cfg(not(Py_3_8))]
unsafe fn bpo_35810_workaround(_py: Python<'_>, ty: *mut ffi::PyTypeObject) {
#[cfg(Py_LIMITED_API)]
{
use crate::sync::GILOnceCell;
static IS_PYTHON_3_8: GILOnceCell<bool> = GILOnceCell::new();
if *IS_PYTHON_3_8.get_or_init(_py, || _py.version_info() >= (3, 8)) {
return;
}
}
ffi::Py_INCREF(ty as *mut ffi::PyObject);
}
#[cfg(feature = "multiple-pymethods")]
pub trait PyClassInventory: inventory::Collect {
fn items(&'static self) -> &'static PyClassItems;
}
#[cfg(not(feature = "multiple-pymethods"))]
pub trait PyMethods<T> {
fn py_methods(self) -> &'static PyClassItems;
}
#[cfg(not(feature = "multiple-pymethods"))]
impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
fn py_methods(self) -> &'static PyClassItems {
&PyClassItems {
methods: &[],
slots: &[],
}
}
}
#[doc(hidden)]
pub trait PyClassThreadChecker<T>: Sized {
fn ensure(&self);
fn new() -> Self;
private_decl! {}
}
#[doc(hidden)]
pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
impl<T: Send> PyClassThreadChecker<T> for ThreadCheckerStub<T> {
fn ensure(&self) {}
#[inline]
fn new() -> Self {
ThreadCheckerStub(PhantomData)
}
private_impl! {}
}
impl<T: PyNativeType> PyClassThreadChecker<T> for ThreadCheckerStub<crate::PyObject> {
fn ensure(&self) {}
#[inline]
fn new() -> Self {
ThreadCheckerStub(PhantomData)
}
private_impl! {}
}
#[doc(hidden)]
pub struct ThreadCheckerImpl<T>(thread::ThreadId, PhantomData<T>);
impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl<T> {
fn ensure(&self) {
assert_eq!(
thread::current().id(),
self.0,
"{} is unsendable, but sent to another thread!",
std::any::type_name::<T>()
);
}
fn new() -> Self {
ThreadCheckerImpl(thread::current().id(), PhantomData)
}
private_impl! {}
}
#[doc(hidden)]
pub struct ThreadCheckerInherited<T: PyClass + Send, U: PyClassBaseType>(
PhantomData<T>,
U::ThreadChecker,
);
impl<T: PyClass + Send, U: PyClassBaseType> PyClassThreadChecker<T>
for ThreadCheckerInherited<T, U>
{
fn ensure(&self) {
self.1.ensure();
}
fn new() -> Self {
ThreadCheckerInherited(PhantomData, U::ThreadChecker::new())
}
private_impl! {}
}
pub trait PyClassBaseType: Sized {
type LayoutAsBase: PyCellLayout<Self>;
type BaseNativeType;
type ThreadChecker: PyClassThreadChecker<Self>;
type Initializer: PyObjectInit<Self>;
type PyClassMutability: PyClassMutability;
}
impl<T: PyClass> PyClassBaseType for T {
type LayoutAsBase = crate::pycell::PyCell<T>;
type BaseNativeType = T::BaseNativeType;
type ThreadChecker = T::ThreadChecker;
type Initializer = crate::pyclass_init::PyClassInitializer<Self>;
type PyClassMutability = T::PyClassMutability;
}
pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
crate::impl_::trampoline::dealloc(obj, T::Layout::tp_dealloc)
}
pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
obj: *mut ffi::PyObject,
index: ffi::Py_ssize_t,
) -> *mut ffi::PyObject {
let index = ffi::PyLong_FromSsize_t(index);
if index.is_null() {
return std::ptr::null_mut();
}
let result = ffi::PyObject_GetItem(obj, index);
ffi::Py_DECREF(index);
result
}
pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
obj: *mut ffi::PyObject,
index: ffi::Py_ssize_t,
value: *mut ffi::PyObject,
) -> c_int {
let index = ffi::PyLong_FromSsize_t(index);
if index.is_null() {
return -1;
}
let result = if value.is_null() {
ffi::PyObject_DelItem(obj, index)
} else {
ffi::PyObject_SetItem(obj, index, value)
};
ffi::Py_DECREF(index);
result
}