| 1 | //! Python type object information |
| 2 | |
| 3 | use crate::ffi_ptr_ext::FfiPtrExt; |
| 4 | use crate::types::any::PyAnyMethods; |
| 5 | use crate::types::{PyAny, PyType}; |
| 6 | use crate::{ffi, Bound, Python}; |
| 7 | use std::ptr; |
| 8 | |
| 9 | /// `T: PyLayout<U>` represents that `T` is a concrete representation of `U` in the Python heap. |
| 10 | /// E.g., `PyClassObject` is a concrete representation of all `pyclass`es, and `ffi::PyObject` |
| 11 | /// is of `PyAny`. |
| 12 | /// |
| 13 | /// This trait is intended to be used internally. |
| 14 | /// |
| 15 | /// # Safety |
| 16 | /// |
| 17 | /// This trait must only be implemented for types which represent valid layouts of Python objects. |
| 18 | pub unsafe trait PyLayout<T> {} |
| 19 | |
| 20 | /// `T: PySizedLayout<U>` represents that `T` is not a instance of |
| 21 | /// [`PyVarObject`](https://docs.python.org/3/c-api/structures.html#c.PyVarObject). |
| 22 | /// |
| 23 | /// In addition, that `T` is a concrete representation of `U`. |
| 24 | pub trait PySizedLayout<T>: PyLayout<T> + Sized {} |
| 25 | |
| 26 | /// Python type information. |
| 27 | /// All Python native types (e.g., `PyDict`) and `#[pyclass]` structs implement this trait. |
| 28 | /// |
| 29 | /// This trait is marked unsafe because: |
| 30 | /// - specifying the incorrect layout can lead to memory errors |
| 31 | /// - the return value of type_object must always point to the same PyTypeObject instance |
| 32 | /// |
| 33 | /// It is safely implemented by the `pyclass` macro. |
| 34 | /// |
| 35 | /// # Safety |
| 36 | /// |
| 37 | /// Implementations must provide an implementation for `type_object_raw` which infallibly produces a |
| 38 | /// non-null pointer to the corresponding Python type object. |
| 39 | pub unsafe trait PyTypeInfo: Sized { |
| 40 | /// Class name. |
| 41 | const NAME: &'static str; |
| 42 | |
| 43 | /// Module name, if any. |
| 44 | const MODULE: Option<&'static str>; |
| 45 | |
| 46 | /// Returns the PyTypeObject instance for this type. |
| 47 | fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject; |
| 48 | |
| 49 | /// Returns the safe abstraction over the type object. |
| 50 | #[inline ] |
| 51 | fn type_object(py: Python<'_>) -> Bound<'_, PyType> { |
| 52 | // Making the borrowed object `Bound` is necessary for soundness reasons. It's an extreme |
| 53 | // edge case, but arbitrary Python code _could_ change the __class__ of an object and cause |
| 54 | // the type object to be freed. |
| 55 | // |
| 56 | // By making `Bound` we assume ownership which is then safe against races. |
| 57 | unsafe { |
| 58 | Self::type_object_raw(py) |
| 59 | .cast::<ffi::PyObject>() |
| 60 | .assume_borrowed_unchecked(py) |
| 61 | .to_owned() |
| 62 | .downcast_into_unchecked() |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | /// Deprecated name for [`PyTypeInfo::type_object`]. |
| 67 | #[deprecated (since = "0.23.0" , note = "renamed to `PyTypeInfo::type_object`" )] |
| 68 | #[inline ] |
| 69 | fn type_object_bound(py: Python<'_>) -> Bound<'_, PyType> { |
| 70 | Self::type_object(py) |
| 71 | } |
| 72 | |
| 73 | /// Checks if `object` is an instance of this type or a subclass of this type. |
| 74 | #[inline ] |
| 75 | fn is_type_of(object: &Bound<'_, PyAny>) -> bool { |
| 76 | unsafe { ffi::PyObject_TypeCheck(object.as_ptr(), Self::type_object_raw(object.py())) != 0 } |
| 77 | } |
| 78 | |
| 79 | /// Deprecated name for [`PyTypeInfo::is_type_of`]. |
| 80 | #[deprecated (since = "0.23.0" , note = "renamed to `PyTypeInfo::is_type_of`" )] |
| 81 | #[inline ] |
| 82 | fn is_type_of_bound(object: &Bound<'_, PyAny>) -> bool { |
| 83 | Self::is_type_of(object) |
| 84 | } |
| 85 | |
| 86 | /// Checks if `object` is an instance of this type. |
| 87 | #[inline ] |
| 88 | fn is_exact_type_of(object: &Bound<'_, PyAny>) -> bool { |
| 89 | unsafe { |
| 90 | ptr::eq( |
| 91 | ffi::Py_TYPE(object.as_ptr()), |
| 92 | Self::type_object_raw(object.py()), |
| 93 | ) |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | /// Deprecated name for [`PyTypeInfo::is_exact_type_of`]. |
| 98 | #[deprecated (since = "0.23.0" , note = "renamed to `PyTypeInfo::is_exact_type_of`" )] |
| 99 | #[inline ] |
| 100 | fn is_exact_type_of_bound(object: &Bound<'_, PyAny>) -> bool { |
| 101 | Self::is_exact_type_of(object) |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | /// Implemented by types which can be used as a concrete Python type inside `Py<T>` smart pointers. |
| 106 | pub trait PyTypeCheck { |
| 107 | /// Name of self. This is used in error messages, for example. |
| 108 | const NAME: &'static str; |
| 109 | |
| 110 | /// Checks if `object` is an instance of `Self`, which may include a subtype. |
| 111 | /// |
| 112 | /// This should be equivalent to the Python expression `isinstance(object, Self)`. |
| 113 | fn type_check(object: &Bound<'_, PyAny>) -> bool; |
| 114 | } |
| 115 | |
| 116 | impl<T> PyTypeCheck for T |
| 117 | where |
| 118 | T: PyTypeInfo, |
| 119 | { |
| 120 | const NAME: &'static str = <T as PyTypeInfo>::NAME; |
| 121 | |
| 122 | #[inline ] |
| 123 | fn type_check(object: &Bound<'_, PyAny>) -> bool { |
| 124 | T::is_type_of(object) |
| 125 | } |
| 126 | } |
| 127 | |