Skip to content

Commit

Permalink
move downcast functions from PyAnyMethods to Bound<T>
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Mar 23, 2024
1 parent 009cd32 commit 7801b7d
Show file tree
Hide file tree
Showing 28 changed files with 245 additions and 244 deletions.
4 changes: 2 additions & 2 deletions guide/src/types.md
Expand Up @@ -446,8 +446,8 @@ let _: &PyAny = cell.as_ref();
[Py]: {{#PYO3_DOCS_URL}}/pyo3/struct.Py.html
[PyAnyMethods::add]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.add
[PyAnyMethods::extract]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.extract
[PyAnyMethods::downcast]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.downcast
[PyAnyMethods::downcast_into]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.downcast_into
[PyAnyMethods::downcast]: {{#PYO3_DOCS_URL}}/pyo3/struct.Bound.html#method.downcast
[PyAnyMethods::downcast_into]: {{#PYO3_DOCS_URL}}/pyo3/struct.Bound.html#method.downcast_into
[`PyTypeCheck`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeCheck.html
[`PyAnyMethods`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html
[`PyDictMethods`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyDictMethods.html
Expand Down
1 change: 0 additions & 1 deletion src/conversion.rs
Expand Up @@ -4,7 +4,6 @@ use crate::err::{self, PyDowncastError, PyResult};
use crate::inspect::types::TypeInfo;
use crate::pyclass::boolean_struct::False;
use crate::type_object::PyTypeInfo;
use crate::types::any::PyAnyMethods;
use crate::types::PyTuple;
use crate::{
ffi, gil, Borrowed, Bound, Py, PyAny, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python,
Expand Down
8 changes: 3 additions & 5 deletions src/conversions/num_bigint.rs
Expand Up @@ -50,10 +50,8 @@
#[cfg(Py_LIMITED_API)]
use crate::types::{bytes::PyBytesMethods, PyBytes};
use crate::{
ffi,
instance::Bound,
types::{any::PyAnyMethods, PyLong},
FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject,
ffi, instance::Bound, types::PyLong, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult,
Python, ToPyObject,
};

use num_bigint::{BigInt, BigUint};
Expand Down Expand Up @@ -262,7 +260,7 @@ fn int_n_bits(long: &Bound<'_, PyLong>) -> PyResult<usize> {
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{PyDict, PyModule};
use crate::types::{PyAnyMethods, PyDict, PyModule};
use indoc::indoc;

fn rust_fib<T>() -> impl Iterator<Item = T>
Expand Down
8 changes: 3 additions & 5 deletions src/conversions/num_complex.rs
Expand Up @@ -94,10 +94,8 @@
//! assert result == [complex(1,-1), complex(-2,0)]
//! ```
use crate::{
ffi,
ffi_ptr_ext::FfiPtrExt,
types::{any::PyAnyMethods, PyComplex},
Bound, FromPyObject, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject,
ffi, ffi_ptr_ext::FfiPtrExt, types::PyComplex, Bound, FromPyObject, PyAny, PyErr, PyObject,
PyResult, Python, ToPyObject,
};
use num_complex::Complex;
use std::os::raw::c_double;
Expand Down Expand Up @@ -199,7 +197,7 @@ complex_conversion!(f64);
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{complex::PyComplexMethods, PyModule};
use crate::types::{PyAnyMethods, PyComplexMethods, PyModule};

#[test]
fn from_complex() {
Expand Down
1 change: 0 additions & 1 deletion src/conversions/std/osstr.rs
@@ -1,5 +1,4 @@
use crate::instance::Bound;
use crate::types::any::PyAnyMethods;
use crate::types::PyString;
use crate::{ffi, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject};
use std::borrow::Cow;
Expand Down
2 changes: 1 addition & 1 deletion src/conversions/std/string.rs
Expand Up @@ -4,7 +4,7 @@ use std::borrow::Cow;
use crate::inspect::types::TypeInfo;
use crate::{
instance::Bound,
types::{any::PyAnyMethods, string::PyStringMethods, PyString},
types::{string::PyStringMethods, PyString},
FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject,
};

Expand Down
2 changes: 1 addition & 1 deletion src/err/mod.rs
Expand Up @@ -119,7 +119,7 @@ impl<'py> DowncastIntoError<'py> {
/// Consumes this `DowncastIntoError` and returns the original object, allowing continued
/// use of it after a failed conversion.
///
/// See [`downcast_into`][PyAnyMethods::downcast_into] for an example.
/// See [`downcast_into`][Bound::downcast_into] for an example.
pub fn into_inner(self) -> Bound<'py, PyAny> {
self.from
}
Expand Down
2 changes: 1 addition & 1 deletion src/impl_/coroutine.rs
Expand Up @@ -8,7 +8,7 @@ use crate::{
instance::Bound,
pycell::impl_::PyClassBorrowChecker,
pyclass::boolean_struct::False,
types::{PyAnyMethods, PyString},
types::PyString,
IntoPy, Py, PyAny, PyClass, PyErr, PyObject, PyResult, Python,
};

Expand Down
2 changes: 1 addition & 1 deletion src/impl_/pymethods.rs
Expand Up @@ -5,7 +5,7 @@ use crate::impl_::panic::PanicTrap;
use crate::internal_tricks::extract_c_string;
use crate::pycell::{PyBorrowError, PyBorrowMutError};
use crate::pyclass::boolean_struct::False;
use crate::types::{any::PyAnyMethods, PyModule, PyType};
use crate::types::{PyModule, PyType};
use crate::{
ffi, Borrowed, Bound, DowncastError, Py, PyAny, PyClass, PyClassInitializer, PyErr, PyObject,
PyRef, PyRefMut, PyResult, PyTraverseError, PyTypeCheck, PyVisit, Python,
Expand Down
219 changes: 217 additions & 2 deletions src/instance.rs
Expand Up @@ -6,8 +6,8 @@ use crate::type_object::HasPyGilRef;
use crate::types::{any::PyAnyMethods, string::PyStringMethods, typeobject::PyTypeMethods};
use crate::types::{DerefToPyAny, PyDict, PyString, PyTuple};
use crate::{
ffi, AsPyPointer, DowncastError, FromPyObject, IntoPy, PyAny, PyClass, PyClassInitializer,
PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject,
ffi, AsPyPointer, DowncastError, DowncastIntoError, FromPyObject, IntoPy, PyAny, PyClass,
PyClassInitializer, PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject,
};
use crate::{gil, PyTypeCheck};
use std::marker::PhantomData;
Expand Down Expand Up @@ -96,6 +96,221 @@ where
}
}

impl<'py, T> Bound<'py, T> {
/// Downcast this `PyAny` to a concrete Python type or pyclass.
///
/// Note that you can often avoid downcasting yourself by just specifying
/// the desired type in function or method signatures.
/// However, manual downcasting is sometimes necessary.
///
/// For extracting a Rust-only type, see [`PyAny::extract`](struct.PyAny.html#method.extract).
///
/// # Example: Downcasting to a specific Python object
///
/// ```rust
/// use pyo3::prelude::*;
/// use pyo3::types::{PyDict, PyList};
///
/// Python::with_gil(|py| {
/// let dict = PyDict::new_bound(py);
/// assert!(dict.is_instance_of::<PyAny>());
/// let any = dict.as_any();
///
/// assert!(any.downcast::<PyDict>().is_ok());
/// assert!(any.downcast::<PyList>().is_err());
/// });
/// ```
///
/// # Example: Getting a reference to a pyclass
///
/// This is useful if you want to mutate a `PyObject` that
/// might actually be a pyclass.
///
/// ```rust
/// # fn main() -> Result<(), pyo3::PyErr> {
/// use pyo3::prelude::*;
///
/// #[pyclass]
/// struct Class {
/// i: i32,
/// }
///
/// Python::with_gil(|py| {
/// let class = Py::new(py, Class { i: 0 }).unwrap().into_bound(py).into_any();
///
/// let class_bound: &Bound<'_, Class> = class.downcast()?;
///
/// class_bound.borrow_mut().i += 1;
///
/// // Alternatively you can get a `PyRefMut` directly
/// let class_ref: PyRefMut<'_, Class> = class.extract()?;
/// assert_eq!(class_ref.i, 1);
/// Ok(())
/// })
/// # }
/// ```
#[inline(always)]
pub fn downcast<U>(&self) -> Result<&Bound<'py, U>, DowncastError<'_, 'py>>
where
U: PyTypeCheck,
{
#[inline]
fn inner<'a, 'py, U>(
any: &'a Bound<'py, PyAny>,
) -> Result<&'a Bound<'py, U>, DowncastError<'a, 'py>>
where
U: PyTypeCheck,
{
if U::type_check(any) {
// Safety: type_check is responsible for ensuring that the type is correct
Ok(unsafe { any.downcast_unchecked() })
} else {
Err(DowncastError::new(any, U::NAME))
}
}

inner(self.as_any())
}

/// Like `downcast` but takes ownership of `self`.
///
/// In case of an error, it is possible to retrieve `self` again via [`DowncastIntoError::into_inner`].
///
/// # Example
///
/// ```rust
/// use pyo3::prelude::*;
/// use pyo3::types::{PyDict, PyList};
///
/// Python::with_gil(|py| {
/// let obj: Bound<'_, PyAny> = PyDict::new_bound(py).into_any();
///
/// let obj: Bound<'_, PyAny> = match obj.downcast_into::<PyList>() {
/// Ok(_) => panic!("obj should not be a list"),
/// Err(err) => err.into_inner(),
/// };
///
/// // obj is a dictionary
/// assert!(obj.downcast_into::<PyDict>().is_ok());
/// })
/// ```
#[inline(always)]
pub fn downcast_into<U>(self) -> Result<Bound<'py, U>, DowncastIntoError<'py>>
where
U: PyTypeCheck,
{
#[inline]
fn inner<U>(any: Bound<'_, PyAny>) -> Result<Bound<'_, U>, DowncastIntoError<'_>>
where
U: PyTypeCheck,
{
if U::type_check(&any) {
// Safety: type_check is responsible for ensuring that the type is correct
Ok(unsafe { any.downcast_into_unchecked() })
} else {
Err(DowncastIntoError::new(any, U::NAME))
}
}

inner(self.into_any())
}

/// Downcast this `PyAny` to a concrete Python type or pyclass (but not a subclass of it).
///
/// It is almost always better to use [`PyAny::downcast`] because it accounts for Python
/// subtyping. Use this method only when you do not want to allow subtypes.
///
/// The advantage of this method over [`PyAny::downcast`] is that it is faster. The implementation
/// of `downcast_exact` uses the equivalent of the Python expression `type(self) is T`, whereas
/// `downcast` uses `isinstance(self, T)`.
///
/// For extracting a Rust-only type, see [`PyAny::extract`](struct.PyAny.html#method.extract).
///
/// # Example: Downcasting to a specific Python object but not a subtype
///
/// ```rust
/// use pyo3::prelude::*;
/// use pyo3::types::{PyBool, PyLong};
///
/// Python::with_gil(|py| {
/// let b = PyBool::new_bound(py, true);
/// assert!(b.is_instance_of::<PyBool>());
/// let any: &Bound<'_, PyAny> = b.as_any();
///
/// // `bool` is a subtype of `int`, so `downcast` will accept a `bool` as an `int`
/// // but `downcast_exact` will not.
/// assert!(any.downcast::<PyLong>().is_ok());
/// assert!(any.downcast_exact::<PyLong>().is_err());
///
/// assert!(any.downcast_exact::<PyBool>().is_ok());
/// });
/// ```
#[inline(always)]
pub fn downcast_exact<U>(&self) -> Result<&Bound<'py, U>, DowncastError<'_, 'py>>
where
U: PyTypeInfo,
{
#[inline]
fn inner<'a, 'py, U>(
any: &'a Bound<'py, PyAny>,
) -> Result<&'a Bound<'py, U>, DowncastError<'a, 'py>>
where
U: PyTypeInfo,
{
if any.is_exact_instance_of::<U>() {
// Safety: is_exact_instance_of is responsible for ensuring that the type is correct
Ok(unsafe { any.downcast_unchecked() })
} else {
Err(DowncastError::new(any, U::NAME))
}
}

inner(self.as_any())
}

/// Like `downcast_exact` but takes ownership of `self`.
#[inline(always)]
pub fn downcast_into_exact<U>(self) -> Result<Bound<'py, U>, DowncastIntoError<'py>>
where
U: PyTypeInfo,
{
#[inline]
fn inner<U>(any: Bound<'_, PyAny>) -> Result<Bound<'_, U>, DowncastIntoError<'_>>
where
U: PyTypeInfo,
{
if any.is_exact_instance_of::<U>() {
// Safety: is_exact_instance_of is responsible for ensuring that the type is correct
Ok(unsafe { any.downcast_into_unchecked() })
} else {
Err(DowncastIntoError::new(any, U::NAME))
}
}

inner(self.into_any())
}

/// Converts this `PyAny` to a concrete Python type without checking validity.
///
/// # Safety
///
/// Callers must ensure that the type is valid or risk type confusion.
#[inline(always)]
pub unsafe fn downcast_unchecked<U>(&self) -> &Bound<'py, U> {
&*(self as *const Bound<'py, T>).cast()
}

/// Like `downcast_unchecked` but takes ownership of `self`.
///
/// # Safety
///
/// Callers must ensure that the type is valid or risk type confusion.
#[inline(always)]
pub unsafe fn downcast_into_unchecked<U>(self) -> Bound<'py, U> {
std::mem::transmute(self)
}
}

impl<'py> Bound<'py, PyAny> {
/// Constructs a new `Bound<'py, PyAny>` from a pointer. Panics if `ptr` is null.
///
Expand Down
2 changes: 1 addition & 1 deletion src/py_result_ext.rs
@@ -1,4 +1,4 @@
use crate::{types::any::PyAnyMethods, Bound, PyAny, PyResult, PyTypeCheck};
use crate::{Bound, PyAny, PyResult, PyTypeCheck};

pub(crate) trait PyResultExt<'py>: crate::sealed::Sealed {
fn downcast_into<T: PyTypeCheck>(self) -> PyResult<Bound<'py, T>>;
Expand Down
6 changes: 3 additions & 3 deletions src/pybacked.rs
Expand Up @@ -4,8 +4,8 @@ use std::{ops::Deref, ptr::NonNull};

use crate::{
types::{
any::PyAnyMethods, bytearray::PyByteArrayMethods, bytes::PyBytesMethods,
string::PyStringMethods, PyByteArray, PyBytes, PyString,
bytearray::PyByteArrayMethods, bytes::PyBytesMethods, string::PyStringMethods, PyByteArray,
PyBytes, PyString,
},
Bound, DowncastError, FromPyObject, Py, PyAny, PyErr, PyResult,
};
Expand Down Expand Up @@ -119,7 +119,7 @@ impl FromPyObject<'_> for PyBackedBytes {
#[cfg(test)]
mod test {
use super::*;
use crate::Python;
use crate::{types::PyAnyMethods, Python};

#[test]
fn py_backed_str_empty() {
Expand Down
1 change: 0 additions & 1 deletion src/pycell.rs
Expand Up @@ -203,7 +203,6 @@ use crate::pyclass::{
};
use crate::pyclass_init::PyClassInitializer;
use crate::type_object::{PyLayout, PySizedLayout};
use crate::types::any::PyAnyMethods;
use crate::types::PyAny;
use crate::{ffi, Bound, IntoPy, PyErr, PyNativeType, PyObject, PyResult, PyTypeCheck, Python};
use std::fmt;
Expand Down
1 change: 0 additions & 1 deletion src/pyclass_init.rs
Expand Up @@ -2,7 +2,6 @@
use crate::callback::IntoPyCallbackOutput;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef};
use crate::types::PyAnyMethods;
use crate::{ffi, Bound, Py, PyClass, PyErr, PyResult, Python};
use crate::{
ffi::PyTypeObject,
Expand Down
1 change: 0 additions & 1 deletion src/type_object.rs
@@ -1,7 +1,6 @@
//! Python type object information

use crate::ffi_ptr_ext::FfiPtrExt;
use crate::types::any::PyAnyMethods;
use crate::types::{PyAny, PyType};
use crate::{ffi, Bound, PyNativeType, Python};

Expand Down

0 comments on commit 7801b7d

Please sign in to comment.