pyo3 0.30.1

Bindings to Python interpreter
Documentation
//! Python type object information

use crate::ffi_ptr_ext::FfiPtrExt;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::{PyStaticExpr, type_hint_identifier};
use crate::types::{PyAny, PyType};
use crate::{Bound, Python, ffi};
use core::ptr;

/// `T: PyLayout<U>` represents that `T` is a concrete representation of `U` in the Python heap.
/// E.g., `PyClassObject` is a concrete representation of all `pyclass`es, and `ffi::PyObject`
/// is of `PyAny`.
///
/// This trait is intended to be used internally.
///
/// # Safety
///
/// This trait must only be implemented for types which represent valid layouts of Python objects.
pub unsafe trait PyLayout<T> {}

/// `T: PySizedLayout<U>` represents that `T` is not a instance of
/// [`PyVarObject`](https://docs.python.org/3/c-api/structures.html#c.PyVarObject).
///
/// In addition, that `T` is a concrete representation of `U`.
pub trait PySizedLayout<T>: PyLayout<T> + Sized {}

/// Python type information.
/// All Python native types (e.g., `PyDict`) and `#[pyclass]` structs implement this trait.
///
/// This trait is marked unsafe because:
///  - specifying the incorrect layout can lead to memory errors
///  - the return value of type_object must always point to the same PyTypeObject instance
///
/// It is safely implemented by the `pyclass` macro.
///
/// # Safety
///
/// Implementations must provide an implementation for `type_object_raw` which infallibly produces a
/// non-null pointer to the corresponding Python type object.
///
/// `is_type_of` must only return true for objects which can safely be treated as instances of `Self`.
///
/// `is_exact_type_of` must only return true for objects whose type is exactly `Self`.
pub unsafe trait PyTypeInfo: Sized {
    /// Provides the full python type as a type hint.
    #[cfg(feature = "experimental-inspect")]
    const TYPE_HINT: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete");

    /// Returns the PyTypeObject instance for this type.
    fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject;

    /// Returns the safe abstraction over the type object.
    #[inline]
    fn type_object(py: Python<'_>) -> Bound<'_, PyType> {
        // Making the borrowed object `Bound` is necessary for soundness reasons. It's an extreme
        // edge case, but arbitrary Python code _could_ change the __class__ of an object and cause
        // the type object to be freed.
        //
        // By making `Bound` we assume ownership which is then safe against races.
        let tp = Self::type_object_raw(py).cast::<ffi::PyObject>();
        // SAFETY: the pointer is known to be a borrowed type object and we immediately make a new reference
        unsafe { tp.assume_borrowed_unchecked(py).cast_unchecked() }.to_owned()
    }

    /// Checks if `object` is an instance of this type or a subclass of this type.
    #[inline]
    fn is_type_of(object: &Bound<'_, PyAny>) -> bool {
        let tp = Self::type_object_raw(object.py());
        // SAFETY: pointers are known to be correct types and borrowed
        (unsafe { ffi::PyObject_TypeCheck(object.as_ptr(), tp) }) != 0
    }

    /// Checks if `object` is an instance of this type.
    #[inline]
    fn is_exact_type_of(object: &Bound<'_, PyAny>) -> bool {
        ptr::eq(
            // SAFETY: no additional requirements
            unsafe { ffi::Py_TYPE(object.as_ptr()) },
            Self::type_object_raw(object.py()),
        )
    }
}

/// Implemented by types which can be used as a concrete Python type inside `Py<T>` smart pointers.
///
/// # Safety
///
/// This trait is used to determine whether [`Bound::cast`] and similar functions can safely cast
/// to a concrete type. The implementor is responsible for ensuring that `type_check` only returns
/// true for objects which can safely be treated as Python instances of `Self`.
pub unsafe trait PyTypeCheck {
    /// Provides the full python type of the allowed values as a Python type hint.
    #[cfg(feature = "experimental-inspect")]
    const TYPE_HINT: PyStaticExpr;

    /// Checks if `object` is an instance of `Self`, which may include a subtype.
    ///
    /// This should be equivalent to the Python expression `isinstance(object, Self)`.
    fn type_check(object: &Bound<'_, PyAny>) -> bool;

    /// Returns the expected type as a possible argument for the `isinstance` and `issubclass` function.
    ///
    /// It may be a single type or a tuple of types.
    fn classinfo_object(py: Python<'_>) -> Bound<'_, PyAny>;
}

// SAFETY: requirements upheld by impl of PyTypeInfo
unsafe impl<T> PyTypeCheck for T
where
    T: PyTypeInfo,
{
    #[cfg(feature = "experimental-inspect")]
    const TYPE_HINT: PyStaticExpr = <T as PyTypeInfo>::TYPE_HINT;

    #[inline]
    fn type_check(object: &Bound<'_, PyAny>) -> bool {
        T::is_type_of(object)
    }

    #[inline]
    fn classinfo_object(py: Python<'_>) -> Bound<'_, PyAny> {
        T::type_object(py).into_any()
    }
}