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