From 249c0209fd41336b9e68bae14290e598fcad34ea Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 17 Nov 2022 10:42:52 +0100 Subject: [PATCH] Add {Py,PyAny}::downcast_unchecked to replace try_from_unchecked calls --- newsfragments/2734.added.md | 3 ++- src/conversions/chrono.rs | 4 +--- src/conversions/std/array.rs | 16 ++++++++-------- src/instance.rs | 15 +++++++++++++++ src/types/any.rs | 12 ++++++++++++ src/types/dict.rs | 4 ++-- src/types/iterator.rs | 2 +- src/types/list.rs | 4 ++-- src/types/mapping.rs | 2 +- src/types/sequence.rs | 12 ++++++------ src/types/tuple.rs | 4 ++-- 11 files changed, 52 insertions(+), 26 deletions(-) diff --git a/newsfragments/2734.added.md b/newsfragments/2734.added.md index 598ec263ff4..ccbd31b59fd 100644 --- a/newsfragments/2734.added.md +++ b/newsfragments/2734.added.md @@ -1 +1,2 @@ -Added `Py::downcast()` as a companion to `PyAny::downcast()`. +Added `Py::downcast()` as a companion to `PyAny::downcast()`, as well as +`downcast_unchecked()` for both types. diff --git a/src/conversions/chrono.rs b/src/conversions/chrono.rs index 85ec71b5c07..934d85fab6b 100644 --- a/src/conversions/chrono.rs +++ b/src/conversions/chrono.rs @@ -50,9 +50,7 @@ use crate::types::{ timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTzInfo, PyTzInfoAccess, PyUnicode, }; -use crate::{ - AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject, -}; +use crate::{AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject}; use chrono::offset::{FixedOffset, Utc}; use chrono::{ DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike, diff --git a/src/conversions/std/array.rs b/src/conversions/std/array.rs index fe920f04895..7a039be5c9b 100644 --- a/src/conversions/std/array.rs +++ b/src/conversions/std/array.rs @@ -6,8 +6,8 @@ mod min_const_generics { use crate::conversion::{AsPyPointer, IntoPyPointer}; use crate::types::PySequence; use crate::{ - ffi, FromPyObject, IntoPy, Py, PyAny, PyDowncastError, PyObject, PyResult, PyTryFrom, - Python, ToPyObject, + ffi, FromPyObject, IntoPy, Py, PyAny, PyDowncastError, PyObject, PyResult, Python, + ToPyObject, }; impl IntoPy for [T; N] @@ -65,9 +65,9 @@ mod min_const_generics { { // Types that pass `PySequence_Check` usually implement enough of the sequence protocol // to support this function and if not, we will only fail extraction safely. - let seq = unsafe { + let seq: &PySequence = unsafe { if ffi::PySequence_Check(obj.as_ptr()) != 0 { - ::try_from_unchecked(obj) + obj.downcast_unchecked() } else { return Err(PyDowncastError::new(obj, "Sequence").into()); } @@ -187,8 +187,8 @@ mod array_impls { use crate::conversion::{AsPyPointer, IntoPyPointer}; use crate::types::PySequence; use crate::{ - ffi, FromPyObject, IntoPy, Py, PyAny, PyDowncastError, PyObject, PyResult, PyTryFrom, - Python, ToPyObject, + ffi, FromPyObject, IntoPy, Py, PyAny, PyDowncastError, PyObject, PyResult, Python, + ToPyObject, }; use std::mem::{transmute_copy, ManuallyDrop}; @@ -288,9 +288,9 @@ mod array_impls { { // Types that pass `PySequence_Check` usually implement enough of the sequence protocol // to support this function and if not, we will only fail extraction safely. - let seq = unsafe { + let seq: &PySequence = unsafe { if ffi::PySequence_Check(obj.as_ptr()) != 0 { - ::try_from_unchecked(obj) + obj.downcast_unchecked() } else { return Err(PyDowncastError::new(obj, "Sequence").into()); } diff --git a/src/instance.rs b/src/instance.rs index a09c6f93d51..7413d354de4 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1002,6 +1002,21 @@ impl PyObject { >::try_from(self.as_ref(py)) } + /// Casts the PyObject to a concrete Python object type without checking validity. + /// + /// This can cast only to native Python types, not types implemented in Rust. For a more + /// flexible alternative, see [`Py::extract`](struct.Py.html#method.extract). + /// + /// # Safety + /// + /// Callers must ensure that the type is valid or risk type confusion. + pub unsafe fn downcast_unchecked<'p, T>(&'p self, py: Python<'p>) -> &T + where + T: PyTryFrom<'p>, + { + >::try_from_unchecked(self.as_ref(py)) + } + /// Casts the PyObject to a concrete Python object type. #[deprecated(since = "0.18.0", note = "use downcast() instead")] pub fn cast_as<'p, D>(&'p self, py: Python<'p>) -> Result<&'p D, PyDowncastError<'_>> diff --git a/src/types/any.rs b/src/types/any.rs index 9be55985f3f..235f7cc187c 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -780,6 +780,18 @@ impl PyAny { ::try_from(self) } + /// Converts this `PyAny` to a concrete Python type without checking validity. + /// + /// # Safety + /// + /// Callers must ensure that the type is valid or risk type confusion. + pub unsafe fn downcast_unchecked<'p, T>(&'p self) -> &'p T + where + T: PyTryFrom<'p>, + { + ::try_from_unchecked(self) + } + /// Extracts some type from the Python object. /// /// This is a wrapper function around [`FromPyObject::extract()`]. diff --git a/src/types/dict.rs b/src/types/dict.rs index a58b1c3c5dd..b0e9dd4fd80 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -4,7 +4,7 @@ use super::PyMapping; use crate::err::{self, PyErr, PyResult}; use crate::ffi::Py_ssize_t; use crate::types::{PyAny, PyList}; -use crate::{ffi, AsPyPointer, PyTryFrom, Python, ToPyObject}; +use crate::{ffi, AsPyPointer, Python, ToPyObject}; #[cfg(not(PyPy))] use crate::{IntoPyPointer, PyObject}; use std::ptr::NonNull; @@ -255,7 +255,7 @@ impl PyDict { /// Returns `self` cast as a `PyMapping`. pub fn as_mapping(&self) -> &PyMapping { - unsafe { PyMapping::try_from_unchecked(self) } + unsafe { self.downcast_unchecked() } } } diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 56ac9fd9c4c..5b51c0671a1 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -70,7 +70,7 @@ impl<'v> PyTryFrom<'v> for PyIterator { let value = value.into(); unsafe { if ffi::PyIter_Check(value.as_ptr()) != 0 { - Ok(::try_from_unchecked(value)) + Ok(value.downcast_unchecked()) } else { Err(PyDowncastError::new(value, "Iterator")) } diff --git a/src/types/list.rs b/src/types/list.rs index daf4629c16a..d81e8153947 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -8,7 +8,7 @@ use crate::err::{self, PyResult}; use crate::ffi::{self, Py_ssize_t}; use crate::internal_tricks::get_ssize_index; use crate::types::PySequence; -use crate::{AsPyPointer, IntoPyPointer, Py, PyAny, PyObject, PyTryFrom, Python, ToPyObject}; +use crate::{AsPyPointer, IntoPyPointer, Py, PyAny, PyObject, Python, ToPyObject}; /// Represents a Python `list`. #[repr(transparent)] @@ -115,7 +115,7 @@ impl PyList { /// Returns `self` cast as a `PySequence`. pub fn as_sequence(&self) -> &PySequence { - unsafe { PySequence::try_from_unchecked(self) } + unsafe { self.downcast_unchecked() } } /// Gets the list item at the specified index. diff --git a/src/types/mapping.rs b/src/types/mapping.rs index edb1effabbb..ab478ba72af 100644 --- a/src/types/mapping.rs +++ b/src/types/mapping.rs @@ -142,7 +142,7 @@ impl<'v> PyTryFrom<'v> for PyMapping { // TODO: surface specific errors in this chain to the user if let Ok(abc) = get_mapping_abc(value.py()) { if value.is_instance(abc).unwrap_or(false) { - unsafe { return Ok(::try_from_unchecked(value)) } + unsafe { return Ok(value.downcast_unchecked()) } } } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 102d663fbea..204770e9ec4 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -301,9 +301,9 @@ where { // Types that pass `PySequence_Check` usually implement enough of the sequence protocol // to support this function and if not, we will only fail extraction safely. - let seq = unsafe { + let seq: &PySequence = unsafe { if ffi::PySequence_Check(obj.as_ptr()) != 0 { - ::try_from_unchecked(obj) + obj.downcast_unchecked() } else { return Err(PyDowncastError::new(obj, "Sequence").into()); } @@ -339,7 +339,7 @@ impl<'v> PyTryFrom<'v> for PySequence { // TODO: surface specific errors in this chain to the user if let Ok(abc) = get_sequence_abc(value.py()) { if value.is_instance(abc).unwrap_or(false) { - unsafe { return Ok(::try_from_unchecked(value)) } + unsafe { return Ok(value.downcast_unchecked::()) } } } @@ -386,7 +386,7 @@ impl Py { #[cfg(test)] mod tests { use crate::types::{PyList, PySequence}; - use crate::{AsPyPointer, Py, PyObject, PyTryFrom, Python, ToPyObject}; + use crate::{AsPyPointer, Py, PyObject, Python, ToPyObject}; fn get_object() -> PyObject { // Convenience function for getting a single unique object @@ -867,13 +867,13 @@ mod tests { } #[test] - fn test_seq_try_from_unchecked() { + fn test_seq_downcast_unchecked() { Python::with_gil(|py| { let v = vec!["foo", "bar"]; let ob = v.to_object(py); let seq = ob.downcast::(py).unwrap(); let type_ptr = seq.as_ref(); - let seq_from = unsafe { ::try_from_unchecked(type_ptr) }; + let seq_from = unsafe { type_ptr.downcast_unchecked::() }; assert!(seq_from.list().is_ok()); }); } diff --git a/src/types/tuple.rs b/src/types/tuple.rs index d3e0dab3514..5362d7fc5cd 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -8,7 +8,7 @@ use crate::internal_tricks::get_ssize_index; use crate::types::PySequence; use crate::{ exceptions, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyAny, PyErr, PyObject, - PyResult, PyTryFrom, Python, ToPyObject, + PyResult, Python, ToPyObject, }; #[inline] @@ -120,7 +120,7 @@ impl PyTuple { /// Returns `self` cast as a `PySequence`. pub fn as_sequence(&self) -> &PySequence { - unsafe { PySequence::try_from_unchecked(self) } + unsafe { self.downcast_unchecked() } } /// Takes the slice `self[low:high]` and returns it as a new tuple.