Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move downcast functions from PyAnyMethods to Bound<T> #3988

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording seems incorrect now that the receiver is Bound<T> instead of &PyAny or even Bound<PyAny>?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "Downcast this Python value to a subtype"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a second thought, it's not just about "downcasting" any more, as U might be a supertype of T. Would it be better to call these APIs .cast() instead of .downcast()?

(That would be a solution to avoid needing to remove the PyAnyMethods.)

I quite like .cast() though that might be a good reason to also leave this API until 0.22 and make it a more deliberate & documented addition.

///
/// 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