Skip to main content

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