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    builtins::{iterable::IteratorHint, set::ordered_set::OrderedSet, Set},
8    error::JsNativeError,
9    object::{JsFunction, JsObject, JsSetIterator},
10    value::TryFromJs,
11    Context, JsResult, JsValue,
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)? {
80            JsValue::Boolean(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)? {
95            JsValue::Boolean(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    /// Utility: Creates `JsSet` from `JsObject`, if not a Set throw `TypeError`.
145    #[inline]
146    pub fn from_object(object: JsObject) -> JsResult<Self> {
147        if object.is::<OrderedSet>() {
148            Ok(Self { inner: object })
149        } else {
150            Err(JsNativeError::typ()
151                .with_message("Object is not a Set")
152                .into())
153        }
154    }
155
156    /// Utility: Creates a `JsSet` from a `<IntoIterator<Item = JsValue>` convertible object.
157    pub fn from_iter<I>(elements: I, context: &mut Context) -> Self
158    where
159        I: IntoIterator<Item = JsValue>,
160    {
161        let inner = Set::create_set_from_list(elements, context);
162        Self { inner }
163    }
164}
165
166impl From<JsSet> for JsObject {
167    #[inline]
168    fn from(o: JsSet) -> Self {
169        o.inner.clone()
170    }
171}
172
173impl From<JsSet> for JsValue {
174    #[inline]
175    fn from(o: JsSet) -> Self {
176        o.inner.clone().into()
177    }
178}
179
180impl Deref for JsSet {
181    type Target = JsObject;
182    #[inline]
183    fn deref(&self) -> &Self::Target {
184        &self.inner
185    }
186}
187
188impl TryFromJs for JsSet {
189    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
190        match value {
191            JsValue::Object(o) => Self::from_object(o.clone()),
192            _ => Err(JsNativeError::typ()
193                .with_message("value is not a Set object")
194                .into()),
195        }
196    }
197}