1use crate::exceptions::PyStopAsyncIteration;
2use crate::impl_::callback::IntoPyCallbackOutput;
3use crate::impl_::panic::PanicTrap;
4use crate::impl_::pycell::PyClassObjectBaseLayout;
5use crate::internal::get_slot::{get_slot, TP_BASE, TP_CLEAR, TP_TRAVERSE};
6use crate::internal::state::ForbidAttaching;
7use crate::pycell::impl_::{PyClassBorrowChecker as _, PyClassObjectLayout};
8use crate::types::PyType;
9use crate::{ffi, Bound, Py, PyAny, PyClass, PyErr, PyResult, PyTraverseError, PyVisit, Python};
10use std::ffi::CStr;
11use std::ffi::{c_int, c_void};
12use std::fmt;
13use std::marker::PhantomData;
14use std::panic::{catch_unwind, AssertUnwindSafe};
15use std::ptr::{null_mut, NonNull};
16
17use super::pyclass::PyClassImpl;
18use super::trampoline;
19use crate::internal_tricks::{clear_eq, traverse_eq};
20
21#[derive(Copy, Clone)]
24pub enum PyMethodDefType {
25 Method(PyMethodDef),
27 ClassAttribute(PyClassAttributeDef),
29 Getter(PyGetterDef),
31 Setter(PySetterDef),
33 Deleter(PyDeleterDef),
35 StructMember(ffi::PyMemberDef),
37}
38
39#[derive(Copy, Clone, Debug)]
40pub enum PyMethodType {
41 PyCFunction(ffi::PyCFunction),
42 PyCFunctionWithKeywords(ffi::PyCFunctionWithKeywords),
43 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
44 PyCFunctionFastWithKeywords(ffi::PyCFunctionFastWithKeywords),
45}
46
47pub type PyClassAttributeFactory = for<'p> fn(Python<'p>) -> PyResult<Py<PyAny>>;
48
49#[derive(Copy, Clone, Debug)]
50pub struct PyMethodDef {
51 pub(crate) ml_name: &'static CStr,
52 pub(crate) ml_meth: PyMethodType,
53 pub(crate) ml_flags: c_int,
54 pub(crate) ml_doc: &'static CStr,
55}
56
57#[derive(Copy, Clone)]
58pub struct PyClassAttributeDef {
59 pub(crate) name: &'static CStr,
60 pub(crate) meth: PyClassAttributeFactory,
61}
62
63#[derive(Copy, Clone)]
64pub struct PyGetterDef {
65 pub(crate) name: &'static CStr,
66 pub(crate) meth: Getter,
67 pub(crate) doc: Option<&'static CStr>,
68}
69
70#[derive(Copy, Clone)]
71pub struct PySetterDef {
72 pub(crate) name: &'static CStr,
73 pub(crate) meth: Setter,
74 pub(crate) doc: Option<&'static CStr>,
75}
76
77#[derive(Copy, Clone)]
78pub struct PyDeleterDef {
79 pub(crate) name: &'static CStr,
80 pub(crate) meth: Deleter,
81 pub(crate) doc: Option<&'static CStr>,
82}
83
84#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
87#[macro_export]
88#[doc(hidden)]
89macro_rules! maybe_define_fastcall_function_with_keywords {
90 ($name:ident, $py:ident, $slf:ident, $args:ident, $nargs:ident, $kwargs:ident, $body:block) => {
91 #[allow(non_snake_case)]
92 unsafe fn $name<'py>(
93 $py: $crate::Python<'py>,
94 $slf: *mut $crate::ffi::PyObject,
95 $args: *const *mut $crate::ffi::PyObject,
96 $nargs: $crate::ffi::Py_ssize_t,
97 $kwargs: *mut $crate::ffi::PyObject
98 ) -> $crate::PyResult<*mut $crate::ffi::PyObject> $body
99 };
100}
101
102#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
104#[macro_export]
105#[doc(hidden)]
106macro_rules! maybe_define_fastcall_function_with_keywords {
107 ($name:ident, $py:ident, $slf:ident, $args:ident, $nargs:ident, $kwargs:ident, $body:block) => {
108 #[allow(non_snake_case)]
109 unsafe fn $name<'py>(
110 $py: $crate::Python<'py>,
111 $slf: *mut $crate::ffi::PyObject,
112 $args: *mut $crate::ffi::PyObject,
113 $kwargs: *mut $crate::ffi::PyObject
114 ) -> $crate::PyResult<*mut $crate::ffi::PyObject> $body
115 };
116}
117
118pub use crate::maybe_define_fastcall_function_with_keywords;
119
120#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
121#[macro_export]
122#[doc(hidden)]
123macro_rules! maybe_extract_arguments_fastcall {
124 ($description:ident, $py:ident, $args:ident, $nargs:ident, $kwargs:ident, $args_array:ident, $args_handler:ty, $kwargs_handler:ty) => {
125 unsafe {
127 $description.extract_arguments_fastcall::<$args_handler, $kwargs_handler>(
128 $py,
129 $args,
130 $nargs,
131 $kwargs,
132 &mut $args_array,
133 )
134 }
135 };
136}
137
138#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
139#[macro_export]
140#[doc(hidden)]
141macro_rules! maybe_extract_arguments_fastcall {
142 ($description:ident, $py:ident, $args:ident, $nargs:ident, $kwargs:ident, $args_array:ident, $args_handler:ty, $kwargs_handler:ty) => {
143 unsafe {
145 $description.extract_arguments_tuple_dict::<$args_handler, $kwargs_handler>(
146 $py,
147 $args,
148 $kwargs,
149 &mut $args_array,
150 )
151 }
152 };
153}
154
155pub use crate::maybe_extract_arguments_fastcall;
156
157impl PyMethodDef {
158 pub const fn noargs(
160 ml_name: &'static CStr,
161 cfunction: ffi::PyCFunction,
162 ml_doc: &'static CStr,
163 ) -> Self {
164 Self {
165 ml_name,
166 ml_meth: PyMethodType::PyCFunction(cfunction),
167 ml_flags: ffi::METH_NOARGS,
168 ml_doc,
169 }
170 }
171
172 pub const fn cfunction_with_keywords(
174 ml_name: &'static CStr,
175 cfunction: ffi::PyCFunctionWithKeywords,
176 ml_doc: &'static CStr,
177 ) -> Self {
178 Self {
179 ml_name,
180 ml_meth: PyMethodType::PyCFunctionWithKeywords(cfunction),
181 ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS,
182 ml_doc,
183 }
184 }
185
186 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
188 pub const fn fastcall_cfunction_with_keywords(
189 ml_name: &'static CStr,
190 cfunction: ffi::PyCFunctionFastWithKeywords,
191 ml_doc: &'static CStr,
192 ) -> Self {
193 Self {
194 ml_name,
195 ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
196 ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS,
197 ml_doc,
198 }
199 }
200
201 pub const fn maybe_fastcall_cfunction_with_keywords(
203 ml_name: &'static CStr,
204 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] cfunction: ffi::PyCFunctionFastWithKeywords,
205 #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))] cfunction: ffi::PyCFunctionWithKeywords,
207 ml_doc: &'static CStr,
208 ) -> Self {
209 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
210 {
211 Self::fastcall_cfunction_with_keywords(ml_name, cfunction, ml_doc)
212 }
213
214 #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
215 {
216 Self::cfunction_with_keywords(ml_name, cfunction, ml_doc)
217 }
218 }
219
220 pub const fn flags(mut self, flags: c_int) -> Self {
221 self.ml_flags |= flags;
222 self
223 }
224
225 pub const fn into_raw(self) -> ffi::PyMethodDef {
226 let meth = match self.ml_meth {
227 PyMethodType::PyCFunction(meth) => ffi::PyMethodDefPointer { PyCFunction: meth },
228 PyMethodType::PyCFunctionWithKeywords(meth) => ffi::PyMethodDefPointer {
229 PyCFunctionWithKeywords: meth,
230 },
231 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
232 PyMethodType::PyCFunctionFastWithKeywords(meth) => ffi::PyMethodDefPointer {
233 PyCFunctionFastWithKeywords: meth,
234 },
235 };
236
237 ffi::PyMethodDef {
238 ml_name: self.ml_name.as_ptr(),
239 ml_meth: meth,
240 ml_flags: self.ml_flags,
241 ml_doc: self.ml_doc.as_ptr(),
242 }
243 }
244}
245
246impl PyClassAttributeDef {
247 pub const fn new(name: &'static CStr, meth: PyClassAttributeFactory) -> Self {
249 Self { name, meth }
250 }
251}
252
253impl fmt::Debug for PyClassAttributeDef {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 f.debug_struct("PyClassAttributeDef")
258 .field("name", &self.name)
259 .finish()
260 }
261}
262
263pub(crate) type Getter =
265 for<'py> unsafe fn(Python<'py>, NonNull<ffi::PyObject>) -> PyResult<*mut ffi::PyObject>;
266pub(crate) type Setter = for<'py> unsafe fn(
267 Python<'py>,
268 NonNull<ffi::PyObject>,
269 NonNull<ffi::PyObject>,
270) -> PyResult<c_int>;
271pub(crate) type Deleter =
272 for<'py> unsafe fn(Python<'py>, NonNull<ffi::PyObject>) -> PyResult<c_int>;
273
274impl PyGetterDef {
275 pub const fn new(name: &'static CStr, getter: Getter, doc: Option<&'static CStr>) -> Self {
277 Self {
278 name,
279 meth: getter,
280 doc,
281 }
282 }
283}
284
285impl PySetterDef {
286 pub const fn new(name: &'static CStr, setter: Setter, doc: Option<&'static CStr>) -> Self {
288 Self {
289 name,
290 meth: setter,
291 doc,
292 }
293 }
294}
295
296impl PyDeleterDef {
297 pub const fn new(name: &'static CStr, deleter: Deleter, doc: Option<&'static CStr>) -> Self {
299 Self {
300 name,
301 meth: deleter,
302 doc,
303 }
304 }
305}
306
307#[doc(hidden)]
343pub unsafe fn _call_traverse<T>(
344 slf: *mut ffi::PyObject,
345 impl_: fn(&T, PyVisit<'_>) -> Result<(), PyTraverseError>,
346 visit: ffi::visitproc,
347 arg: *mut c_void,
348 current_traverse: ffi::traverseproc,
349) -> c_int
350where
351 T: PyClass,
352{
353 let trap = PanicTrap::new("uncaught panic inside __traverse__ handler");
359 let lock = ForbidAttaching::during_traverse();
360
361 let super_retval = unsafe { call_super_traverse(slf, visit, arg, current_traverse) };
362 if super_retval != 0 {
363 return super_retval;
364 }
365
366 let class_object: &<T as PyClassImpl>::Layout = unsafe { &*slf.cast() };
369
370 let retval =
371 if class_object.check_threadsafe().is_ok()
374 && class_object.borrow_checker().try_borrow().is_ok() {
376 struct TraverseGuard<'a, T: PyClassImpl>(&'a T::Layout);
377 impl<T: PyClassImpl> Drop for TraverseGuard<'_, T> {
378 fn drop(&mut self) {
379 self.0.borrow_checker().release_borrow()
380 }
381 }
382
383 let _guard = TraverseGuard::<T>(class_object);
386 let instance = unsafe {&*class_object.contents().value.get()};
387
388 let visit = PyVisit { visit, arg, _guard: PhantomData };
389
390 match catch_unwind(AssertUnwindSafe(move || impl_(instance, visit))) {
391 Ok(Ok(())) => 0,
392 Ok(Err(traverse_error)) => traverse_error.into_inner(),
393 Err(_err) => -1,
394 }
395 } else {
396 0
397 };
398
399 drop(lock);
401 trap.disarm();
402 retval
403}
404
405unsafe fn call_super_traverse(
416 obj: *mut ffi::PyObject,
417 visit: ffi::visitproc,
418 arg: *mut c_void,
419 current_traverse: ffi::traverseproc,
420) -> c_int {
421 let mut ty = unsafe { ffi::Py_TYPE(obj) };
426 let mut traverse: Option<ffi::traverseproc>;
427
428 loop {
430 traverse = unsafe { get_slot(ty, TP_TRAVERSE) };
431 if traverse_eq(traverse, current_traverse) {
432 break;
433 }
434 ty = unsafe { get_slot(ty, TP_BASE) };
435 if ty.is_null() {
436 return 0;
438 }
439 }
440
441 while traverse_eq(traverse, current_traverse) {
443 ty = unsafe { get_slot(ty, TP_BASE) };
444 if ty.is_null() {
445 break;
446 }
447 traverse = unsafe { get_slot(ty, TP_TRAVERSE) };
448 }
449
450 if let Some(traverse) = traverse {
452 return unsafe { traverse(obj, visit, arg) };
453 }
454
455 0
457}
458
459pub unsafe fn _call_clear(
461 slf: *mut ffi::PyObject,
462 impl_: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> PyResult<()>,
463 current_clear: ffi::inquiry,
464) -> c_int {
465 unsafe {
466 trampoline::trampoline(move |py| {
467 let super_retval = call_super_clear(py, slf, current_clear);
468 if super_retval != 0 {
469 return Err(PyErr::fetch(py));
470 }
471 impl_(py, slf)?;
472 Ok(0)
473 })
474 }
475}
476
477unsafe fn call_super_clear(
488 py: Python<'_>,
489 obj: *mut ffi::PyObject,
490 current_clear: ffi::inquiry,
491) -> c_int {
492 let mut ty = unsafe { PyType::from_borrowed_type_ptr(py, ffi::Py_TYPE(obj)) };
493 let mut clear: Option<ffi::inquiry>;
494
495 loop {
497 clear = ty.get_slot(TP_CLEAR);
498 if clear_eq(clear, current_clear) {
499 break;
500 }
501 let base = ty.get_slot(TP_BASE);
502 if base.is_null() {
503 return 0;
505 }
506 ty = unsafe { PyType::from_borrowed_type_ptr(py, base) };
507 }
508
509 while clear_eq(clear, current_clear) {
511 let base = ty.get_slot(TP_BASE);
512 if base.is_null() {
513 break;
514 }
515 ty = unsafe { PyType::from_borrowed_type_ptr(py, base) };
516 clear = ty.get_slot(TP_CLEAR);
517 }
518
519 if let Some(clear) = clear {
521 return unsafe { clear(obj) };
522 }
523
524 0
526}
527
528pub struct IterBaseTag;
531
532impl IterBaseTag {
533 #[inline]
534 pub fn convert<'py, Value, Target>(self, py: Python<'py>, value: Value) -> PyResult<Target>
535 where
536 Value: IntoPyCallbackOutput<'py, Target>,
537 {
538 value.convert(py)
539 }
540}
541
542pub trait IterBaseKind {
543 #[inline]
544 fn iter_tag(&self) -> IterBaseTag {
545 IterBaseTag
546 }
547}
548
549impl<Value> IterBaseKind for &Value {}
550
551pub struct IterOptionTag;
552
553impl IterOptionTag {
554 #[inline]
555 pub fn convert<'py, Value>(
556 self,
557 py: Python<'py>,
558 value: Option<Value>,
559 ) -> PyResult<*mut ffi::PyObject>
560 where
561 Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
562 {
563 match value {
564 Some(value) => value.convert(py),
565 None => Ok(null_mut()),
566 }
567 }
568}
569
570pub trait IterOptionKind {
571 #[inline]
572 fn iter_tag(&self) -> IterOptionTag {
573 IterOptionTag
574 }
575}
576
577impl<Value> IterOptionKind for Option<Value> {}
578
579pub struct IterResultOptionTag;
580
581impl IterResultOptionTag {
582 #[inline]
583 pub fn convert<'py, Value, Error>(
584 self,
585 py: Python<'py>,
586 value: Result<Option<Value>, Error>,
587 ) -> PyResult<*mut ffi::PyObject>
588 where
589 Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
590 Error: Into<PyErr>,
591 {
592 match value {
593 Ok(Some(value)) => value.convert(py),
594 Ok(None) => Ok(null_mut()),
595 Err(err) => Err(err.into()),
596 }
597 }
598}
599
600pub trait IterResultOptionKind {
601 #[inline]
602 fn iter_tag(&self) -> IterResultOptionTag {
603 IterResultOptionTag
604 }
605}
606
607impl<Value, Error> IterResultOptionKind for Result<Option<Value>, Error> {}
608
609pub struct AsyncIterBaseTag;
612
613impl AsyncIterBaseTag {
614 #[inline]
615 pub fn convert<'py, Value, Target>(self, py: Python<'py>, value: Value) -> PyResult<Target>
616 where
617 Value: IntoPyCallbackOutput<'py, Target>,
618 {
619 value.convert(py)
620 }
621}
622
623pub trait AsyncIterBaseKind {
624 #[inline]
625 fn async_iter_tag(&self) -> AsyncIterBaseTag {
626 AsyncIterBaseTag
627 }
628}
629
630impl<Value> AsyncIterBaseKind for &Value {}
631
632pub struct AsyncIterOptionTag;
633
634impl AsyncIterOptionTag {
635 #[inline]
636 pub fn convert<'py, Value>(
637 self,
638 py: Python<'py>,
639 value: Option<Value>,
640 ) -> PyResult<*mut ffi::PyObject>
641 where
642 Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
643 {
644 match value {
645 Some(value) => value.convert(py),
646 None => Err(PyStopAsyncIteration::new_err(())),
647 }
648 }
649}
650
651pub trait AsyncIterOptionKind {
652 #[inline]
653 fn async_iter_tag(&self) -> AsyncIterOptionTag {
654 AsyncIterOptionTag
655 }
656}
657
658impl<Value> AsyncIterOptionKind for Option<Value> {}
659
660pub struct AsyncIterResultOptionTag;
661
662impl AsyncIterResultOptionTag {
663 #[inline]
664 pub fn convert<'py, Value, Error>(
665 self,
666 py: Python<'py>,
667 value: Result<Option<Value>, Error>,
668 ) -> PyResult<*mut ffi::PyObject>
669 where
670 Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
671 Error: Into<PyErr>,
672 {
673 match value {
674 Ok(Some(value)) => value.convert(py),
675 Ok(None) => Err(PyStopAsyncIteration::new_err(())),
676 Err(err) => Err(err.into()),
677 }
678 }
679}
680
681pub trait AsyncIterResultOptionKind {
682 #[inline]
683 fn async_iter_tag(&self) -> AsyncIterResultOptionTag {
684 AsyncIterResultOptionTag
685 }
686}
687
688impl<Value, Error> AsyncIterResultOptionKind for Result<Option<Value>, Error> {}
689
690pub unsafe fn tp_new_impl<'py, T, const IS_PYCLASS: bool, const IS_INITIALIZER_TUPLE: bool>(
691 py: Python<'py>,
692 obj: T,
693 cls: *mut ffi::PyTypeObject,
694) -> PyResult<*mut ffi::PyObject>
695where
696 T: super::pyclass_init::PyClassInit<'py, IS_PYCLASS, IS_INITIALIZER_TUPLE>,
697{
698 unsafe {
699 obj.init(crate::Borrowed::from_ptr_unchecked(py, cls.cast()).cast_unchecked())
700 .map(Bound::into_ptr)
701 }
702}
703
704#[cfg(test)]
705mod tests {
706 #[test]
707 #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
708 fn test_fastcall_function_with_keywords() {
709 use super::PyMethodDef;
710 use crate::impl_::pyfunction::PyFunctionDef;
711 use crate::types::PyAnyMethods;
712 use crate::{ffi, Python};
713
714 Python::attach(|py| {
715 let def =
716 PyFunctionDef::from_method_def(PyMethodDef::fastcall_cfunction_with_keywords(
717 c"test",
718 accepts_no_arguments,
719 c"doc",
720 ));
721 let def = Box::leak(Box::new(def));
724
725 unsafe extern "C" fn accepts_no_arguments(
726 _slf: *mut ffi::PyObject,
727 _args: *const *mut ffi::PyObject,
728 nargs: ffi::Py_ssize_t,
729 kwargs: *mut ffi::PyObject,
730 ) -> *mut ffi::PyObject {
731 assert_eq!(nargs, 0);
732 assert!(kwargs.is_null());
733 unsafe { Python::assume_attached().None().into_ptr() }
734 }
735
736 let f = def.create_py_c_function(py, None).unwrap();
737
738 f.call0().unwrap();
739 });
740 }
741}