use crate::exceptions::PyStopAsyncIteration;
use crate::impl_::callback::IntoPyCallbackOutput;
use crate::impl_::panic::PanicTrap;
use crate::impl_::pycell::PyClassObjectBaseLayout;
use crate::internal::get_slot::{get_slot, TP_BASE, TP_CLEAR, TP_TRAVERSE};
use crate::internal::state::ForbidAttaching;
use crate::pycell::impl_::{PyClassBorrowChecker as _, PyClassObjectLayout};
use crate::pycell::{PyBorrowError, PyBorrowMutError};
use crate::pyclass::boolean_struct::False;
use crate::types::PyType;
use crate::{
ffi, Bound, CastError, Py, PyAny, PyClass, PyClassGuard, PyClassGuardMut, PyErr, PyRef,
PyRefMut, PyResult, PyTraverseError, PyTypeCheck, PyVisit, Python,
};
use std::ffi::CStr;
use std::ffi::{c_int, c_void};
use std::fmt;
use std::marker::PhantomData;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ptr::{null_mut, NonNull};
use super::pyclass::PyClassImpl;
use super::trampoline;
use crate::internal_tricks::{clear_eq, traverse_eq};
#[cfg(Py_3_8)]
#[repr(transparent)]
pub struct IPowModulo(*mut ffi::PyObject);
#[cfg(not(Py_3_8))]
#[repr(transparent)]
pub struct IPowModulo(#[allow(dead_code)] std::mem::MaybeUninit<*mut ffi::PyObject>);
#[allow(non_camel_case_types)]
pub type ipowfunc = unsafe extern "C" fn(
arg1: *mut ffi::PyObject,
arg2: *mut ffi::PyObject,
arg3: IPowModulo,
) -> *mut ffi::PyObject;
impl IPowModulo {
#[cfg(Py_3_8)]
#[inline]
pub fn as_ptr(self) -> *mut ffi::PyObject {
self.0
}
#[cfg(not(Py_3_8))]
#[inline]
pub fn as_ptr(self) -> *mut ffi::PyObject {
unsafe { ffi::Py_None() }
}
}
#[derive(Copy, Clone)]
pub enum PyMethodDefType {
Method(PyMethodDef),
ClassAttribute(PyClassAttributeDef),
Getter(PyGetterDef),
Setter(PySetterDef),
Deleter(PyDeleterDef),
StructMember(ffi::PyMemberDef),
}
#[derive(Copy, Clone, Debug)]
pub enum PyMethodType {
PyCFunction(ffi::PyCFunction),
PyCFunctionWithKeywords(ffi::PyCFunctionWithKeywords),
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
PyCFunctionFastWithKeywords(ffi::PyCFunctionFastWithKeywords),
}
pub type PyClassAttributeFactory = for<'p> fn(Python<'p>) -> PyResult<Py<PyAny>>;
#[derive(Copy, Clone, Debug)]
pub struct PyMethodDef {
pub(crate) ml_name: &'static CStr,
pub(crate) ml_meth: PyMethodType,
pub(crate) ml_flags: c_int,
pub(crate) ml_doc: &'static CStr,
}
#[derive(Copy, Clone)]
pub struct PyClassAttributeDef {
pub(crate) name: &'static CStr,
pub(crate) meth: PyClassAttributeFactory,
}
#[derive(Copy, Clone)]
pub struct PyGetterDef {
pub(crate) name: &'static CStr,
pub(crate) meth: Getter,
pub(crate) doc: &'static CStr,
}
#[derive(Copy, Clone)]
pub struct PySetterDef {
pub(crate) name: &'static CStr,
pub(crate) meth: Setter,
pub(crate) doc: &'static CStr,
}
#[derive(Copy, Clone)]
pub struct PyDeleterDef {
pub(crate) name: &'static CStr,
pub(crate) meth: Deleter,
pub(crate) doc: &'static CStr,
}
impl PyMethodDef {
pub const fn noargs(
ml_name: &'static CStr,
cfunction: ffi::PyCFunction,
ml_doc: &'static CStr,
) -> Self {
Self {
ml_name,
ml_meth: PyMethodType::PyCFunction(cfunction),
ml_flags: ffi::METH_NOARGS,
ml_doc,
}
}
pub const fn cfunction_with_keywords(
ml_name: &'static CStr,
cfunction: ffi::PyCFunctionWithKeywords,
ml_doc: &'static CStr,
) -> Self {
Self {
ml_name,
ml_meth: PyMethodType::PyCFunctionWithKeywords(cfunction),
ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS,
ml_doc,
}
}
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub const fn fastcall_cfunction_with_keywords(
ml_name: &'static CStr,
cfunction: ffi::PyCFunctionFastWithKeywords,
ml_doc: &'static CStr,
) -> Self {
Self {
ml_name,
ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS,
ml_doc,
}
}
pub const fn flags(mut self, flags: c_int) -> Self {
self.ml_flags |= flags;
self
}
pub const fn into_raw(self) -> ffi::PyMethodDef {
let meth = match self.ml_meth {
PyMethodType::PyCFunction(meth) => ffi::PyMethodDefPointer { PyCFunction: meth },
PyMethodType::PyCFunctionWithKeywords(meth) => ffi::PyMethodDefPointer {
PyCFunctionWithKeywords: meth,
},
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
PyMethodType::PyCFunctionFastWithKeywords(meth) => ffi::PyMethodDefPointer {
PyCFunctionFastWithKeywords: meth,
},
};
ffi::PyMethodDef {
ml_name: self.ml_name.as_ptr(),
ml_meth: meth,
ml_flags: self.ml_flags,
ml_doc: self.ml_doc.as_ptr(),
}
}
}
impl PyClassAttributeDef {
pub const fn new(name: &'static CStr, meth: PyClassAttributeFactory) -> Self {
Self { name, meth }
}
}
impl fmt::Debug for PyClassAttributeDef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PyClassAttributeDef")
.field("name", &self.name)
.finish()
}
}
pub(crate) type Getter =
for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> PyResult<*mut ffi::PyObject>;
pub(crate) type Setter =
for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject, *mut ffi::PyObject) -> PyResult<c_int>;
pub(crate) type Deleter = for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> PyResult<c_int>;
impl PyGetterDef {
pub const fn new(name: &'static CStr, getter: Getter, doc: &'static CStr) -> Self {
Self {
name,
meth: getter,
doc,
}
}
}
impl PySetterDef {
pub const fn new(name: &'static CStr, setter: Setter, doc: &'static CStr) -> Self {
Self {
name,
meth: setter,
doc,
}
}
}
impl PyDeleterDef {
pub const fn new(name: &'static CStr, deleter: Deleter, doc: &'static CStr) -> Self {
Self {
name,
meth: deleter,
doc,
}
}
}
#[doc(hidden)]
pub unsafe fn _call_traverse<T>(
slf: *mut ffi::PyObject,
impl_: fn(&T, PyVisit<'_>) -> Result<(), PyTraverseError>,
visit: ffi::visitproc,
arg: *mut c_void,
current_traverse: ffi::traverseproc,
) -> c_int
where
T: PyClass,
{
let trap = PanicTrap::new("uncaught panic inside __traverse__ handler");
let lock = ForbidAttaching::during_traverse();
let super_retval = unsafe { call_super_traverse(slf, visit, arg, current_traverse) };
if super_retval != 0 {
return super_retval;
}
let class_object: &<T as PyClassImpl>::Layout = unsafe { &*slf.cast() };
let retval =
if class_object.check_threadsafe().is_ok()
&& class_object.borrow_checker().try_borrow().is_ok() {
struct TraverseGuard<'a, T: PyClassImpl>(&'a T::Layout);
impl<T: PyClassImpl> Drop for TraverseGuard<'_, T> {
fn drop(&mut self) {
self.0.borrow_checker().release_borrow()
}
}
let _guard = TraverseGuard::<T>(class_object);
let instance = unsafe {&*class_object.contents().value.get()};
let visit = PyVisit { visit, arg, _guard: PhantomData };
match catch_unwind(AssertUnwindSafe(move || impl_(instance, visit))) {
Ok(Ok(())) => 0,
Ok(Err(traverse_error)) => traverse_error.into_inner(),
Err(_err) => -1,
}
} else {
0
};
drop(lock);
trap.disarm();
retval
}
unsafe fn call_super_traverse(
obj: *mut ffi::PyObject,
visit: ffi::visitproc,
arg: *mut c_void,
current_traverse: ffi::traverseproc,
) -> c_int {
let mut ty = unsafe { ffi::Py_TYPE(obj) };
let mut traverse: Option<ffi::traverseproc>;
loop {
traverse = unsafe { get_slot(ty, TP_TRAVERSE) };
if traverse_eq(traverse, current_traverse) {
break;
}
ty = unsafe { get_slot(ty, TP_BASE) };
if ty.is_null() {
return 0;
}
}
while traverse_eq(traverse, current_traverse) {
ty = unsafe { get_slot(ty, TP_BASE) };
if ty.is_null() {
break;
}
traverse = unsafe { get_slot(ty, TP_TRAVERSE) };
}
if let Some(traverse) = traverse {
return unsafe { traverse(obj, visit, arg) };
}
0
}
pub unsafe fn _call_clear(
slf: *mut ffi::PyObject,
impl_: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> PyResult<()>,
current_clear: ffi::inquiry,
) -> c_int {
unsafe {
trampoline::trampoline(move |py| {
let super_retval = call_super_clear(py, slf, current_clear);
if super_retval != 0 {
return Err(PyErr::fetch(py));
}
impl_(py, slf)?;
Ok(0)
})
}
}
unsafe fn call_super_clear(
py: Python<'_>,
obj: *mut ffi::PyObject,
current_clear: ffi::inquiry,
) -> c_int {
let mut ty = unsafe { PyType::from_borrowed_type_ptr(py, ffi::Py_TYPE(obj)) };
let mut clear: Option<ffi::inquiry>;
loop {
clear = ty.get_slot(TP_CLEAR);
if clear_eq(clear, current_clear) {
break;
}
let base = ty.get_slot(TP_BASE);
if base.is_null() {
return 0;
}
ty = unsafe { PyType::from_borrowed_type_ptr(py, base) };
}
while clear_eq(clear, current_clear) {
let base = ty.get_slot(TP_BASE);
if base.is_null() {
break;
}
ty = unsafe { PyType::from_borrowed_type_ptr(py, base) };
clear = ty.get_slot(TP_CLEAR);
}
if let Some(clear) = clear {
return unsafe { clear(obj) };
}
0
}
pub struct IterBaseTag;
impl IterBaseTag {
#[inline]
pub fn convert<'py, Value, Target>(self, py: Python<'py>, value: Value) -> PyResult<Target>
where
Value: IntoPyCallbackOutput<'py, Target>,
{
value.convert(py)
}
}
pub trait IterBaseKind {
#[inline]
fn iter_tag(&self) -> IterBaseTag {
IterBaseTag
}
}
impl<Value> IterBaseKind for &Value {}
pub struct IterOptionTag;
impl IterOptionTag {
#[inline]
pub fn convert<'py, Value>(
self,
py: Python<'py>,
value: Option<Value>,
) -> PyResult<*mut ffi::PyObject>
where
Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
{
match value {
Some(value) => value.convert(py),
None => Ok(null_mut()),
}
}
}
pub trait IterOptionKind {
#[inline]
fn iter_tag(&self) -> IterOptionTag {
IterOptionTag
}
}
impl<Value> IterOptionKind for Option<Value> {}
pub struct IterResultOptionTag;
impl IterResultOptionTag {
#[inline]
pub fn convert<'py, Value, Error>(
self,
py: Python<'py>,
value: Result<Option<Value>, Error>,
) -> PyResult<*mut ffi::PyObject>
where
Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
Error: Into<PyErr>,
{
match value {
Ok(Some(value)) => value.convert(py),
Ok(None) => Ok(null_mut()),
Err(err) => Err(err.into()),
}
}
}
pub trait IterResultOptionKind {
#[inline]
fn iter_tag(&self) -> IterResultOptionTag {
IterResultOptionTag
}
}
impl<Value, Error> IterResultOptionKind for Result<Option<Value>, Error> {}
pub struct AsyncIterBaseTag;
impl AsyncIterBaseTag {
#[inline]
pub fn convert<'py, Value, Target>(self, py: Python<'py>, value: Value) -> PyResult<Target>
where
Value: IntoPyCallbackOutput<'py, Target>,
{
value.convert(py)
}
}
pub trait AsyncIterBaseKind {
#[inline]
fn async_iter_tag(&self) -> AsyncIterBaseTag {
AsyncIterBaseTag
}
}
impl<Value> AsyncIterBaseKind for &Value {}
pub struct AsyncIterOptionTag;
impl AsyncIterOptionTag {
#[inline]
pub fn convert<'py, Value>(
self,
py: Python<'py>,
value: Option<Value>,
) -> PyResult<*mut ffi::PyObject>
where
Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
{
match value {
Some(value) => value.convert(py),
None => Err(PyStopAsyncIteration::new_err(())),
}
}
}
pub trait AsyncIterOptionKind {
#[inline]
fn async_iter_tag(&self) -> AsyncIterOptionTag {
AsyncIterOptionTag
}
}
impl<Value> AsyncIterOptionKind for Option<Value> {}
pub struct AsyncIterResultOptionTag;
impl AsyncIterResultOptionTag {
#[inline]
pub fn convert<'py, Value, Error>(
self,
py: Python<'py>,
value: Result<Option<Value>, Error>,
) -> PyResult<*mut ffi::PyObject>
where
Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
Error: Into<PyErr>,
{
match value {
Ok(Some(value)) => value.convert(py),
Ok(None) => Err(PyStopAsyncIteration::new_err(())),
Err(err) => Err(err.into()),
}
}
}
pub trait AsyncIterResultOptionKind {
#[inline]
fn async_iter_tag(&self) -> AsyncIterResultOptionTag {
AsyncIterResultOptionTag
}
}
impl<Value, Error> AsyncIterResultOptionKind for Result<Option<Value>, Error> {}
pub struct BoundRef<'a, 'py, T>(pub &'a Bound<'py, T>);
impl<'a, 'py> BoundRef<'a, 'py, PyAny> {
pub unsafe fn ref_from_ptr(py: Python<'py>, ptr: &'a *mut ffi::PyObject) -> Self {
unsafe { BoundRef(Bound::ref_from_ptr(py, ptr)) }
}
pub unsafe fn ref_from_ptr_or_opt(
py: Python<'py>,
ptr: &'a *mut ffi::PyObject,
) -> Option<Self> {
unsafe { Bound::ref_from_ptr_or_opt(py, ptr).as_ref().map(BoundRef) }
}
pub unsafe fn ref_from_non_null(py: Python<'py>, ptr: &'a NonNull<ffi::PyObject>) -> Self {
unsafe { Self(Bound::ref_from_non_null(py, ptr)) }
}
pub fn cast<T: PyTypeCheck>(self) -> Result<BoundRef<'a, 'py, T>, CastError<'a, 'py>> {
self.0.cast::<T>().map(BoundRef)
}
pub unsafe fn cast_unchecked<T>(self) -> BoundRef<'a, 'py, T> {
unsafe { BoundRef(self.0.cast_unchecked::<T>()) }
}
}
impl<'a, 'py, T: PyClass> TryFrom<BoundRef<'a, 'py, T>> for PyClassGuard<'a, T> {
type Error = PyBorrowError;
#[inline]
fn try_from(value: BoundRef<'a, 'py, T>) -> Result<Self, Self::Error> {
PyClassGuard::try_borrow(value.0.as_unbound())
}
}
impl<'a, 'py, T: PyClass<Frozen = False>> TryFrom<BoundRef<'a, 'py, T>> for PyClassGuardMut<'a, T> {
type Error = PyBorrowMutError;
#[inline]
fn try_from(value: BoundRef<'a, 'py, T>) -> Result<Self, Self::Error> {
PyClassGuardMut::try_borrow_mut(value.0.as_unbound())
}
}
impl<'a, 'py, T: PyClass> TryFrom<BoundRef<'a, 'py, T>> for PyRef<'py, T> {
type Error = PyBorrowError;
#[inline]
fn try_from(value: BoundRef<'a, 'py, T>) -> Result<Self, Self::Error> {
PyRef::try_borrow(value.0)
}
}
impl<'a, 'py, T: PyClass<Frozen = False>> TryFrom<BoundRef<'a, 'py, T>> for PyRefMut<'py, T> {
type Error = PyBorrowMutError;
#[inline]
fn try_from(value: BoundRef<'a, 'py, T>) -> Result<Self, Self::Error> {
PyRefMut::try_borrow(value.0)
}
}
impl<'a, 'py, T> From<BoundRef<'a, 'py, T>> for Bound<'py, T> {
#[inline]
fn from(bound: BoundRef<'a, 'py, T>) -> Self {
bound.0.clone()
}
}
impl<'a, 'py, T> From<BoundRef<'a, 'py, T>> for &'a Bound<'py, T> {
#[inline]
fn from(bound: BoundRef<'a, 'py, T>) -> Self {
bound.0
}
}
impl<T> From<BoundRef<'_, '_, T>> for Py<T> {
#[inline]
fn from(bound: BoundRef<'_, '_, T>) -> Self {
bound.0.clone().unbind()
}
}
impl<'py, T> std::ops::Deref for BoundRef<'_, 'py, T> {
type Target = Bound<'py, T>;
#[inline]
fn deref(&self) -> &Self::Target {
self.0
}
}
pub unsafe fn tp_new_impl<'py, T, const IS_PYCLASS: bool, const IS_INITIALIZER_TUPLE: bool>(
py: Python<'py>,
obj: T,
cls: *mut ffi::PyTypeObject,
) -> PyResult<*mut ffi::PyObject>
where
T: super::pyclass_init::PyClassInit<'py, IS_PYCLASS, IS_INITIALIZER_TUPLE>,
{
unsafe {
obj.init(crate::Borrowed::from_ptr_unchecked(py, cls.cast()).cast_unchecked())
.map(Bound::into_ptr)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
fn test_fastcall_function_with_keywords() {
use super::PyMethodDef;
use crate::impl_::pyfunction::PyFunctionDef;
use crate::types::PyAnyMethods;
use crate::{ffi, Python};
Python::attach(|py| {
let def =
PyFunctionDef::from_method_def(PyMethodDef::fastcall_cfunction_with_keywords(
c"test",
accepts_no_arguments,
c"doc",
));
let def = Box::leak(Box::new(def));
unsafe extern "C" fn accepts_no_arguments(
_slf: *mut ffi::PyObject,
_args: *const *mut ffi::PyObject,
nargs: ffi::Py_ssize_t,
kwargs: *mut ffi::PyObject,
) -> *mut ffi::PyObject {
assert_eq!(nargs, 0);
assert!(kwargs.is_null());
unsafe { Python::assume_attached().None().into_ptr() }
}
let f = def.create_py_c_function(py, None).unwrap();
f.call0().unwrap();
});
}
}