diff --git a/src/instance.rs b/src/instance.rs index e857efc9..e3f3743e 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -206,6 +206,14 @@ impl<'py, T> Bound<'py, T> { ) } + /// Removes the connection for this `Bound` from the GIL, allowing + /// it to cross thread boundaries. + pub fn unbind(self) -> Py { + // Safety: the type T is known to be correct and the ownership of the + // pointer is transferred to the new Py instance. + unsafe { Py::from_non_null(self.into_non_null()) } + } + /// Casts this `Bound` as the corresponding "GIL Ref" type. /// /// This is a helper to be used for migration from the deprecated "GIL Refs" API. @@ -973,7 +981,7 @@ impl Py { where N: IntoPy>, { - self.bind(py).as_any().getattr(attr_name).map(Into::into) + self.bind(py).as_any().getattr(attr_name).map(Bound::unbind) } /// Sets an attribute value. @@ -1017,21 +1025,21 @@ impl Py { args: impl IntoPy>, kwargs: Option<&PyDict>, ) -> PyResult { - self.bind(py).as_any().call(args, kwargs).map(Into::into) + self.bind(py).as_any().call(args, kwargs).map(Bound::unbind) } /// Calls the object with only positional arguments. /// /// This is equivalent to the Python expression `self(*args)`. pub fn call1(&self, py: Python<'_>, args: impl IntoPy>) -> PyResult { - self.bind(py).as_any().call1(args).map(Into::into) + self.bind(py).as_any().call1(args).map(Bound::unbind) } /// Calls the object without arguments. /// /// This is equivalent to the Python expression `self()`. pub fn call0(&self, py: Python<'_>) -> PyResult { - self.bind(py).as_any().call0().map(Into::into) + self.bind(py).as_any().call0().map(Bound::unbind) } /// Calls a method on the object. @@ -1054,7 +1062,7 @@ impl Py { self.bind(py) .as_any() .call_method(name, args, kwargs) - .map(Into::into) + .map(Bound::unbind) } /// Calls a method on the object with only positional arguments. @@ -1071,7 +1079,7 @@ impl Py { self.bind(py) .as_any() .call_method1(name, args) - .map(Into::into) + .map(Bound::unbind) } /// Calls a method on the object with no arguments. @@ -1084,7 +1092,7 @@ impl Py { where N: IntoPy>, { - self.bind(py).as_any().call_method0(name).map(Into::into) + self.bind(py).as_any().call_method0(name).map(Bound::unbind) } /// Create a `Py` instance by taking ownership of the given FFI pointer. @@ -1292,7 +1300,7 @@ where impl std::convert::From> for Py { #[inline] fn from(other: Bound<'_, T>) -> Self { - unsafe { Self::from_non_null(other.into_non_null()) } + other.unbind() } } @@ -1618,7 +1626,7 @@ a = A() .as_borrowed() .to_owned(); let ptr = instance.as_ptr(); - let instance: PyObject = instance.clone().into(); + let instance: PyObject = instance.clone().unbind(); assert_eq!(instance.as_ptr(), ptr); }) } diff --git a/src/prelude.rs b/src/prelude.rs index b06625dc..9be5dc0d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -31,7 +31,9 @@ pub use crate::types::bytearray::PyByteArrayMethods; pub use crate::types::bytes::PyBytesMethods; pub use crate::types::dict::PyDictMethods; pub use crate::types::float::PyFloatMethods; +pub use crate::types::frozenset::PyFrozenSetMethods; pub use crate::types::list::PyListMethods; pub use crate::types::mapping::PyMappingMethods; pub use crate::types::sequence::PySequenceMethods; +pub use crate::types::set::PySetMethods; pub use crate::types::string::PyStringMethods; diff --git a/src/types/dict.rs b/src/types/dict.rs index 80d2798a..0fe48662 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -626,15 +626,6 @@ impl<'py> BoundDictIterator<'py> { } } -impl<'py> IntoIterator for &'_ Bound<'py, PyDict> { - type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>); - type IntoIter = BoundDictIterator<'py>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - impl<'py> IntoIterator for Bound<'py, PyDict> { type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>); type IntoIter = BoundDictIterator<'py>; diff --git a/src/types/frozenset.rs b/src/types/frozenset.rs index bbcd7102..1bcdce4c 100644 --- a/src/types/frozenset.rs +++ b/src/types/frozenset.rs @@ -1,11 +1,11 @@ +#[cfg(not(Py_LIMITED_API))] +use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(Py_LIMITED_API)] use crate::types::PyIterator; use crate::{ err::{self, PyErr, PyResult}, - Py, PyObject, + ffi, Bound, Py, PyAny, PyNativeType, PyObject, Python, ToPyObject, }; -use crate::{ffi, PyAny, Python, ToPyObject}; - use std::ptr; /// Allows building a Python `frozenset` one item at a time @@ -83,12 +83,12 @@ impl PyFrozenSet { /// This is equivalent to len(p) on a set. #[inline] pub fn len(&self) -> usize { - unsafe { ffi::PySet_Size(self.as_ptr()) as usize } + self.as_borrowed().len() } /// Check if set is empty. pub fn is_empty(&self) -> bool { - self.len() == 0 + self.as_borrowed().is_empty() } /// Determine if the set contains the specified key. @@ -97,7 +97,54 @@ impl PyFrozenSet { where K: ToPyObject, { - fn inner(frozenset: &PyFrozenSet, key: PyObject) -> PyResult { + self.as_borrowed().contains(key) + } + + /// Returns an iterator of values in this frozen set. + pub fn iter(&self) -> PyFrozenSetIterator<'_> { + PyFrozenSetIterator(BoundFrozenSetIterator::new(self.as_borrowed().to_owned())) + } +} + +/// Implementation of functionality for [`PyFrozenSet`]. +/// +/// These methods are defined for the `Bound<'py, PyFrozenSet>` 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 = "PyFrozenSet")] +pub trait PyFrozenSetMethods<'py> { + /// Returns the number of items in the set. + /// + /// This is equivalent to the Python expression `len(self)`. + fn len(&self) -> usize; + + /// Checks if set is empty. + fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Determines if the set contains the specified key. + /// + /// This is equivalent to the Python expression `key in self`. + fn contains(&self, key: K) -> PyResult + where + K: ToPyObject; + + /// Returns an iterator of values in this set. + fn iter(&self) -> BoundFrozenSetIterator<'py>; +} + +impl<'py> PyFrozenSetMethods<'py> for Bound<'py, PyFrozenSet> { + #[inline] + fn len(&self) -> usize { + unsafe { ffi::PySet_Size(self.as_ptr()) as usize } + } + + fn contains(&self, key: K) -> PyResult + where + K: ToPyObject, + { + fn inner(frozenset: &Bound<'_, PyFrozenSet>, key: Bound<'_, PyAny>) -> PyResult { match unsafe { ffi::PySet_Contains(frozenset.as_ptr(), key.as_ptr()) } { 1 => Ok(true), 0 => Ok(false), @@ -105,12 +152,48 @@ impl PyFrozenSet { } } - inner(self, key.to_object(self.py())) + let py = self.py(); + inner(self, key.to_object(py).into_bound(py)) } - /// Returns an iterator of values in this frozen set. - pub fn iter(&self) -> PyFrozenSetIterator<'_> { - IntoIterator::into_iter(self) + fn iter(&self) -> BoundFrozenSetIterator<'py> { + BoundFrozenSetIterator::new(self.clone()) + } +} + +/// PyO3 implementation of an iterator for a Python `frozenset` object. +pub struct PyFrozenSetIterator<'py>(BoundFrozenSetIterator<'py>); + +impl<'py> Iterator for PyFrozenSetIterator<'py> { + type Item = &'py super::PyAny; + + /// Advances the iterator and returns the next value. + #[inline] + fn next(&mut self) -> Option { + self.0.next().map(Bound::into_gil_ref) + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl<'py> IntoIterator for &'py PyFrozenSet { + type Item = &'py PyAny; + type IntoIter = PyFrozenSetIterator<'py>; + /// Returns an iterator of values in this set. + fn into_iter(self) -> Self::IntoIter { + PyFrozenSetIterator(BoundFrozenSetIterator::new(self.as_borrowed().to_owned())) + } +} + +impl<'py> IntoIterator for Bound<'py, PyFrozenSet> { + type Item = Bound<'py, PyAny>; + type IntoIter = BoundFrozenSetIterator<'py>; + + /// Returns an iterator of values in this set. + fn into_iter(self) -> Self::IntoIter { + BoundFrozenSetIterator::new(self) } } @@ -118,25 +201,23 @@ impl PyFrozenSet { mod impl_ { use super::*; - impl<'a> std::iter::IntoIterator for &'a PyFrozenSet { - type Item = &'a PyAny; - type IntoIter = PyFrozenSetIterator<'a>; + /// PyO3 implementation of an iterator for a Python `set` object. + pub struct BoundFrozenSetIterator<'p> { + it: Bound<'p, PyIterator>, + } - fn into_iter(self) -> Self::IntoIter { - PyFrozenSetIterator { - it: PyIterator::from_object(self).unwrap(), + impl<'py> BoundFrozenSetIterator<'py> { + pub(super) fn new(frozenset: Bound<'py, PyFrozenSet>) -> Self { + Self { + it: PyIterator::from_object2(&frozenset).unwrap(), } } } - /// PyO3 implementation of an iterator for a Python `frozenset` object. - pub struct PyFrozenSetIterator<'p> { - it: &'p PyIterator, - } - - impl<'py> Iterator for PyFrozenSetIterator<'py> { - type Item = &'py super::PyAny; + impl<'py> Iterator for BoundFrozenSetIterator<'py> { + type Item = Bound<'py, super::PyAny>; + /// Advances the iterator and returns the next value. #[inline] fn next(&mut self) -> Option { self.it.next().map(Result::unwrap) @@ -148,23 +229,20 @@ mod impl_ { mod impl_ { use super::*; - impl<'a> std::iter::IntoIterator for &'a PyFrozenSet { - type Item = &'a PyAny; - type IntoIter = PyFrozenSetIterator<'a>; - - fn into_iter(self) -> Self::IntoIter { - PyFrozenSetIterator { set: self, pos: 0 } - } - } - /// PyO3 implementation of an iterator for a Python `frozenset` object. - pub struct PyFrozenSetIterator<'py> { - set: &'py PyFrozenSet, + pub struct BoundFrozenSetIterator<'py> { + set: Bound<'py, PyFrozenSet>, pos: ffi::Py_ssize_t, } - impl<'py> Iterator for PyFrozenSetIterator<'py> { - type Item = &'py PyAny; + impl<'py> BoundFrozenSetIterator<'py> { + pub(super) fn new(set: Bound<'py, PyFrozenSet>) -> Self { + Self { set, pos: 0 } + } + } + + impl<'py> Iterator for BoundFrozenSetIterator<'py> { + type Item = Bound<'py, PyAny>; #[inline] fn next(&mut self) -> Option { @@ -174,7 +252,7 @@ mod impl_ { if ffi::_PySet_NextEntry(self.set.as_ptr(), &mut self.pos, &mut key, &mut hash) != 0 { // _PySet_NextEntry returns borrowed object; for safety must make owned (see #890) - Some(self.set.py().from_owned_ptr(ffi::_Py_NewRef(key))) + Some(key.assume_borrowed(self.set.py()).to_owned()) } else { None } @@ -188,11 +266,17 @@ mod impl_ { } } - impl<'py> ExactSizeIterator for PyFrozenSetIterator<'py> { + impl<'py> ExactSizeIterator for BoundFrozenSetIterator<'py> { fn len(&self) -> usize { self.set.len().saturating_sub(self.pos as usize) } } + + impl<'py> ExactSizeIterator for PyFrozenSetIterator<'py> { + fn len(&self) -> usize { + self.0.len() + } + } } pub use impl_::*; @@ -243,6 +327,7 @@ mod tests { Python::with_gil(|py| { let set = PyFrozenSet::empty(py).unwrap(); assert_eq!(0, set.len()); + assert!(set.is_empty()); }); } @@ -271,6 +356,34 @@ mod tests { }); } + #[test] + #[cfg(not(Py_LIMITED_API))] + fn test_frozenset_iter_size_hint() { + Python::with_gil(|py| { + let set = PyFrozenSet::new(py, &[1]).unwrap(); + let mut iter = set.iter(); + + // Exact size + assert_eq!(iter.len(), 1); + assert_eq!(iter.size_hint(), (1, Some(1))); + iter.next(); + assert_eq!(iter.len(), 0); + assert_eq!(iter.size_hint(), (0, Some(0))); + }); + } + + #[test] + #[cfg(Py_LIMITED_API)] + fn test_frozenset_iter_size_hint() { + Python::with_gil(|py| { + let set = PyFrozenSet::new(py, &[1]).unwrap(); + let iter = set.iter(); + + // No known bounds + assert_eq!(iter.size_hint(), (0, None)); + }); + } + #[test] fn test_frozenset_builder() { use super::PyFrozenSetBuilder; diff --git a/src/types/list.rs b/src/types/list.rs index cdc39349..2ba4b10c 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -657,15 +657,6 @@ impl ExactSizeIterator for BoundListIterator<'_> { impl FusedIterator for BoundListIterator<'_> {} -impl<'a, 'py> IntoIterator for &'a Bound<'py, PyList> { - type Item = Bound<'py, PyAny>; - type IntoIter = BoundListIterator<'py>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - impl<'py> IntoIterator for Bound<'py, PyList> { type Item = Bound<'py, PyAny>; type IntoIter = BoundListIterator<'py>; diff --git a/src/types/mod.rs b/src/types/mod.rs index 41fb5b0a..705d3666 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -77,9 +77,9 @@ pub use self::typeobject::PyType; /// In these cases the iterators are implemented by forwarding to [`PyIterator`]. pub mod iter { pub use super::dict::{BoundDictIterator, PyDictIterator}; - pub use super::frozenset::PyFrozenSetIterator; + pub use super::frozenset::{BoundFrozenSetIterator, PyFrozenSetIterator}; pub use super::list::{BoundListIterator, PyListIterator}; - pub use super::set::PySetIterator; + pub use super::set::{BoundSetIterator, PySetIterator}; pub use super::tuple::PyTupleIterator; } @@ -288,7 +288,7 @@ mod ellipsis; pub(crate) mod float; #[cfg(all(not(Py_LIMITED_API), not(PyPy)))] mod frame; -mod frozenset; +pub(crate) mod frozenset; mod function; mod iterator; pub(crate) mod list; diff --git a/src/types/set.rs b/src/types/set.rs index 68b89d65..be65500b 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -2,7 +2,9 @@ use crate::types::PyIterator; use crate::{ err::{self, PyErr, PyResult}, - Py, + ffi_ptr_ext::FfiPtrExt, + instance::Bound, + Py, PyNativeType, }; use crate::{ffi, PyAny, PyObject, Python, ToPyObject}; use std::ptr; @@ -46,9 +48,7 @@ impl PySet { /// Removes all elements from the set. #[inline] pub fn clear(&self) { - unsafe { - ffi::PySet_Clear(self.as_ptr()); - } + self.as_borrowed().clear() } /// Returns the number of items in the set. @@ -56,12 +56,12 @@ impl PySet { /// This is equivalent to the Python expression `len(self)`. #[inline] pub fn len(&self) -> usize { - unsafe { ffi::PySet_Size(self.as_ptr()) as usize } + self.as_borrowed().len() } /// Checks if set is empty. pub fn is_empty(&self) -> bool { - self.len() == 0 + self.as_borrowed().is_empty() } /// Determines if the set contains the specified key. @@ -71,15 +71,7 @@ impl PySet { where K: ToPyObject, { - fn inner(set: &PySet, key: PyObject) -> PyResult { - match unsafe { ffi::PySet_Contains(set.as_ptr(), key.as_ptr()) } { - 1 => Ok(true), - 0 => Ok(false), - _ => Err(PyErr::fetch(set.py())), - } - } - - inner(self, key.to_object(self.py())) + self.as_borrowed().contains(key) } /// Removes the element from the set if it is present. @@ -89,15 +81,7 @@ impl PySet { where K: ToPyObject, { - fn inner(set: &PySet, key: PyObject) -> PyResult { - match unsafe { ffi::PySet_Discard(set.as_ptr(), key.as_ptr()) } { - 1 => Ok(true), - 0 => Ok(false), - _ => Err(PyErr::fetch(set.py())), - } - } - - inner(self, key.to_object(self.py())) + self.as_borrowed().discard(key) } /// Adds an element to the set. @@ -105,23 +89,12 @@ impl PySet { where K: ToPyObject, { - fn inner(set: &PySet, key: PyObject) -> PyResult<()> { - err::error_on_minusone(set.py(), unsafe { - ffi::PySet_Add(set.as_ptr(), key.as_ptr()) - }) - } - - inner(self, key.to_object(self.py())) + self.as_borrowed().add(key) } /// Removes and returns an arbitrary element from the set. pub fn pop(&self) -> Option { - let element = - unsafe { PyObject::from_owned_ptr_or_err(self.py(), ffi::PySet_Pop(self.as_ptr())) }; - match element { - Ok(e) => Some(e), - Err(_) => None, - } + self.as_borrowed().pop().map(Bound::unbind) } /// Returns an iterator of values in this set. @@ -130,7 +103,177 @@ impl PySet { /// /// If PyO3 detects that the set is mutated during iteration, it will panic. pub fn iter(&self) -> PySetIterator<'_> { - IntoIterator::into_iter(self) + PySetIterator(BoundSetIterator::new(self.as_borrowed().to_owned())) + } +} + +/// Implementation of functionality for [`PySet`]. +/// +/// These methods are defined for the `Bound<'py, PySet>` 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 = "PySet")] +pub trait PySetMethods<'py> { + /// Removes all elements from the set. + fn clear(&self); + + /// Returns the number of items in the set. + /// + /// This is equivalent to the Python expression `len(self)`. + fn len(&self) -> usize; + + /// Checks if set is empty. + fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Determines if the set contains the specified key. + /// + /// This is equivalent to the Python expression `key in self`. + fn contains(&self, key: K) -> PyResult + where + K: ToPyObject; + + /// Removes the element from the set if it is present. + /// + /// Returns `true` if the element was present in the set. + fn discard(&self, key: K) -> PyResult + where + K: ToPyObject; + + /// Adds an element to the set. + fn add(&self, key: K) -> PyResult<()> + where + K: ToPyObject; + + /// Removes and returns an arbitrary element from the set. + fn pop(&self) -> Option>; + + /// Returns an iterator of values in this set. + /// + /// # Panics + /// + /// If PyO3 detects that the set is mutated during iteration, it will panic. + fn iter(&self) -> BoundSetIterator<'py>; +} + +impl<'py> PySetMethods<'py> for Bound<'py, PySet> { + #[inline] + fn clear(&self) { + unsafe { + ffi::PySet_Clear(self.as_ptr()); + } + } + + #[inline] + fn len(&self) -> usize { + unsafe { ffi::PySet_Size(self.as_ptr()) as usize } + } + + fn contains(&self, key: K) -> PyResult + where + K: ToPyObject, + { + fn inner(set: &Bound<'_, PySet>, key: Bound<'_, PyAny>) -> PyResult { + match unsafe { ffi::PySet_Contains(set.as_ptr(), key.as_ptr()) } { + 1 => Ok(true), + 0 => Ok(false), + _ => Err(PyErr::fetch(set.py())), + } + } + + let py = self.py(); + inner(self, key.to_object(py).into_bound(py)) + } + + fn discard(&self, key: K) -> PyResult + where + K: ToPyObject, + { + fn inner(set: &Bound<'_, PySet>, key: Bound<'_, PyAny>) -> PyResult { + match unsafe { ffi::PySet_Discard(set.as_ptr(), key.as_ptr()) } { + 1 => Ok(true), + 0 => Ok(false), + _ => Err(PyErr::fetch(set.py())), + } + } + + let py = self.py(); + inner(self, key.to_object(py).into_bound(py)) + } + + fn add(&self, key: K) -> PyResult<()> + where + K: ToPyObject, + { + fn inner(set: &Bound<'_, PySet>, key: Bound<'_, PyAny>) -> PyResult<()> { + err::error_on_minusone(set.py(), unsafe { + ffi::PySet_Add(set.as_ptr(), key.as_ptr()) + }) + } + + let py = self.py(); + inner(self, key.to_object(py).into_bound(py)) + } + + fn pop(&self) -> Option> { + let element = unsafe { ffi::PySet_Pop(self.as_ptr()).assume_owned_or_err(self.py()) }; + match element { + Ok(e) => Some(e), + Err(_) => None, + } + } + + fn iter(&self) -> BoundSetIterator<'py> { + BoundSetIterator::new(self.clone()) + } +} + +/// PyO3 implementation of an iterator for a Python `set` object. +pub struct PySetIterator<'py>(BoundSetIterator<'py>); + +impl<'py> Iterator for PySetIterator<'py> { + type Item = &'py super::PyAny; + + /// Advances the iterator and returns the next value. + /// + /// # Panics + /// + /// If PyO3 detects that the set is mutated during iteration, it will panic. + #[inline] + fn next(&mut self) -> Option { + self.0.next().map(Bound::into_gil_ref) + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl<'py> IntoIterator for &'py PySet { + type Item = &'py PyAny; + type IntoIter = PySetIterator<'py>; + /// Returns an iterator of values in this set. + /// + /// # Panics + /// + /// If PyO3 detects that the set is mutated during iteration, it will panic. + fn into_iter(self) -> Self::IntoIter { + PySetIterator(BoundSetIterator::new(self.as_borrowed().to_owned())) + } +} + +impl<'py> IntoIterator for Bound<'py, PySet> { + type Item = Bound<'py, PyAny>; + type IntoIter = BoundSetIterator<'py>; + + /// Returns an iterator of values in this set. + /// + /// # Panics + /// + /// If PyO3 detects that the set is mutated during iteration, it will panic. + fn into_iter(self) -> Self::IntoIter { + BoundSetIterator::new(self) } } @@ -138,29 +281,21 @@ impl PySet { mod impl_ { use super::*; - impl<'a> std::iter::IntoIterator for &'a PySet { - type Item = &'a PyAny; - type IntoIter = PySetIterator<'a>; + /// PyO3 implementation of an iterator for a Python `set` object. + pub struct BoundSetIterator<'p> { + it: Bound<'p, PyIterator>, + } - /// Returns an iterator of values in this set. - /// - /// # Panics - /// - /// If PyO3 detects that the set is mutated during iteration, it will panic. - fn into_iter(self) -> Self::IntoIter { - PySetIterator { - it: PyIterator::from_object(self).unwrap(), + impl<'py> BoundSetIterator<'py> { + pub(super) fn new(set: Bound<'py, PySet>) -> Self { + Self { + it: PyIterator::from_object2(&set).unwrap(), } } } - /// PyO3 implementation of an iterator for a Python `set` object. - pub struct PySetIterator<'p> { - it: &'p PyIterator, - } - - impl<'py> Iterator for PySetIterator<'py> { - type Item = &'py super::PyAny; + impl<'py> Iterator for BoundSetIterator<'py> { + type Item = Bound<'py, super::PyAny>; /// Advances the iterator and returns the next value. /// @@ -179,31 +314,21 @@ mod impl_ { use super::*; /// PyO3 implementation of an iterator for a Python `set` object. - pub struct PySetIterator<'py> { - set: &'py super::PySet, + pub struct BoundSetIterator<'py> { + set: Bound<'py, super::PySet>, pos: ffi::Py_ssize_t, used: ffi::Py_ssize_t, } - impl<'a> std::iter::IntoIterator for &'a PySet { - type Item = &'a PyAny; - type IntoIter = PySetIterator<'a>; - /// Returns an iterator of values in this set. - /// - /// # Panics - /// - /// If PyO3 detects that the set is mutated during iteration, it will panic. - fn into_iter(self) -> Self::IntoIter { - PySetIterator { - set: self, - pos: 0, - used: unsafe { ffi::PySet_Size(self.as_ptr()) }, - } + impl<'py> BoundSetIterator<'py> { + pub(super) fn new(set: Bound<'py, PySet>) -> Self { + let used = unsafe { ffi::PySet_Size(set.as_ptr()) }; + BoundSetIterator { set, pos: 0, used } } } - impl<'py> Iterator for PySetIterator<'py> { - type Item = &'py super::PyAny; + impl<'py> Iterator for BoundSetIterator<'py> { + type Item = Bound<'py, super::PyAny>; /// Advances the iterator and returns the next value. /// @@ -221,7 +346,7 @@ mod impl_ { if ffi::_PySet_NextEntry(self.set.as_ptr(), &mut self.pos, &mut key, &mut hash) != 0 { // _PySet_NextEntry returns borrowed object; for safety must make owned (see #890) - Some(self.set.py().from_owned_ptr(ffi::_Py_NewRef(key))) + Some(key.assume_borrowed(self.set.py()).to_owned()) } else { None } @@ -235,11 +360,17 @@ mod impl_ { } } - impl<'py> ExactSizeIterator for PySetIterator<'py> { + impl<'py> ExactSizeIterator for BoundSetIterator<'py> { fn len(&self) -> usize { self.set.len().saturating_sub(self.pos as usize) } } + + impl<'py> ExactSizeIterator for PySetIterator<'py> { + fn len(&self) -> usize { + self.0.len() + } + } } pub use impl_::*; @@ -289,6 +420,7 @@ mod tests { Python::with_gil(|py| { let set = PySet::empty(py).unwrap(); assert_eq!(0, set.len()); + assert!(set.is_empty()); }); } @@ -406,19 +538,30 @@ mod tests { } #[test] + #[cfg(not(Py_LIMITED_API))] fn test_set_iter_size_hint() { Python::with_gil(|py| { let set = PySet::new(py, &[1]).unwrap(); - let mut iter = set.iter(); - if cfg!(Py_LIMITED_API) { - assert_eq!(iter.size_hint(), (0, None)); - } else { - assert_eq!(iter.size_hint(), (1, Some(1))); - iter.next(); - assert_eq!(iter.size_hint(), (0, Some(0))); - } + // Exact size + assert_eq!(iter.len(), 1); + assert_eq!(iter.size_hint(), (1, Some(1))); + iter.next(); + assert_eq!(iter.len(), 0); + assert_eq!(iter.size_hint(), (0, Some(0))); + }); + } + + #[test] + #[cfg(Py_LIMITED_API)] + fn test_set_iter_size_hint() { + Python::with_gil(|py| { + let set = PySet::new(py, &[1]).unwrap(); + let iter = set.iter(); + + // No known bounds + assert_eq!(iter.size_hint(), (0, None)); }); } } diff --git a/src/types/string.rs b/src/types/string.rs index 65f5a392..0ee64be5 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -435,13 +435,13 @@ impl Py { impl IntoPy> for Bound<'_, PyString> { fn into_py(self, _py: Python<'_>) -> Py { - self.into() + self.unbind() } } impl IntoPy> for &Bound<'_, PyString> { fn into_py(self, _py: Python<'_>) -> Py { - self.clone().into() + self.clone().unbind() } }