boa_engine/object/builtins/
jsset.rs

1//! A Rust API wrapper for the `Set` Builtin ECMAScript Object
2use std::ops::Deref;
3
4use boa_gc::{Finalize, Trace};
5
6use crate::{
7    Context, JsResult, JsValue,
8    builtins::{Set, iterable::IteratorHint, set::ordered_set::OrderedSet},
9    error::JsNativeError,
10    object::{JsFunction, JsObject, JsSetIterator},
11    value::TryFromJs,
12};
13
14/// `JsSet` provides a wrapper for Boa's implementation of the ECMAScript `Set` object.
15#[derive(Debug, Clone, Trace, Finalize)]
16pub struct JsSet {
17    inner: JsObject,
18}
19
20impl JsSet {
21    /// Create a new empty set.
22    ///
23    /// Doesn't matches JavaScript `new Set()` as it doesn't takes an iterator
24    /// similar to Rust initialization.
25    #[inline]
26    pub fn new(context: &mut Context) -> Self {
27        let inner = Set::set_create(None, context);
28
29        Self { inner }
30    }
31
32    /// Returns the size of the `Set` as an integer.
33    ///
34    /// Same as JavaScript's `set.size`.
35    #[inline]
36    pub fn size(&self) -> JsResult<usize> {
37        Set::get_size(&self.inner.clone().into())
38    }
39
40    /// Appends value to the Set object.
41    /// Returns the Set object with added value.
42    ///
43    /// Same as JavaScript's `set.add(value)`.
44    pub fn add<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue>
45    where
46        T: Into<JsValue>,
47    {
48        self.add_items(&[value.into()], context)
49    }
50
51    /// Adds slice as a single element.
52    /// Returns the Set object with added slice.
53    ///
54    /// Same as JavaScript's `set.add(["one", "two", "three"])`
55    #[inline]
56    pub fn add_items(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
57        Set::add(&self.inner.clone().into(), items, context)
58    }
59
60    /// Removes all elements from the Set object.
61    /// Returns `Undefined`.
62    ///
63    /// Same as JavaScript's `set.clear()`.
64    #[inline]
65    pub fn clear(&self, context: &mut Context) -> JsResult<JsValue> {
66        Set::clear(&self.inner.clone().into(), &[JsValue::null()], context)
67    }
68
69    /// Removes the element associated to the value.
70    /// Returns a boolean asserting whether an element was
71    /// successfully removed or not.
72    ///
73    /// Same as JavaScript's `set.delete(value)`.
74    pub fn delete<T>(&self, value: T, context: &mut Context) -> JsResult<bool>
75    where
76        T: Into<JsValue>,
77    {
78        // TODO: Make `delete` return a native `bool`
79        match Set::delete(&self.inner.clone().into(), &[value.into()], context)?.as_boolean() {
80            Some(bool) => Ok(bool),
81            _ => unreachable!("`delete` must always return a bool"),
82        }
83    }
84
85    /// Returns a boolean asserting whether an element is present
86    /// with the given value in the Set object or not.
87    ///
88    /// Same as JavaScript's `set.has(value)`.
89    pub fn has<T>(&self, value: T, context: &mut Context) -> JsResult<bool>
90    where
91        T: Into<JsValue>,
92    {
93        // TODO: Make `has` return a native `bool`
94        match Set::has(&self.inner.clone().into(), &[value.into()], context)?.as_boolean() {
95            Some(bool) => Ok(bool),
96            _ => unreachable!("`has` must always return a bool"),
97        }
98    }
99
100    /// Returns a new iterator object that yields the values
101    /// for each element in the Set object in insertion order.
102    ///
103    /// Same as JavaScript's `set.values()`.
104    #[inline]
105    pub fn values(&self, context: &mut Context) -> JsResult<JsSetIterator> {
106        let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::null()], context)?
107            .get_iterator(IteratorHint::Sync, context)?;
108
109        JsSetIterator::from_object(iterator_object.iterator().clone())
110    }
111
112    /// Alias for `Set.prototype.values()`
113    /// Returns a new iterator object that yields the values
114    /// for each element in the Set object in insertion order.
115    ///
116    /// Same as JavaScript's `set.keys()`.
117    #[inline]
118    pub fn keys(&self, context: &mut Context) -> JsResult<JsSetIterator> {
119        let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::null()], context)?
120            .get_iterator(IteratorHint::Sync, context)?;
121
122        JsSetIterator::from_object(iterator_object.iterator().clone())
123    }
124
125    /// Calls callbackFn once for each value present in the Set object,
126    /// in insertion order.
127    /// Returns `Undefined`.
128    ///
129    /// Same as JavaScript's `set.forEach(values)`.
130    #[inline]
131    pub fn for_each(
132        &self,
133        callback: JsFunction,
134        this_arg: JsValue,
135        context: &mut Context,
136    ) -> JsResult<JsValue> {
137        Set::for_each(
138            &self.inner.clone().into(),
139            &[callback.into(), this_arg],
140            context,
141        )
142    }
143
144    /// Executes the provided callback function for each key-value pair within the [`JsSet`].
145    #[inline]
146    pub fn for_each_native<F>(&self, f: F) -> JsResult<()>
147    where
148        F: FnMut(JsValue) -> JsResult<()>,
149    {
150        let this = self.inner.clone().into();
151        Set::for_each_native(&this, f)
152    }
153
154    /// Utility: Creates `JsSet` from `JsObject`, if not a Set throw `TypeError`.
155    #[inline]
156    pub fn from_object(object: JsObject) -> JsResult<Self> {
157        if object.is::<OrderedSet>() {
158            Ok(Self { inner: object })
159        } else {
160            Err(JsNativeError::typ()
161                .with_message("Object is not a Set")
162                .into())
163        }
164    }
165
166    /// Utility: Creates a `JsSet` from a `<IntoIterator<Item = JsValue>` convertible object.
167    pub fn from_iter<I>(elements: I, context: &mut Context) -> Self
168    where
169        I: IntoIterator<Item = JsValue>,
170    {
171        let inner = Set::create_set_from_list(elements, context);
172        Self { inner }
173    }
174}
175
176impl From<JsSet> for JsObject {
177    #[inline]
178    fn from(o: JsSet) -> Self {
179        o.inner.clone()
180    }
181}
182
183impl From<JsSet> for JsValue {
184    #[inline]
185    fn from(o: JsSet) -> Self {
186        o.inner.clone().into()
187    }
188}
189
190impl Deref for JsSet {
191    type Target = JsObject;
192    #[inline]
193    fn deref(&self) -> &Self::Target {
194        &self.inner
195    }
196}
197
198impl TryFromJs for JsSet {
199    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
200        if let Some(o) = value.as_object() {
201            Self::from_object(o.clone())
202        } else {
203            Err(JsNativeError::typ()
204                .with_message("value is not a Set object")
205                .into())
206        }
207    }
208}