diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 631acdb2bf3..fd9e90097f2 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -183,7 +183,7 @@ struct RustyTuple(String, String); # use pyo3::types::PyTuple; # fn main() -> PyResult<()> { # Python::with_gil(|py| -> PyResult<()> { -# let tuple = PyTuple::new(py, vec!["test", "test2"]); +# let tuple = PyTuple::new(py, vec!["test", "test2"])?; # # let rustytuple: RustyTuple = tuple.extract()?; # assert_eq!(rustytuple.0, "test"); @@ -206,7 +206,7 @@ struct RustyTuple((String,)); # use pyo3::types::PyTuple; # fn main() -> PyResult<()> { # Python::with_gil(|py| -> PyResult<()> { -# let tuple = PyTuple::new(py, vec!["test"]); +# let tuple = PyTuple::new(py, vec!["test"])?; # # let rustytuple: RustyTuple = tuple.extract()?; # assert_eq!((rustytuple.0).0, "test"); diff --git a/guide/src/python-from-rust/function-calls.md b/guide/src/python-from-rust/function-calls.md index 12544dc02bd..f1eb8025431 100644 --- a/guide/src/python-from-rust/function-calls.md +++ b/guide/src/python-from-rust/function-calls.md @@ -50,7 +50,7 @@ fn main() -> PyResult<()> { fun.call1(py, args)?; // call object with Python tuple of positional arguments - let args = PyTuple::new(py, &[arg1, arg2, arg3]); + let args = PyTuple::new(py, &[arg1, arg2, arg3])?; fun.call1(py, args)?; Ok(()) }) diff --git a/guide/src/types.md b/guide/src/types.md index df4d6b4812d..06559d49d13 100644 --- a/guide/src/types.md +++ b/guide/src/types.md @@ -145,7 +145,7 @@ use pyo3::types::PyTuple; # fn example<'py>(py: Python<'py>) -> PyResult<()> { // Create a new tuple with the elements (0, 1, 2) -let t = PyTuple::new(py, [0, 1, 2]); +let t = PyTuple::new(py, [0, 1, 2])?; for i in 0..=2 { let entry: Borrowed<'_, 'py, PyAny> = t.get_borrowed_item(i)?; // `PyAnyMethods::extract` is available on `Borrowed` @@ -295,7 +295,7 @@ For example, the following snippet extracts a Rust tuple of integers from a Pyth # use pyo3::types::PyTuple; # fn example<'py>(py: Python<'py>) -> PyResult<()> { // create a new Python `tuple`, and use `.into_any()` to erase the type -let obj: Bound<'py, PyAny> = PyTuple::new(py, [1, 2, 3]).into_any(); +let obj: Bound<'py, PyAny> = PyTuple::new(py, [1, 2, 3])?.into_any(); // extracting the Python `tuple` to a rust `(i32, i32, i32)` tuple let (x, y, z) = obj.extract::<(i32, i32, i32)>()?; diff --git a/pytests/src/datetime.rs b/pytests/src/datetime.rs index 744be279431..3bdf103b62e 100644 --- a/pytests/src/datetime.rs +++ b/pytests/src/datetime.rs @@ -12,7 +12,7 @@ fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult(d: &Bound<'py, PyDate>) -> Bound<'py, PyTuple> { +fn get_date_tuple<'py>(d: &Bound<'py, PyDate>) -> PyResult> { PyTuple::new( d.py(), [d.get_year(), d.get_month() as i32, d.get_day() as i32], @@ -52,7 +52,7 @@ fn time_with_fold<'py>( } #[pyfunction] -fn get_time_tuple<'py>(dt: &Bound<'py, PyTime>) -> Bound<'py, PyTuple> { +fn get_time_tuple<'py>(dt: &Bound<'py, PyTime>) -> PyResult> { PyTuple::new( dt.py(), [ @@ -65,7 +65,7 @@ fn get_time_tuple<'py>(dt: &Bound<'py, PyTime>) -> Bound<'py, PyTuple> { } #[pyfunction] -fn get_time_tuple_fold<'py>(dt: &Bound<'py, PyTime>) -> Bound<'py, PyTuple> { +fn get_time_tuple_fold<'py>(dt: &Bound<'py, PyTime>) -> PyResult> { PyTuple::new( dt.py(), [ @@ -89,7 +89,7 @@ fn make_delta( } #[pyfunction] -fn get_delta_tuple<'py>(delta: &Bound<'py, PyDelta>) -> Bound<'py, PyTuple> { +fn get_delta_tuple<'py>(delta: &Bound<'py, PyDelta>) -> PyResult> { PyTuple::new( delta.py(), [ @@ -128,7 +128,7 @@ fn make_datetime<'py>( } #[pyfunction] -fn get_datetime_tuple<'py>(dt: &Bound<'py, PyDateTime>) -> Bound<'py, PyTuple> { +fn get_datetime_tuple<'py>(dt: &Bound<'py, PyDateTime>) -> PyResult> { PyTuple::new( dt.py(), [ @@ -144,7 +144,7 @@ fn get_datetime_tuple<'py>(dt: &Bound<'py, PyDateTime>) -> Bound<'py, PyTuple> { } #[pyfunction] -fn get_datetime_tuple_fold<'py>(dt: &Bound<'py, PyDateTime>) -> Bound<'py, PyTuple> { +fn get_datetime_tuple_fold<'py>(dt: &Bound<'py, PyDateTime>) -> PyResult> { PyTuple::new( dt.py(), [ diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index bb31f96d8b7..098722060c6 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -703,7 +703,7 @@ impl<'py> VarargsHandler<'py> for TupleVarargs { varargs: &[Option>], _function_description: &FunctionDescription, ) -> PyResult { - Ok(PyTuple::new(py, varargs)) + PyTuple::new(py, varargs) } #[inline] diff --git a/src/impl_/pymodule.rs b/src/impl_/pymodule.rs index 270d32f15a9..ab38bc49e8e 100644 --- a/src/impl_/pymodule.rs +++ b/src/impl_/pymodule.rs @@ -97,7 +97,7 @@ impl ModuleDef { .import("sys")? .getattr("implementation")? .getattr("version")?; - if version.lt(crate::types::PyTuple::new(py, PYPY_GOOD_VERSION))? { + if version.lt(crate::types::PyTuple::new(py, PYPY_GOOD_VERSION)?)? { let warn = py.import("warnings")?.getattr("warn")?; warn.call1(( "PyPy 3.7 versions older than 7.3.8 are known to have binary \ diff --git a/src/instance.rs b/src/instance.rs index 26c6b14ffa0..1ff18f82466 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -640,7 +640,7 @@ impl<'a, 'py, T> Borrowed<'a, 'py, T> { /// /// # fn main() -> PyResult<()> { /// Python::with_gil(|py| -> PyResult<()> { - /// let tuple = PyTuple::new(py, [1, 2, 3]); + /// let tuple = PyTuple::new(py, [1, 2, 3])?; /// /// // borrows from `tuple`, so can only be /// // used while `tuple` stays alive diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 2f6906064f3..ac956c250d3 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -223,7 +223,7 @@ impl PyDate { /// /// This is equivalent to `datetime.date.fromtimestamp` pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult> { - let time_tuple = PyTuple::new(py, [timestamp]); + let time_tuple = PyTuple::new(py, [timestamp])?; // safety ensure that the API is loaded let _api = ensure_datetime_api(py)?; diff --git a/src/types/list.rs b/src/types/list.rs index 9bc17fa9780..ec6383b4419 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -1108,7 +1108,7 @@ mod tests { Python::with_gil(|py| { let list = PyList::new(py, vec![1, 2, 3]).unwrap(); let tuple = list.to_tuple(); - let tuple_expected = PyTuple::new(py, vec![1, 2, 3]); + let tuple_expected = PyTuple::new(py, vec![1, 2, 3]).unwrap(); assert!(tuple.eq(tuple_expected).unwrap()); }) } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index ae337ae7583..03e20644ee5 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -777,7 +777,7 @@ mod tests { assert!(seq .to_tuple() .unwrap() - .eq(PyTuple::new(py, ["foo", "bar"])) + .eq(PyTuple::new(py, ["foo", "bar"]).unwrap()) .unwrap()); }); } @@ -788,7 +788,11 @@ mod tests { let v = vec!["foo", "bar"]; let ob = (&v).into_pyobject(py).unwrap(); let seq = ob.downcast::().unwrap(); - assert!(seq.to_tuple().unwrap().eq(PyTuple::new(py, &v)).unwrap()); + assert!(seq + .to_tuple() + .unwrap() + .eq(PyTuple::new(py, &v).unwrap()) + .unwrap()); }); } diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 265ce27708a..a7c9be1ebb7 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -17,10 +17,10 @@ use crate::{ #[inline] #[track_caller] -fn new_from_iter<'py>( +fn try_new_from_iter<'py>( py: Python<'py>, - elements: &mut dyn ExactSizeIterator, -) -> Bound<'py, PyTuple> { + elements: &mut dyn ExactSizeIterator>>, +) -> PyResult> { unsafe { // PyTuple_New checks for overflow but has a bad error message, so we check ourselves let len: Py_ssize_t = elements @@ -38,16 +38,16 @@ fn new_from_iter<'py>( for obj in elements.take(len as usize) { #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] - ffi::PyTuple_SET_ITEM(ptr, counter, obj.into_ptr()); + ffi::PyTuple_SET_ITEM(ptr, counter, obj?.into_ptr()); #[cfg(any(Py_LIMITED_API, PyPy, GraalPy))] - ffi::PyTuple_SetItem(ptr, counter, obj.into_ptr()); + ffi::PyTuple_SetItem(ptr, counter, obj?.into_ptr()); counter += 1; } assert!(elements.next().is_none(), "Attempted to create PyTuple but `elements` was larger than reported by its `ExactSizeIterator` implementation."); assert_eq!(len, counter, "Attempted to create PyTuple but `elements` was smaller than reported by its `ExactSizeIterator` implementation."); - tup + Ok(tup) } } @@ -76,12 +76,13 @@ impl PyTuple { /// use pyo3::prelude::*; /// use pyo3::types::PyTuple; /// - /// # fn main() { + /// # fn main() -> PyResult<()> { /// Python::with_gil(|py| { /// let elements: Vec = vec![0, 1, 2, 3, 4, 5]; - /// let tuple = PyTuple::new(py, elements); + /// let tuple = PyTuple::new(py, elements)?; /// assert_eq!(format!("{:?}", tuple), "(0, 1, 2, 3, 4, 5)"); - /// }); + /// # Ok(()) + /// }) /// # } /// ``` /// @@ -91,16 +92,21 @@ impl PyTuple { /// All standard library structures implement this trait correctly, if they do, so calling this /// function using [`Vec`]`` or `&[T]` will always succeed. #[track_caller] - pub fn new( - py: Python<'_>, + pub fn new<'py, T, U>( + py: Python<'py>, elements: impl IntoIterator, - ) -> Bound<'_, PyTuple> + ) -> PyResult> where - T: ToPyObject, + T: IntoPyObject<'py>, U: ExactSizeIterator, { - let mut elements = elements.into_iter().map(|e| e.to_object(py)); - new_from_iter(py, &mut elements) + let mut elements = elements.into_iter().map(|e| { + e.into_pyobject(py) + .map(BoundObject::into_any) + .map(BoundObject::into_bound) + .map_err(Into::into) + }); + try_new_from_iter(py, &mut elements) } /// Deprecated name for [`PyTuple::new`]. @@ -115,7 +121,7 @@ impl PyTuple { T: ToPyObject, U: ExactSizeIterator, { - PyTuple::new(py, elements) + PyTuple::new(py, elements.into_iter().map(|e| e.to_object(py))).unwrap() } /// Constructs an empty tuple (on the Python side, a singleton object). @@ -542,6 +548,19 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ } } + impl <'a, 'py, $($T),+> IntoPyObject<'py> for &'a ($($T,)+) + where + $(&'a $T: IntoPyObject<'py>,)+ + { + type Target = PyTuple; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(array_into_tuple(py, [$(self.$n.into_pyobject(py).map_err(Into::into)?.into_any().unbind()),+]).into_bound(py)) + } + } + impl <$($T: IntoPy),+> IntoPy> for ($($T,)+) { fn into_py(self, py: Python<'_>) -> Py { array_into_tuple(py, [$(self.$n.into_py(py)),+]) @@ -805,7 +824,7 @@ mod tests { #[test] fn test_new() { Python::with_gil(|py| { - let ob = PyTuple::new(py, [1, 2, 3]); + let ob = PyTuple::new(py, [1, 2, 3]).unwrap(); assert_eq!(3, ob.len()); let ob = ob.as_any(); assert_eq!((1, 2, 3), ob.extract().unwrap()); @@ -813,7 +832,7 @@ mod tests { let mut map = HashSet::new(); map.insert(1); map.insert(2); - PyTuple::new(py, map); + PyTuple::new(py, map).unwrap(); }); } @@ -841,7 +860,7 @@ mod tests { #[test] fn test_slice() { Python::with_gil(|py| { - let tup = PyTuple::new(py, [2, 3, 5, 7]); + let tup = PyTuple::new(py, [2, 3, 5, 7]).unwrap(); let slice = tup.get_slice(1, 3); assert_eq!(2, slice.len()); let slice = tup.get_slice(1, 7); @@ -900,7 +919,7 @@ mod tests { #[test] fn test_bound_iter() { Python::with_gil(|py| { - let tuple = PyTuple::new(py, [1, 2, 3]); + let tuple = PyTuple::new(py, [1, 2, 3]).unwrap(); assert_eq!(3, tuple.len()); let mut iter = tuple.iter(); @@ -923,7 +942,7 @@ mod tests { #[test] fn test_bound_iter_rev() { Python::with_gil(|py| { - let tuple = PyTuple::new(py, [1, 2, 3]); + let tuple = PyTuple::new(py, [1, 2, 3]).unwrap(); assert_eq!(3, tuple.len()); let mut iter = tuple.iter().rev(); @@ -1178,13 +1197,12 @@ mod tests { #[cfg(feature = "macros")] #[test] fn bad_clone_mem_leaks() { - use crate::{IntoPy, Py, PyAny, ToPyObject}; + use crate::types::PyInt; + use std::convert::Infallible; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; static NEEDS_DESTRUCTING_COUNT: AtomicUsize = AtomicUsize::new(0); - #[crate::pyclass] - #[pyo3(crate = "crate")] struct Bad(usize); impl Clone for Bad { @@ -1203,9 +1221,13 @@ mod tests { } } - impl ToPyObject for Bad { - fn to_object(&self, py: Python<'_>) -> Py { - self.to_owned().into_py(py) + impl<'py> IntoPyObject<'py> for Bad { + type Target = PyInt; + type Output = crate::Bound<'py, Self::Target>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + self.clone().0.into_pyobject(py) } } @@ -1246,13 +1268,12 @@ mod tests { #[cfg(feature = "macros")] #[test] fn bad_clone_mem_leaks_2() { - use crate::{IntoPy, Py, PyAny, ToPyObject}; + use crate::types::PyInt; + use std::convert::Infallible; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; static NEEDS_DESTRUCTING_COUNT: AtomicUsize = AtomicUsize::new(0); - #[crate::pyclass] - #[pyo3(crate = "crate")] struct Bad(usize); impl Clone for Bad { @@ -1271,9 +1292,13 @@ mod tests { } } - impl ToPyObject for Bad { - fn to_object(&self, py: Python<'_>) -> Py { - self.to_owned().into_py(py) + impl<'py> IntoPyObject<'py> for &Bad { + type Target = PyInt; + type Output = crate::Bound<'py, Self::Target>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + self.clone().0.into_pyobject(py) } } @@ -1281,7 +1306,7 @@ mod tests { NEEDS_DESTRUCTING_COUNT.store(4, SeqCst); Python::with_gil(|py| { std::panic::catch_unwind(|| { - let _tuple: Py = s.to_object(py); + let _tuple = (&s).into_pyobject(py).unwrap(); }) .unwrap_err(); }); @@ -1297,7 +1322,7 @@ mod tests { #[test] fn test_tuple_to_list() { Python::with_gil(|py| { - let tuple = PyTuple::new(py, vec![1, 2, 3]); + let tuple = PyTuple::new(py, vec![1, 2, 3]).unwrap(); let list = tuple.to_list(); let list_expected = PyList::new(py, vec![1, 2, 3]).unwrap(); assert!(list.eq(list_expected).unwrap()); @@ -1307,7 +1332,7 @@ mod tests { #[test] fn test_tuple_as_sequence() { Python::with_gil(|py| { - let tuple = PyTuple::new(py, vec![1, 2, 3]); + let tuple = PyTuple::new(py, vec![1, 2, 3]).unwrap(); let sequence = tuple.as_sequence(); assert!(tuple.get_item(0).unwrap().eq(1).unwrap()); assert!(sequence.get_item(0).unwrap().eq(1).unwrap()); @@ -1320,7 +1345,7 @@ mod tests { #[test] fn test_tuple_into_sequence() { Python::with_gil(|py| { - let tuple = PyTuple::new(py, vec![1, 2, 3]); + let tuple = PyTuple::new(py, vec![1, 2, 3]).unwrap(); let sequence = tuple.into_sequence(); assert!(sequence.get_item(0).unwrap().eq(1).unwrap()); assert_eq!(sequence.len().unwrap(), 3); @@ -1330,7 +1355,7 @@ mod tests { #[test] fn test_bound_tuple_get_item() { Python::with_gil(|py| { - let tuple = PyTuple::new(py, vec![1, 2, 3, 4]); + let tuple = PyTuple::new(py, vec![1, 2, 3, 4]).unwrap(); assert_eq!(tuple.len(), 4); assert_eq!(tuple.get_item(0).unwrap().extract::().unwrap(), 1); diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 8d4e759580c..7a66b7ad0df 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -285,7 +285,8 @@ mod tests { py.get_type::(), py.get_type::() ] - )) + ) + .unwrap()) .unwrap()); }); } @@ -296,7 +297,7 @@ mod tests { assert!(py .get_type::() .bases() - .eq(PyTuple::new(py, [py.get_type::()])) + .eq(PyTuple::new(py, [py.get_type::()]).unwrap()) .unwrap()); }); } diff --git a/tests/test_frompyobject.rs b/tests/test_frompyobject.rs index f7699d305b2..a1b91c25128 100644 --- a/tests/test_frompyobject.rs +++ b/tests/test_frompyobject.rs @@ -168,10 +168,24 @@ pub struct Tuple(String, usize); #[test] fn test_tuple_struct() { Python::with_gil(|py| { - let tup = PyTuple::new(py, &[1.into_py(py), "test".into_py(py)]); + let tup = PyTuple::new( + py, + &[ + 1i32.into_pyobject(py).unwrap().into_any(), + "test".into_pyobject(py).unwrap().into_any(), + ], + ) + .unwrap(); let tup = tup.extract::(); assert!(tup.is_err()); - let tup = PyTuple::new(py, &["test".into_py(py), 1.into_py(py)]); + let tup = PyTuple::new( + py, + &[ + "test".into_pyobject(py).unwrap().into_any(), + 1i32.into_pyobject(py).unwrap().into_any(), + ], + ) + .unwrap(); let tup = tup .extract::() .expect("Failed to extract Tuple from PyTuple"); @@ -333,7 +347,7 @@ pub struct PyBool { #[test] fn test_enum() { Python::with_gil(|py| { - let tup = PyTuple::new(py, &[1.into_py(py), "test".into_py(py)]); + let tup = PyTuple::new(py, &[1i32.into_py(py), "test".into_py(py)]).unwrap(); let f = tup .extract::>() .expect("Failed to extract Foo from tuple"); diff --git a/tests/test_various.rs b/tests/test_various.rs index dc6bbc76dba..27192aba3bb 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -91,7 +91,7 @@ fn intopytuple_pyclass() { #[test] fn pytuple_primitive_iter() { Python::with_gil(|py| { - let tup = PyTuple::new(py, [1u32, 2, 3].iter()); + let tup = PyTuple::new(py, [1u32, 2, 3].iter()).unwrap(); py_assert!(py, tup, "tup == (1, 2, 3)"); }); } @@ -106,7 +106,8 @@ fn pytuple_pyclass_iter() { Py::new(py, SimplePyClass {}).unwrap(), ] .iter(), - ); + ) + .unwrap(); py_assert!(py, tup, "type(tup[0]).__name__ == 'SimplePyClass'"); py_assert!(py, tup, "type(tup[0]).__name__ == type(tup[0]).__name__"); py_assert!(py, tup, "tup[0] != tup[1]"); diff --git a/tests/ui/invalid_property_args.stderr b/tests/ui/invalid_property_args.stderr index 70f3dd4e8f8..786533efd53 100644 --- a/tests/ui/invalid_property_args.stderr +++ b/tests/ui/invalid_property_args.stderr @@ -56,13 +56,13 @@ error[E0277]: `PhantomData` cannot be converted to a Python object = note: implement `IntoPyObject` for `&PhantomData` or `IntoPyObject + Clone` for `PhantomData` to define the conversion = help: the following other types implement trait `IntoPyObject<'py>`: &&str - &'a BTreeMap - &'a BTreeSet - &'a Cell - &'a HashMap - &'a HashSet - &'a Option - &'a Py + &'a (T0, T1) + &'a (T0, T1, T2) + &'a (T0, T1, T2, T3) + &'a (T0, T1, T2, T3, T4) + &'a (T0, T1, T2, T3, T4, T5) + &'a (T0, T1, T2, T3, T4, T5, T6) + &'a (T0, T1, T2, T3, T4, T5, T6, T7) and $N others = note: required for `PhantomData` to implement `for<'py> PyO3GetField<'py>` note: required by a bound in `PyClassGetterGenerator::::generate` diff --git a/tests/ui/missing_intopy.stderr b/tests/ui/missing_intopy.stderr index f7dcca419bf..653fb785dfd 100644 --- a/tests/ui/missing_intopy.stderr +++ b/tests/ui/missing_intopy.stderr @@ -9,13 +9,13 @@ error[E0277]: `Blah` cannot be converted to a Python object = note: if you do not own `Blah` you can perform a manual conversion to one of the types in `pyo3::types::*` = help: the following other types implement trait `IntoPyObject<'py>`: &&str - &'a BTreeMap - &'a BTreeSet - &'a Cell - &'a HashMap - &'a HashSet - &'a Option - &'a Py + &'a (T0, T1) + &'a (T0, T1, T2) + &'a (T0, T1, T2, T3) + &'a (T0, T1, T2, T3, T4) + &'a (T0, T1, T2, T3, T4, T5) + &'a (T0, T1, T2, T3, T4, T5, T6) + &'a (T0, T1, T2, T3, T4, T5, T6, T7) and $N others note: required by a bound in `UnknownReturnType::::wrap` --> src/impl_/wrap.rs