1use crate::types::PyIterator;
4use crate::{
5 Bound, PyAny, Python,
6 err::{self, PyErr, PyResult},
7 ffi,
8 ffi_ptr_ext::FfiPtrExt,
9 py_result_ext::PyResultExt,
10};
11use crate::{BoundObject, IntoPyObject, IntoPyObjectExt};
12#[cfg(RustPython)]
13use crate::{
14 Py,
15 sync::PyOnceLock,
16 types::{PyType, PyTypeMethods},
17};
18use core::ptr;
19
20pub struct PyFrozenSetBuilder<'py> {
22 py_frozen_set: Bound<'py, PyFrozenSet>,
23}
24
25impl<'py> PyFrozenSetBuilder<'py> {
26 pub fn new(py: Python<'py>) -> PyResult<PyFrozenSetBuilder<'py>> {
30 Ok(PyFrozenSetBuilder {
31 py_frozen_set: PyFrozenSet::empty(py)?,
32 })
33 }
34
35 pub fn add<K>(&mut self, key: K) -> PyResult<()>
37 where
38 K: IntoPyObject<'py>,
39 {
40 let py = self.py_frozen_set.py();
41 let key = key.into_pyobject_or_pyerr(py)?;
42 err::error_on_negative(py, unsafe {
43 ffi::PySet_Add(self.py_frozen_set.as_ptr(), key.as_ptr())
44 })
45 }
46
47 pub fn finalize(self) -> Bound<'py, PyFrozenSet> {
49 self.py_frozen_set
50 }
51}
52
53#[repr(transparent)]
61pub struct PyFrozenSet(PyAny);
62
63#[cfg(not(any(PyPy, GraalPy)))]
64pyobject_subclassable_native_type!(PyFrozenSet, crate::ffi::PySetObject);
65#[cfg(all(not(any(PyPy, GraalPy)), not(RustPython)))]
66pyobject_native_type!(
67 PyFrozenSet,
68 ffi::PySetObject,
69 pyobject_native_static_type_object!(ffi::PyFrozenSet_Type),
70 "builtins",
71 "frozenset",
72 #checkfunction=ffi::PyFrozenSet_Check
73);
74
75#[cfg(all(not(any(PyPy, GraalPy)), RustPython))]
76pyobject_native_type!(
77 PyFrozenSet,
78 ffi::PySetObject,
79 |py| {
80 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
81 TYPE.import(py, "builtins", "frozenset").unwrap().as_type_ptr()
82 },
83 "builtins",
84 "frozenset",
85 #checkfunction=ffi::PyFrozenSet_Check
86);
87
88#[cfg(any(PyPy, GraalPy))]
89pyobject_native_type_core!(
90 PyFrozenSet,
91 pyobject_native_static_type_object!(ffi::PyFrozenSet_Type),
92 "builtins",
93 "frozenset",
94 #checkfunction=ffi::PyFrozenSet_Check
95);
96
97impl PyFrozenSet {
98 #[inline]
102 pub fn new<'py, T>(
103 py: Python<'py>,
104 elements: impl IntoIterator<Item = T>,
105 ) -> PyResult<Bound<'py, PyFrozenSet>>
106 where
107 T: IntoPyObject<'py>,
108 {
109 let mut builder = PyFrozenSetBuilder::new(py)?;
110 for e in elements {
111 builder.add(e)?;
112 }
113 Ok(builder.finalize())
114 }
115
116 pub fn empty(py: Python<'_>) -> PyResult<Bound<'_, PyFrozenSet>> {
118 unsafe {
119 ffi::PyFrozenSet_New(ptr::null_mut())
120 .assume_owned_or_err(py)
121 .cast_into_unchecked()
122 }
123 }
124}
125
126#[doc(alias = "PyFrozenSet")]
132pub trait PyFrozenSetMethods<'py>: crate::sealed::Sealed {
133 fn len(&self) -> usize;
137
138 fn is_empty(&self) -> bool {
140 self.len() == 0
141 }
142
143 fn contains<K>(&self, key: K) -> PyResult<bool>
147 where
148 K: IntoPyObject<'py>;
149
150 fn iter(&self) -> BoundFrozenSetIterator<'py>;
152}
153
154impl<'py> PyFrozenSetMethods<'py> for Bound<'py, PyFrozenSet> {
155 #[inline]
156 fn len(&self) -> usize {
157 let size = cfg_select! {
158 not(any(Py_LIMITED_API, PyPy, GraalPy)) => unsafe {
160 ffi::PySet_GET_SIZE(self.as_ptr())
161 },
162 _ => unsafe { ffi::PySet_Size(self.as_ptr()) },
164 };
165 size as usize
166 }
167
168 fn contains<K>(&self, key: K) -> PyResult<bool>
169 where
170 K: IntoPyObject<'py>,
171 {
172 let py = self.py();
173 let key = key.into_pyobject_or_pyerr(py)?;
174 match unsafe { ffi::PySet_Contains(self.as_ptr(), key.as_ptr()) } {
175 1 => Ok(true),
176 0 => Ok(false),
177 _ => Err(PyErr::fetch(py)),
178 }
179 }
180
181 fn iter(&self) -> BoundFrozenSetIterator<'py> {
182 BoundFrozenSetIterator::new(self.clone())
183 }
184}
185
186impl<'py> IntoIterator for Bound<'py, PyFrozenSet> {
187 type Item = Bound<'py, PyAny>;
188 type IntoIter = BoundFrozenSetIterator<'py>;
189
190 fn into_iter(self) -> Self::IntoIter {
192 BoundFrozenSetIterator::new(self)
193 }
194}
195
196impl<'py> IntoIterator for &Bound<'py, PyFrozenSet> {
197 type Item = Bound<'py, PyAny>;
198 type IntoIter = BoundFrozenSetIterator<'py>;
199
200 fn into_iter(self) -> Self::IntoIter {
202 self.iter()
203 }
204}
205
206pub struct BoundFrozenSetIterator<'py>(Bound<'py, PyIterator>);
208
209impl<'py> BoundFrozenSetIterator<'py> {
210 pub(super) fn new(set: Bound<'py, PyFrozenSet>) -> Self {
211 Self(PyIterator::from_object(&set).expect("frozenset should always be iterable"))
212 }
213}
214
215impl<'py> Iterator for BoundFrozenSetIterator<'py> {
216 type Item = Bound<'py, super::PyAny>;
217
218 fn next(&mut self) -> Option<Self::Item> {
220 self.0
221 .next()
222 .map(|result| result.expect("frozenset iteration should be infallible"))
223 }
224
225 fn size_hint(&self) -> (usize, Option<usize>) {
226 let len = ExactSizeIterator::len(self);
227 (len, Some(len))
228 }
229
230 #[inline]
231 fn count(self) -> usize
232 where
233 Self: Sized,
234 {
235 self.len()
236 }
237}
238
239impl ExactSizeIterator for BoundFrozenSetIterator<'_> {
240 fn len(&self) -> usize {
241 self.0.size_hint().0
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::types::PyAnyMethods as _;
249
250 #[test]
251 fn test_frozenset_new_and_len() {
252 Python::attach(|py| {
253 let set = PyFrozenSet::new(py, [1]).unwrap();
254 assert_eq!(1, set.len());
255
256 let v = vec![1];
257 assert!(PyFrozenSet::new(py, &[v]).is_err());
258 });
259 }
260
261 #[test]
262 fn test_frozenset_empty() {
263 Python::attach(|py| {
264 let set = PyFrozenSet::empty(py).unwrap();
265 assert_eq!(0, set.len());
266 assert!(set.is_empty());
267 });
268 }
269
270 #[test]
271 fn test_frozenset_contains() {
272 Python::attach(|py| {
273 let set = PyFrozenSet::new(py, [1]).unwrap();
274 assert!(set.contains(1).unwrap());
275 });
276 }
277
278 #[test]
279 fn test_frozenset_iter() {
280 Python::attach(|py| {
281 let set = PyFrozenSet::new(py, [1]).unwrap();
282
283 for el in set {
284 assert_eq!(1i32, el.extract::<i32>().unwrap());
285 }
286 });
287 }
288
289 #[test]
290 fn test_frozenset_iter_bound() {
291 Python::attach(|py| {
292 let set = PyFrozenSet::new(py, [1]).unwrap();
293
294 for el in &set {
295 assert_eq!(1i32, el.extract::<i32>().unwrap());
296 }
297 });
298 }
299
300 #[test]
301 fn test_frozenset_iter_size_hint() {
302 Python::attach(|py| {
303 let set = PyFrozenSet::new(py, [1]).unwrap();
304 let mut iter = set.iter();
305
306 assert_eq!(iter.len(), 1);
308 assert_eq!(iter.size_hint(), (1, Some(1)));
309 iter.next();
310 assert_eq!(iter.len(), 0);
311 assert_eq!(iter.size_hint(), (0, Some(0)));
312 });
313 }
314
315 #[test]
316 fn test_frozenset_builder() {
317 use super::PyFrozenSetBuilder;
318
319 Python::attach(|py| {
320 let mut builder = PyFrozenSetBuilder::new(py).unwrap();
321
322 builder.add(1).unwrap();
324 builder.add(2).unwrap();
325 builder.add(2).unwrap();
326
327 let set = builder.finalize();
329
330 assert!(set.contains(1).unwrap());
331 assert!(set.contains(2).unwrap());
332 assert!(!set.contains(3).unwrap());
333 });
334 }
335
336 #[test]
337 fn test_iter_count() {
338 Python::attach(|py| {
339 let set = PyFrozenSet::new(py, vec![1, 2, 3]).unwrap();
340 assert_eq!(set.iter().count(), 3);
341 })
342 }
343}