diff --git a/src/instance.rs b/src/instance.rs index 3df28261c4b..961a7886406 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -14,6 +14,9 @@ use std::mem::{self, ManuallyDrop}; use std::ops::Deref; use std::ptr::NonNull; +pub(crate) mod ffi_call; +pub(crate) use ffi_call::FromFfiCallResult; + /// Types that are built into the Python interpreter. /// /// PyO3 is designed in a way that all references to those types are bound @@ -64,6 +67,18 @@ impl<'py> Py2<'py, PyAny> { ) -> PyResult { Py::from_owned_ptr_or_err(py, ptr).map(|obj| Self(py, ManuallyDrop::new(obj))) } + + /// Internal helper to make an ffi call which produces an owned reference or + /// NULL on error, doing an unchecked downcast to the result type T. + pub(crate) unsafe fn do_ffi_call( + &self, + call: impl FnOnce(*mut ffi::PyObject) -> R, + ) -> PyResult + where + T: FromFfiCallResult<'py, R>, + { + T::from_ffi_call_result(self.py(), call(self.as_ptr())) + } } impl<'py, T> Py2<'py, T> { diff --git a/src/instance/ffi_call.rs b/src/instance/ffi_call.rs new file mode 100644 index 00000000000..162796505f1 --- /dev/null +++ b/src/instance/ffi_call.rs @@ -0,0 +1,59 @@ +use std::os::raw::c_int; + +use crate::err::{error_on_minusone, SignedInteger}; +use crate::types::any::PyAnyMethods; +use crate::{ffi, Py2, PyErr, PyResult, Python}; + +/// Internal helper to convert raw ffi call results such as pointers +/// or integers into safe wrappers. +/// +/// `unsafe` to implement because it is highly likely this trait is +/// passed a pointer, and is free to do interpret it as it sees fit. +pub(crate) unsafe trait FromFfiCallResult<'py, RawResult>: Sized { + fn from_ffi_call_result(py: Python<'py>, raw: RawResult) -> PyResult; +} + +/// For Py2, perform an unchecked downcast to the target type T. +unsafe impl<'py, T> FromFfiCallResult<'py, *mut ffi::PyObject> for Py2<'py, T> { + fn from_ffi_call_result(py: Python<'py>, raw: *mut ffi::PyObject) -> PyResult { + unsafe { Py2::from_owned_ptr_or_err(py, raw).map(|any| any.downcast_into_unchecked()) } + } +} + +unsafe impl<'py, T> FromFfiCallResult<'py, T> for () +where + T: SignedInteger, +{ + fn from_ffi_call_result(py: Python<'py>, raw: T) -> PyResult { + error_on_minusone(py, raw) + } +} + +unsafe impl<'py, T> FromFfiCallResult<'py, T> for T +where + T: SignedInteger, +{ + fn from_ffi_call_result(py: Python<'py>, raw: T) -> PyResult { + if raw != T::MINUS_ONE { + Ok(raw) + } else { + Err(PyErr::fetch(py)) + } + } +} + +unsafe impl<'py> FromFfiCallResult<'py, c_int> for bool { + fn from_ffi_call_result(py: Python<'py>, raw: c_int) -> PyResult { + match raw { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(PyErr::fetch(py)), + } + } +} + +/// Convert an isize which is known to be positive to a usize. +#[inline] +pub(crate) fn positive_isize_as_usize(x: isize) -> usize { + x as usize +} diff --git a/src/prelude.rs b/src/prelude.rs index fc0199e59e3..2daf36fb70d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -25,3 +25,4 @@ pub use crate::wrap_pyfunction; // Expected to become public API in 0.21 // pub(crate) use crate::instance::Py2; // Will be stabilized with a different name // pub(crate) use crate::types::any::PyAnyMethods; +// pub(crate) use crate::types::sequence::PySequenceMethods; diff --git a/src/types/mod.rs b/src/types/mod.rs index c6bc1972991..799d04e9562 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -297,7 +297,7 @@ mod notimplemented; mod num; #[cfg(not(PyPy))] mod pysuper; -mod sequence; +pub(crate) mod sequence; pub(crate) mod set; mod slice; mod string; diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 4772d7f1363..ee9a0f0b23b 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -1,14 +1,13 @@ -use crate::err::{self, PyDowncastError, PyErr, PyResult}; +use crate::err::{PyDowncastError, PyResult}; use crate::exceptions::PyTypeError; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; +use crate::instance::ffi_call::positive_isize_as_usize; use crate::internal_tricks::get_ssize_index; use crate::sync::GILOnceCell; use crate::type_object::PyTypeInfo; use crate::types::{PyAny, PyList, PyString, PyTuple, PyType}; -use crate::{ffi, PyNativeType, PyObject, ToPyObject}; -use crate::{FromPyObject, PyTryFrom}; -use crate::{Py, Python}; +use crate::{ffi, FromPyObject, Py, Py2, PyNativeType, PyTryFrom, Python, ToPyObject}; /// Represents a reference to a Python object supporting the sequence protocol. #[repr(transparent)] @@ -22,15 +21,13 @@ impl PySequence { /// This is equivalent to the Python expression `len(self)`. #[inline] pub fn len(&self) -> PyResult { - let v = unsafe { ffi::PySequence_Size(self.as_ptr()) }; - crate::err::error_on_minusone(self.py(), v)?; - Ok(v as usize) + Py2::borrowed_from_gil_ref_sequence(&self).len() } /// Returns whether the sequence is empty. #[inline] pub fn is_empty(&self) -> PyResult { - self.len().map(|l| l == 0) + Py2::borrowed_from_gil_ref_sequence(&self).is_empty() } /// Returns the concatenation of `self` and `other`. @@ -38,10 +35,9 @@ impl PySequence { /// This is equivalent to the Python expression `self + other`. #[inline] pub fn concat(&self, other: &PySequence) -> PyResult<&PySequence> { - unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PySequence_Concat(self.as_ptr(), other.as_ptr())) - } + Py2::borrowed_from_gil_ref_sequence(&self) + .concat(Py2::borrowed_from_gil_ref_sequence(&other)) + .map(|py2| py2.into_gil_ref_sequence()) } /// Returns the result of repeating a sequence object `count` times. @@ -49,12 +45,9 @@ impl PySequence { /// This is equivalent to the Python expression `self * count`. #[inline] pub fn repeat(&self, count: usize) -> PyResult<&PySequence> { - unsafe { - self.py().from_owned_ptr_or_err(ffi::PySequence_Repeat( - self.as_ptr(), - get_ssize_index(count), - )) - } + Py2::borrowed_from_gil_ref_sequence(&self) + .repeat(count) + .map(|py2| py2.into_gil_ref_sequence()) } /// Concatenates `self` and `other`, in place if possible. @@ -66,10 +59,9 @@ impl PySequence { /// possible, but create and return a new object if not. #[inline] pub fn in_place_concat(&self, other: &PySequence) -> PyResult<&PySequence> { - unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PySequence_InPlaceConcat(self.as_ptr(), other.as_ptr())) - } + Py2::borrowed_from_gil_ref_sequence(&self) + .in_place_concat(Py2::borrowed_from_gil_ref_sequence(&other)) + .map(|py2| py2.into_gil_ref_sequence()) } /// Repeats the sequence object `count` times and updates `self`, if possible. @@ -81,13 +73,9 @@ impl PySequence { /// possible, but create and return a new object if not. #[inline] pub fn in_place_repeat(&self, count: usize) -> PyResult<&PySequence> { - unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PySequence_InPlaceRepeat( - self.as_ptr(), - get_ssize_index(count), - )) - } + Py2::borrowed_from_gil_ref_sequence(&self) + .in_place_repeat(count) + .map(|py2| py2.into_gil_ref_sequence()) } /// Returns the `index`th element of the Sequence. @@ -95,12 +83,9 @@ impl PySequence { /// This is equivalent to the Python expression `self[index]` without support of negative indices. #[inline] pub fn get_item(&self, index: usize) -> PyResult<&PyAny> { - unsafe { - self.py().from_owned_ptr_or_err(ffi::PySequence_GetItem( - self.as_ptr(), - get_ssize_index(index), - )) - } + Py2::borrowed_from_gil_ref_sequence(&self) + .get_item(index) + .map(|py2| py2.into_gil_ref()) } /// Returns the slice of sequence object between `begin` and `end`. @@ -108,13 +93,9 @@ impl PySequence { /// This is equivalent to the Python expression `self[begin:end]`. #[inline] pub fn get_slice(&self, begin: usize, end: usize) -> PyResult<&PySequence> { - unsafe { - self.py().from_owned_ptr_or_err(ffi::PySequence_GetSlice( - self.as_ptr(), - get_ssize_index(begin), - get_ssize_index(end), - )) - } + Py2::borrowed_from_gil_ref_sequence(&self) + .get_slice(begin, end) + .map(|py2| py2.into_gil_ref_sequence()) } /// Assigns object `item` to the `i`th element of self. @@ -125,13 +106,7 @@ impl PySequence { where I: ToPyObject, { - fn inner(seq: &PySequence, i: usize, item: PyObject) -> PyResult<()> { - err::error_on_minusone(seq.py(), unsafe { - ffi::PySequence_SetItem(seq.as_ptr(), get_ssize_index(i), item.as_ptr()) - }) - } - - inner(self, i, item.to_object(self.py())) + Py2::borrowed_from_gil_ref_sequence(&self).set_item(i, item) } /// Deletes the `i`th element of self. @@ -139,9 +114,7 @@ impl PySequence { /// This is equivalent to the Python statement `del self[i]`. #[inline] pub fn del_item(&self, i: usize) -> PyResult<()> { - err::error_on_minusone(self.py(), unsafe { - ffi::PySequence_DelItem(self.as_ptr(), get_ssize_index(i)) - }) + Py2::borrowed_from_gil_ref_sequence(&self).del_item(i) } /// Assigns the sequence `v` to the slice of `self` from `i1` to `i2`. @@ -149,14 +122,7 @@ impl PySequence { /// This is equivalent to the Python statement `self[i1:i2] = v`. #[inline] pub fn set_slice(&self, i1: usize, i2: usize, v: &PyAny) -> PyResult<()> { - err::error_on_minusone(self.py(), unsafe { - ffi::PySequence_SetSlice( - self.as_ptr(), - get_ssize_index(i1), - get_ssize_index(i2), - v.as_ptr(), - ) - }) + Py2::borrowed_from_gil_ref_sequence(&self).set_slice(i1, i2, Py2::borrowed_from_gil_ref(&v)) } /// Deletes the slice from `i1` to `i2` from `self`. @@ -164,9 +130,7 @@ impl PySequence { /// This is equivalent to the Python statement `del self[i1:i2]`. #[inline] pub fn del_slice(&self, i1: usize, i2: usize) -> PyResult<()> { - err::error_on_minusone(self.py(), unsafe { - ffi::PySequence_DelSlice(self.as_ptr(), get_ssize_index(i1), get_ssize_index(i2)) - }) + Py2::borrowed_from_gil_ref_sequence(&self).del_slice(i1, i2) } /// Returns the number of occurrences of `value` in self, that is, return the @@ -177,13 +141,7 @@ impl PySequence { where V: ToPyObject, { - fn inner(seq: &PySequence, value: PyObject) -> PyResult { - let r = unsafe { ffi::PySequence_Count(seq.as_ptr(), value.as_ptr()) }; - crate::err::error_on_minusone(seq.py(), r)?; - Ok(r as usize) - } - - inner(self, value.to_object(self.py())) + Py2::borrowed_from_gil_ref_sequence(&self).count(value) } /// Determines if self contains `value`. @@ -194,16 +152,7 @@ impl PySequence { where V: ToPyObject, { - fn inner(seq: &PySequence, value: PyObject) -> PyResult { - let r = unsafe { ffi::PySequence_Contains(seq.as_ptr(), value.as_ptr()) }; - match r { - 0 => Ok(false), - 1 => Ok(true), - _ => Err(PyErr::fetch(seq.py())), - } - } - - inner(self, value.to_object(self.py())) + Py2::borrowed_from_gil_ref_sequence(&self).contains(value) } /// Returns the first index `i` for which `self[i] == value`. @@ -214,22 +163,15 @@ impl PySequence { where V: ToPyObject, { - fn inner(seq: &PySequence, value: PyObject) -> PyResult { - let r = unsafe { ffi::PySequence_Index(seq.as_ptr(), value.as_ptr()) }; - crate::err::error_on_minusone(seq.py(), r)?; - Ok(r as usize) - } - - inner(self, value.to_object(self.py())) + Py2::borrowed_from_gil_ref_sequence(&self).index(value) } /// Returns a fresh list based on the Sequence. #[inline] pub fn to_list(&self) -> PyResult<&PyList> { - unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PySequence_List(self.as_ptr())) - } + Py2::borrowed_from_gil_ref_sequence(&self) + .to_list() + .map(|py2| py2.into_gil_ref()) } /// Returns a fresh list based on the Sequence. @@ -242,10 +184,9 @@ impl PySequence { /// Returns a fresh tuple based on the Sequence. #[inline] pub fn to_tuple(&self) -> PyResult<&PyTuple> { - unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PySequence_Tuple(self.as_ptr())) - } + Py2::borrowed_from_gil_ref_sequence(&self) + .to_tuple() + .map(|py2| py2.into_gil_ref()) } /// Returns a fresh tuple based on the Sequence. @@ -265,6 +206,270 @@ impl PySequence { } } +/// Helpers to get around the fact that PySequence does not implement PyTypeInfo +impl<'py> Py2<'py, PySequence> { + pub(crate) fn borrowed_from_gil_ref_sequence<'a>( + gil_ref: &'a &'py PySequence, + ) -> &'a Py2<'py, PySequence> { + // safety: Py2 is the same layout as &PySequence + unsafe { &*(gil_ref as *const &'py PySequence as *const Py2<'py, PySequence>) } + } + + pub(crate) fn into_gil_ref_sequence(self: Py2<'py, PySequence>) -> &'py PySequence { + unsafe { self.py().from_owned_ptr(self.into_ptr()) } + } +} + +/// Implementation of functionality for [`PySequence`]. +/// +/// These methods are defined for the `Py2<'py, PySequence>` smart pointer, so to use method call +/// syntax these methods are separated into a trait, because stable Rust does not yet support +/// `arbitrary_self_types`. +#[doc(alias = "PySequence")] +pub(crate) trait PySequenceMethods<'py> { + /// Returns the number of objects in sequence. + /// + /// This is equivalent to the Python expression `len(self)`. + fn len(&self) -> PyResult; + + /// Returns whether the sequence is empty. + fn is_empty(&self) -> PyResult; + + /// Returns the concatenation of `self` and `other`. + /// + /// This is equivalent to the Python expression `self + other`. + fn concat(&self, other: &Py2<'_, PySequence>) -> PyResult>; + + /// Returns the result of repeating a sequence object `count` times. + /// + /// This is equivalent to the Python expression `self * count`. + fn repeat(&self, count: usize) -> PyResult>; + + /// Concatenates `self` and `other`, in place if possible. + /// + /// This is equivalent to the Python expression `self.__iadd__(other)`. + /// + /// The Python statement `self += other` is syntactic sugar for `self = + /// self.__iadd__(other)`. `__iadd__` should modify and return `self` if + /// possible, but create and return a new object if not. + fn in_place_concat(&self, other: &Py2<'_, PySequence>) -> PyResult>; + + /// Repeats the sequence object `count` times and updates `self`, if possible. + /// + /// This is equivalent to the Python expression `self.__imul__(other)`. + /// + /// The Python statement `self *= other` is syntactic sugar for `self = + /// self.__imul__(other)`. `__imul__` should modify and return `self` if + /// possible, but create and return a new object if not. + fn in_place_repeat(&self, count: usize) -> PyResult>; + + /// Returns the `index`th element of the Sequence. + /// + /// This is equivalent to the Python expression `self[index]` without support of negative indices. + fn get_item(&self, index: usize) -> PyResult>; + + /// Returns the slice of sequence object between `begin` and `end`. + /// + /// This is equivalent to the Python expression `self[begin:end]`. + fn get_slice(&self, begin: usize, end: usize) -> PyResult>; + + /// Assigns object `item` to the `i`th element of self. + /// + /// This is equivalent to the Python statement `self[i] = v`. + fn set_item(&self, i: usize, item: I) -> PyResult<()> + where + I: ToPyObject; + + /// Deletes the `i`th element of self. + /// + /// This is equivalent to the Python statement `del self[i]`. + fn del_item(&self, i: usize) -> PyResult<()>; + + /// Assigns the sequence `v` to the slice of `self` from `i1` to `i2`. + /// + /// This is equivalent to the Python statement `self[i1:i2] = v`. + fn set_slice(&self, i1: usize, i2: usize, v: &Py2<'_, PyAny>) -> PyResult<()>; + + /// Deletes the slice from `i1` to `i2` from `self`. + /// + /// This is equivalent to the Python statement `del self[i1:i2]`. + fn del_slice(&self, i1: usize, i2: usize) -> PyResult<()>; + + /// Returns the number of occurrences of `value` in self, that is, return the + /// number of keys for which `self[key] == value`. + #[cfg(not(PyPy))] + fn count(&self, value: V) -> PyResult + where + V: ToPyObject; + + /// Determines if self contains `value`. + /// + /// This is equivalent to the Python expression `value in self`. + fn contains(&self, value: V) -> PyResult + where + V: ToPyObject; + + /// Returns the first index `i` for which `self[i] == value`. + /// + /// This is equivalent to the Python expression `self.index(value)`. + fn index(&self, value: V) -> PyResult + where + V: ToPyObject; + + /// Returns a fresh list based on the Sequence. + fn to_list(&self) -> PyResult>; + + /// Returns a fresh tuple based on the Sequence. + fn to_tuple(&self) -> PyResult>; +} + +impl<'py> PySequenceMethods<'py> for Py2<'py, PySequence> { + #[inline] + fn len(&self) -> PyResult { + unsafe { + self.do_ffi_call(|ptr| ffi::PySequence_Length(ptr)) + .map(positive_isize_as_usize) + } + } + + #[inline] + fn is_empty(&self) -> PyResult { + self.len().map(|l| l == 0) + } + + #[inline] + fn concat(&self, other: &Py2<'_, PySequence>) -> PyResult> { + unsafe { self.do_ffi_call(|ptr| ffi::PySequence_Concat(ptr, other.as_ptr())) } + } + + #[inline] + fn repeat(&self, count: usize) -> PyResult> { + unsafe { self.do_ffi_call(|ptr| ffi::PySequence_Repeat(ptr, get_ssize_index(count))) } + } + + #[inline] + fn in_place_concat(&self, other: &Py2<'_, PySequence>) -> PyResult> { + unsafe { self.do_ffi_call(|ptr| ffi::PySequence_InPlaceConcat(ptr, other.as_ptr())) } + } + + #[inline] + fn in_place_repeat(&self, count: usize) -> PyResult> { + unsafe { + self.do_ffi_call(|ptr| ffi::PySequence_InPlaceRepeat(ptr, get_ssize_index(count))) + } + } + + #[inline] + fn get_item(&self, index: usize) -> PyResult> { + unsafe { self.do_ffi_call(|ptr| ffi::PySequence_GetItem(ptr, get_ssize_index(index))) } + } + + #[inline] + fn get_slice(&self, begin: usize, end: usize) -> PyResult> { + unsafe { + self.do_ffi_call(|ptr| { + ffi::PySequence_GetSlice(ptr, get_ssize_index(begin), get_ssize_index(end)) + }) + } + } + + #[inline] + fn set_item(&self, i: usize, item: I) -> PyResult<()> + where + I: ToPyObject, + { + fn inner(seq: &Py2<'_, PySequence>, i: usize, item: Py2<'_, PyAny>) -> PyResult<()> { + unsafe { + seq.do_ffi_call(|ptr| { + ffi::PySequence_SetItem(ptr, get_ssize_index(i), item.as_ptr()) + }) + } + } + + let py = self.py(); + inner(self, i, item.to_object(py).attach_into(py)) + } + + #[inline] + fn del_item(&self, i: usize) -> PyResult<()> { + unsafe { self.do_ffi_call(|ptr| ffi::PySequence_DelItem(ptr, get_ssize_index(i))) } + } + + #[inline] + fn set_slice(&self, i1: usize, i2: usize, v: &Py2<'_, PyAny>) -> PyResult<()> { + unsafe { + self.do_ffi_call(|ptr| { + ffi::PySequence_SetSlice(ptr, get_ssize_index(i1), get_ssize_index(i2), v.as_ptr()) + }) + } + } + + #[inline] + fn del_slice(&self, i1: usize, i2: usize) -> PyResult<()> { + unsafe { + self.do_ffi_call(|ptr| { + ffi::PySequence_DelSlice(ptr, get_ssize_index(i1), get_ssize_index(i2)) + }) + } + } + + #[inline] + #[cfg(not(PyPy))] + fn count(&self, value: V) -> PyResult + where + V: ToPyObject, + { + fn inner(seq: &Py2<'_, PySequence>, value: Py2<'_, PyAny>) -> PyResult { + unsafe { + seq.do_ffi_call(|ptr| ffi::PySequence_Count(ptr, value.as_ptr())) + .map(positive_isize_as_usize) + } + } + + let py = self.py(); + inner(self, value.to_object(py).attach_into(py)) + } + + #[inline] + fn contains(&self, value: V) -> PyResult + where + V: ToPyObject, + { + fn inner(seq: &Py2<'_, PySequence>, value: Py2<'_, PyAny>) -> PyResult { + unsafe { seq.do_ffi_call(|ptr| ffi::PySequence_Contains(ptr, value.as_ptr())) } + } + + let py = self.py(); + inner(self, value.to_object(py).attach_into(py)) + } + + #[inline] + fn index(&self, value: V) -> PyResult + where + V: ToPyObject, + { + fn inner(seq: &Py2<'_, PySequence>, value: Py2<'_, PyAny>) -> PyResult { + unsafe { + seq.do_ffi_call(|ptr| ffi::PySequence_Index(ptr, value.as_ptr())) + .map(positive_isize_as_usize) + } + } + + let py = self.py(); + inner(self, value.to_object(self.py()).attach_into(py)) + } + + #[inline] + fn to_list(&self) -> PyResult> { + unsafe { self.do_ffi_call(|ptr| ffi::PySequence_List(ptr)) } + } + + #[inline] + fn to_tuple(&self) -> PyResult> { + unsafe { self.do_ffi_call(|ptr| ffi::PySequence_Tuple(ptr)) } + } +} + #[inline] fn sequence_len(seq: &PySequence) -> usize { seq.len().expect("failed to get sequence length")