Skip to main content

boa_engine/builtins/set/
mod.rs

1//! Boa's implementation of ECMAScript's global `Set` object.
2//!
3//! The ECMAScript `Set` class is a global object that is used in the construction of sets; which
4//! are high-level, collections of values.
5//!
6//! More information:
7//!  - [ECMAScript reference][spec]
8//!  - [MDN documentation][mdn]
9//!
10//! [spec]: https://tc39.es/ecma262/#sec-set-objects
11//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
12
13mod set_iterator;
14
15#[cfg(test)]
16mod tests;
17
18pub mod ordered_set;
19
20use self::ordered_set::OrderedSet;
21use super::iterable::IteratorHint;
22use crate::{
23    Context, JsArgs, JsResult, JsString, JsValue,
24    builtins::{
25        BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
26        canonicalize_keyed_collection_key, set::ordered_set::SetLock,
27    },
28    context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
29    error::JsNativeError,
30    js_error, js_string,
31    object::{JsObject, internal_methods::get_prototype_from_constructor},
32    property::{Attribute, PropertyNameKind},
33    realm::Realm,
34    string::StaticJsStrings,
35    symbol::JsSymbol,
36};
37use boa_engine::value::IntegerOrInfinity;
38pub(crate) use set_iterator::SetIterator;
39
40/// A record containing information about a Set-like object.
41#[derive(Debug)]
42struct SetRecord {
43    /// The size of the Set-like object.
44    size: usize,
45    /// The `has` method of the Set-like object.
46    has: JsObject,
47    /// The `keys` method of the Set-like object.
48    keys: JsObject,
49}
50
51/// Implementation of the abstract operation `GetSetRecord`.
52///
53/// More information:
54/// - [ECMAScript specification][spec]
55///
56/// [spec]: https://tc39.es/ecma262/#sec-getsetrecord
57fn get_set_record(obj: &JsValue, context: &mut Context) -> JsResult<SetRecord> {
58    // 1. If obj is not an Object, throw a TypeError exception.
59    let obj = obj.as_object().ok_or_else(|| {
60        JsNativeError::typ().with_message("Set operation called with non-object argument")
61    })?;
62
63    // 2. Let rawSize be ? Get(obj, "size").
64    let raw_size = obj.get(js_string!("size"), context)?;
65
66    // 3. Let numSize be ? ToNumber(rawSize).
67    // 4. NOTE: If rawSize is undefined, then numSize will be NaN.
68    let num_size = raw_size.to_number(context)?;
69
70    // 5. If numSize is NaN, throw a TypeError exception.
71    if num_size.is_nan() {
72        return Err(JsNativeError::typ()
73            .with_message("size is undefined")
74            .into());
75    }
76
77    // 6. Let intSize be ! ToIntegerOrInfinity(numSize).
78    let int_size = IntegerOrInfinity::from(num_size);
79    // 7. If intSize < 0, throw a RangeError exception.
80    let size: usize = match int_size {
81        IntegerOrInfinity::NegativeInfinity => {
82            return Err(JsNativeError::range()
83                .with_message("Set size must be non-negative")
84                .into());
85        }
86        IntegerOrInfinity::Integer(size) if size < 0 => {
87            return Err(JsNativeError::range()
88                .with_message("Set size must be non-negative")
89                .into());
90        }
91        IntegerOrInfinity::Integer(size) => size as usize,
92        IntegerOrInfinity::PositiveInfinity => usize::MAX,
93    };
94
95    // 8. Let has be ? Get(obj, "has").
96    let has = obj.get(js_string!("has"), context)?;
97
98    // 9. If IsCallable(has) is false, throw a TypeError exception.
99    let has = has.as_callable().ok_or_else(|| {
100        JsNativeError::typ().with_message("Set-like object must have a callable 'has' method")
101    })?;
102
103    // 10. Let keys be ? Get(obj, "keys").
104    let keys = obj.get(js_string!("keys"), context)?;
105
106    // 11. If IsCallable(keys) is false, throw a TypeError exception.
107    let keys = keys.as_callable().ok_or_else(|| {
108        JsNativeError::typ().with_message("Set-like object must have a callable 'keys' method")
109    })?;
110
111    // 12. Return a new Set Record { [[SetObject]]: obj, [[Size]]: intSize, [[Has]]: has, [[Keys]]: keys }.
112    Ok(SetRecord {
113        size,
114        has: has.clone(),
115        keys: keys.clone(),
116    })
117}
118
119#[derive(Debug, Clone)]
120pub(crate) struct Set;
121
122impl IntrinsicObject for Set {
123    fn get(intrinsics: &Intrinsics) -> JsObject {
124        Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
125    }
126    fn init(realm: &Realm) {
127        let get_species = BuiltInBuilder::callable(realm, Self::get_species)
128            .name(js_string!("get [Symbol.species]"))
129            .build();
130
131        let size_getter = BuiltInBuilder::callable(realm, Self::size_getter)
132            .name(js_string!("get size"))
133            .build();
134
135        let values_function = BuiltInBuilder::callable(realm, Self::values)
136            .name(js_string!("values"))
137            .build();
138
139        BuiltInBuilder::from_standard_constructor::<Self>(realm)
140            .static_accessor(
141                JsSymbol::species(),
142                Some(get_species),
143                None,
144                Attribute::CONFIGURABLE,
145            )
146            .method(Self::add, js_string!("add"), 1)
147            .method(Self::clear, js_string!("clear"), 0)
148            .method(Self::delete, js_string!("delete"), 1)
149            .method(Self::entries, js_string!("entries"), 0)
150            .method(Self::for_each, js_string!("forEach"), 1)
151            .method(Self::has, js_string!("has"), 1)
152            .method(Self::difference, js_string!("difference"), 1)
153            .method(Self::intersection, js_string!("intersection"), 1)
154            .method(Self::is_disjoint_from, js_string!("isDisjointFrom"), 1)
155            .method(Self::is_subset_of, js_string!("isSubsetOf"), 1)
156            .method(Self::is_superset_of, js_string!("isSupersetOf"), 1)
157            .method(
158                Self::symmetric_difference,
159                js_string!("symmetricDifference"),
160                1,
161            )
162            .method(Self::union, js_string!("union"), 1)
163            .property(
164                js_string!("keys"),
165                values_function.clone(),
166                Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
167            )
168            .accessor(
169                js_string!("size"),
170                Some(size_getter),
171                None,
172                Attribute::CONFIGURABLE,
173            )
174            .property(
175                js_string!("values"),
176                values_function.clone(),
177                Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
178            )
179            .property(
180                JsSymbol::iterator(),
181                values_function,
182                Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
183            )
184            .property(
185                JsSymbol::to_string_tag(),
186                Self::NAME,
187                Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
188            )
189            .build();
190    }
191}
192
193impl BuiltInObject for Set {
194    const NAME: JsString = StaticJsStrings::SET;
195}
196
197impl BuiltInConstructor for Set {
198    const CONSTRUCTOR_ARGUMENTS: usize = 0;
199    const PROTOTYPE_STORAGE_SLOTS: usize = 19;
200    const CONSTRUCTOR_STORAGE_SLOTS: usize = 2;
201    const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
202        StandardConstructors::set;
203
204    /// [`Set ( [ iterable ] )`][spec]
205    ///
206    /// [spec]: https://tc39.es/ecma262/#sec-set-iterable
207    fn constructor(
208        new_target: &JsValue,
209        args: &[JsValue],
210        context: &mut Context,
211    ) -> JsResult<JsValue> {
212        // 1. If NewTarget is undefined, throw a TypeError exception.
213        if new_target.is_undefined() {
214            return Err(JsNativeError::typ()
215                .with_message("calling a builtin Set constructor without new is forbidden")
216                .into());
217        }
218
219        // 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%Set.prototype%", « [[SetData]] »).
220        // 3. Set set.[[SetData]] to a new empty List.
221        let prototype =
222            get_prototype_from_constructor(new_target, StandardConstructors::set, context)?;
223        let set = JsObject::from_proto_and_data_with_shared_shape(
224            context.root_shape(),
225            prototype,
226            OrderedSet::default(),
227        )
228        .upcast();
229
230        // 4. If iterable is either undefined or null, return set.
231        let iterable = args.get_or_undefined(0);
232        if iterable.is_null_or_undefined() {
233            return Ok(set.into());
234        }
235
236        // 5. Let adder be ? Get(set, "add").
237        let adder = set.get(js_string!("add"), context)?;
238
239        // 6. If IsCallable(adder) is false, throw a TypeError exception.
240        let adder = adder.as_callable().ok_or_else(|| {
241            JsNativeError::typ().with_message("'add' of 'newTarget' is not a function")
242        })?;
243
244        // 7. Let iteratorRecord be ? GetIterator(iterable, sync).
245        let mut iterator_record = iterable.clone().get_iterator(IteratorHint::Sync, context)?;
246
247        // 8. Repeat,
248        //     a. Let next be ? IteratorStepValue(iteratorRecord).
249        while let Some(next) = iterator_record.step_value(context)? {
250            // c. Let status be Completion(Call(adder, set, « next »)).
251            if let Err(status) = adder.call(&set.clone().into(), &[next], context) {
252                // d. IfAbruptCloseIterator(status, iteratorRecord).
253                return iterator_record.close(Err(status), context);
254            }
255        }
256
257        //     b. If next is done, return set.
258        Ok(set.into())
259    }
260}
261
262impl Set {
263    /// `get Set [ @@species ]`
264    ///
265    /// The Set[Symbol.species] accessor property returns the Set constructor.
266    ///
267    /// More information:
268    ///  - [ECMAScript reference][spec]
269    ///  - [MDN documentation][mdn]
270    ///
271    /// [spec]: https://tc39.es/ecma262/#sec-get-set-@@species
272    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/@@species
273    #[allow(clippy::unnecessary_wraps)]
274    fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
275        // 1. Return the this value.
276        Ok(this.clone())
277    }
278
279    /// `Set.prototype.add( value )`
280    ///
281    /// This method adds an entry with value into the set. Returns the set object
282    ///
283    /// More information:
284    ///  - [ECMAScript reference][spec]
285    ///  - [MDN documentation][mdn]
286    ///
287    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.add
288    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add
289    pub(crate) fn add(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
290        // 1. Let S be the this value.
291        // 2. Perform ? RequireInternalSlot(S, [[SetData]]).
292        let set = this
293            .as_object()
294            .and_then(|o| o.downcast::<OrderedSet>().ok())
295            .ok_or_else(|| {
296                js_error!(TypeError: "method `Set.prototype.add` called on incompatible receiver")
297            })?;
298
299        // 3. Set value to CanonicalizeKeyedCollectionKey(value).
300        let value = canonicalize_keyed_collection_key(args.get_or_undefined(0).clone());
301
302        // 4. For each element e of S.[[SetData]], do
303        //   a. If e is not empty and SameValueZero(e, value) is true, then
304        //     i. Return S.
305        // 5. Append value to S.[[SetData]].
306        set.borrow_mut().data_mut().add(value.clone());
307
308        Ok(this.clone())
309        // 6. Return S.
310    }
311
312    /// `Set.prototype.clear( )`
313    ///
314    /// This method removes all entries from the set.
315    ///
316    /// More information:
317    ///  - [ECMAScript reference][spec]
318    ///  - [MDN documentation][mdn]
319    ///
320    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.clear
321    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear
322    pub(crate) fn clear(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
323        // 1. Let S be the this value.
324        // 2. Perform ? RequireInternalSlot(S, [[SetData]]).
325        // 3. For each element e of S.[[SetData]], do
326        //        a. Replace the element of S.[[SetData]] whose value is e with an element whose value is empty.
327        this.as_object()
328            .and_then(|o| o.downcast::<OrderedSet>().ok())
329            .ok_or_else(|| {
330                js_error!(
331                    TypeError: "method `Set.prototype.clear` called on incompatible receiver"
332                )
333            })?
334            .borrow_mut()
335            .data_mut()
336            .clear();
337
338        // 4. Return undefined.
339        Ok(JsValue::undefined())
340    }
341
342    /// `Set.prototype.delete( value )`
343    ///
344    /// This method removes the entry for the given value if it exists.
345    /// Returns true if there was an element, false otherwise.
346    ///
347    /// More information:
348    ///  - [ECMAScript reference][spec]
349    ///  - [MDN documentation][mdn]
350    ///
351    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.delete
352    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete
353    pub(crate) fn delete(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
354        // 1. Let S be the this value.
355        // 2. Perform ? RequireInternalSlot(S, [[SetData]]).
356        let this = this
357            .as_object()
358            .and_then(|o| o.downcast::<OrderedSet>().ok())
359            .ok_or_else(|| {
360                js_error!(
361                    TypeError: "method `Set.prototype.delete` called on incompatible receiver"
362                )
363            })?;
364
365        let value = canonicalize_keyed_collection_key(args.get_or_undefined(0).clone());
366
367        // 3. For each element e of S.[[SetData]], do
368        // a. If e is not empty and SameValueZero(e, value) is true, then
369        // i. Replace the element of S.[[SetData]] whose value is e with an element whose value is empty.
370        // ii. Return true.
371        // 4. Return false.
372        Ok(this.borrow_mut().data_mut().delete(&value).into())
373    }
374
375    /// `Set.prototype.entries( )`
376    ///
377    /// This method returns an iterator over the entries of the set
378    ///
379    /// More information:
380    ///  - [ECMAScript reference][spec]
381    ///  - [MDN documentation][mdn]
382    ///
383    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.entries
384    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries
385    pub(crate) fn entries(
386        this: &JsValue,
387        _: &[JsValue],
388        context: &mut Context,
389    ) -> JsResult<JsValue> {
390        let this = this
391            .as_object()
392            .and_then(|o| o.downcast::<OrderedSet>().ok())
393            .ok_or_else(|| {
394                js_error!(
395                    TypeError: "method `Set.prototype.entries` called on incompatible receiver"
396                )
397            })?;
398
399        Ok(SetIterator::create_set_iterator(
400            this.clone(),
401            PropertyNameKind::KeyAndValue,
402            context,
403        ))
404    }
405
406    /// `Set.prototype.forEach( callbackFn [ , thisArg ] )`
407    ///
408    /// This method executes the provided callback function for each value in the set
409    ///
410    /// More information:
411    ///  - [ECMAScript reference][spec]
412    ///  - [MDN documentation][mdn]
413    ///
414    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.foreach
415    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/foreach
416    pub(crate) fn for_each(
417        this: &JsValue,
418        args: &[JsValue],
419        context: &mut Context,
420    ) -> JsResult<JsValue> {
421        // 1. Let S be the this value.
422        // 2. Perform ? RequireInternalSlot(S, [[SetData]]).
423
424        let obj = this
425            .as_object()
426            .and_then(|o| o.downcast::<OrderedSet>().ok())
427            .ok_or_else(|| {
428                js_error!(
429                    TypeError: "method `Set.prototype.forEach` called on incompatible receiver"
430                )
431            })?;
432
433        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
434        let Some(callback_fn) = args.get_or_undefined(0).as_callable() else {
435            return Err(js_error!(
436                TypeError:
437                    "Method Set.prototype.forEach called with non-callable callback function"
438            ));
439        };
440
441        let _lock = SetLock::new(&obj);
442
443        // 4. Let entries be S.[[SetData]].
444        // 5. Let numEntries be the number of elements in entries.
445        // 6. Let index be 0.
446        let mut index = 0;
447
448        // 7. Repeat, while index < numEntries,
449        while index < obj.borrow().data().full_len() {
450            // a. Let e be entries[index].
451            let e = obj.borrow().data().get_index(index).cloned();
452
453            // b. Set index to index + 1.
454            index += 1;
455
456            // c. If e is not empty, then
457            if let Some(e) = e {
458                // i. Perform ? Call(callbackfn, thisArg, « e, e, S »).
459                // ii. NOTE: The number of elements in entries may have increased during execution of callbackfn.
460                // iii. Set numEntries to the number of elements in entries.
461                callback_fn.call(
462                    args.get_or_undefined(1),
463                    &[e.clone(), e.clone(), this.clone()],
464                    context,
465                )?;
466            }
467        }
468
469        // 8. Return undefined.
470        Ok(JsValue::undefined())
471    }
472
473    /// Call `f` for each `(value)` in the `Set`.
474    ///
475    /// Can not be used in [`Self::for_each`] because it will be running an
476    /// incorrect order for next steps of the algo:
477    /// ```txt
478    /// 2. Perform ? RequireInternalSlot(M, [[SetData]]).
479    /// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
480    /// ```
481    pub(crate) fn for_each_native<F>(this: &JsValue, mut f: F) -> JsResult<()>
482    where
483        F: FnMut(JsValue) -> JsResult<()>,
484    {
485        // See `Self::for_each` for comments on the algo.
486
487        let set = this.as_object();
488        let set = set
489            .and_then(|obj| obj.downcast::<OrderedSet>().ok())
490            .ok_or_else(|| {
491                js_error!(
492                    TypeError: "method `Set.prototype.forEach` called on incompatible receiver"
493                )
494            })?;
495        let _lock = SetLock::new(&set);
496
497        let mut index = 0;
498        loop {
499            let v = {
500                let set = set.borrow();
501                let set = set.data();
502
503                if index < set.full_len() {
504                    if let Some(k) = set.get_index(index) {
505                        k.clone()
506                    } else {
507                        continue;
508                    }
509                } else {
510                    return Ok(());
511                }
512            };
513
514            f(v)?;
515            index += 1;
516        }
517    }
518
519    /// `Map.prototype.has( key )`
520    ///
521    /// This method checks if the map contains an entry with the given key.
522    ///
523    /// More information:
524    ///  - [ECMAScript reference][spec]
525    ///  - [MDN documentation][mdn]
526    ///
527    /// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has
528    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has
529    pub(crate) fn has(this: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
530        // 1. Let M be the this value.
531        // 2. Perform ? RequireInternalSlot(S, [[SetData]]).
532        let this = this
533            .as_object()
534            .and_then(|o| o.downcast::<OrderedSet>().ok())
535            .ok_or_else(|| {
536                js_error!(
537                    TypeError: "method `Set.prototype.has` called on incompatible receiver"
538                )
539            })?;
540
541        // 3. Set value to CanonicalizeKeyedCollectionKey(key).
542        let value = args.get_or_undefined(0);
543        let value = canonicalize_keyed_collection_key(value.clone());
544
545        // 4. For each element e of S.[[SetData]], do
546        //    a. If e is not empty and SameValue(e, value) is true, return true.
547        // 5. Return false.
548        Ok(this.borrow().data().contains(&value).into())
549    }
550
551    /// `Set.prototype.values( )`
552    ///
553    /// This method returns an iterator over the values of the set
554    ///
555    /// More information:
556    ///  - [ECMAScript reference][spec]
557    ///  - [MDN documentation][mdn]
558    ///
559    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.values
560    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values
561    pub(crate) fn values(
562        this: &JsValue,
563        _: &[JsValue],
564        context: &mut Context,
565    ) -> JsResult<JsValue> {
566        let this = this
567            .as_object()
568            .and_then(|o| o.downcast::<OrderedSet>().ok())
569            .ok_or_else(|| js_error!(TypeError: "method `Set.prototype.values` called on incompatible receiver"))?;
570
571        Ok(SetIterator::create_set_iterator(
572            this,
573            PropertyNameKind::Value,
574            context,
575        ))
576    }
577
578    /// `Set.prototype.isDisjointFrom ( other )`
579    ///
580    /// This method checks whether the current Set and the given iterable `other` have no elements in common.
581    /// It returns `true` if the two Sets are disjoint (i.e., they have no overlapping elements),
582    /// and `false` otherwise.
583    ///
584    /// More information:
585    /// - [ECMAScript reference][spec]
586    /// - [MDN documentation][mdn]
587    ///
588    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.isdisjointfrom
589    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/isDisjointFrom
590    pub(crate) fn is_disjoint_from(
591        this: &JsValue,
592        args: &[JsValue],
593        context: &mut Context,
594    ) -> JsResult<JsValue> {
595        // 1. Let O be the this value.
596        // 2. Perform ? RequireInternalSlot(O, [[SetData]]).
597
598        let this = this
599            .as_object()
600            .and_then(|o| o.downcast::<OrderedSet>().ok())
601            .ok_or_else(|| js_error!(TypeError: "method `Set.prototype.isDisjointFrom` called on incompatible receiver"))?;
602
603        // 3. Let otherRec be ? GetSetRecord(other).
604        let other = args.get_or_undefined(0);
605        let other_rec = get_set_record(other, context)?;
606
607        // 4. If SetDataSize(O.[[SetData]]) ≤ otherRec.[[Size]], then
608        if this.borrow().data().len() <= other_rec.size {
609            // a. Let thisSize be the number of elements in O.[[SetData]].
610            let mut this_size = this.borrow().data().full_len();
611            // b. Let index be 0.
612            let mut index = 0;
613            // c. Repeat, while index < thisSize,
614            while index < this_size {
615                // i. Let e be O.[[SetData]][index].
616                let e = this.borrow().data().get_index(index).cloned();
617
618                // ii. Set index to index + 1.
619                index += 1;
620
621                // iii. If e is not empty, then
622                if let Some(e) = e {
623                    // 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
624                    let in_other = other_rec.has.call(other, &[e], context)?.to_boolean();
625
626                    // 2. If inOther is true, return false.
627                    if in_other {
628                        return Ok(JsValue::from(false));
629                    }
630
631                    // 3. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]].
632                    // 4. Set thisSize to the number of elements in O.[[SetData]].
633                    this_size = this.borrow().data().full_len();
634                }
635            }
636        } else {
637            // 5. Else,
638            //    a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
639            let mut keys_iter = other.get_iterator_from_method(&other_rec.keys, context)?;
640
641            //    b. Let next be not-started.
642            //    c. Repeat, while next is not done,
643            //       i. Set next to ? IteratorStepValue(keysIter).
644            while let Some(next) = keys_iter.step_value(context)? {
645                //   ii. If next is not done, then
646                //       1. If SetDataHas(O.[[SetData]], next) is true, then
647                let next = canonicalize_keyed_collection_key(next);
648
649                if this.borrow().data().contains(&next) {
650                    //      a. Perform ? IteratorClose(keysIter, NormalCompletion(unused)).
651                    keys_iter.close(Ok(JsValue::undefined()), context)?;
652
653                    //      b. Return false.
654                    return Ok(JsValue::from(false));
655                }
656            }
657        }
658        // 6. Return true.
659        Ok(JsValue::from(true))
660    }
661
662    /// `Set.prototype.isSubsetOf ( other )`
663    ///
664    /// This method checks whether the current Set is a subset of the given iterable `other`.
665    /// It returns `true` if all elements of the current Set are present in the given iterable,
666    /// and `false` otherwise.
667    ///
668    /// More information:
669    /// - [ECMAScript reference][spec]
670    /// - [MDN documentation][mdn]
671    ///
672    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.issubsetof
673    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/isSubsetOf
674    pub(crate) fn is_subset_of(
675        this: &JsValue,
676        args: &[JsValue],
677        context: &mut Context,
678    ) -> JsResult<JsValue> {
679        // 1. Let O be the this value.
680        // 2. Perform ? RequireInternalSlot(O, [[SetData]]).
681
682        let this = this
683            .as_object()
684            .and_then(|o| o.downcast::<OrderedSet>().ok())
685            .ok_or_else(|| js_error!(TypeError: "method `Set.prototype.isSubsetOf` called on incompatible receiver"))?;
686
687        // 3. Let otherRec be ? GetSetRecord(other).
688        let other = args.get_or_undefined(0);
689        let other_rec = get_set_record(other, context)?;
690        // 4. If SetDataSize(O.[[SetData]]) > otherRec.[[Size]], return false.
691        if this.borrow().data().len() > other_rec.size {
692            return Ok(JsValue::from(false));
693        }
694
695        // 5. Let thisSize be the number of elements in O.[[SetData]].
696        let mut this_size = this.borrow().data().full_len();
697        // 6. Let index be 0.
698        let mut index = 0;
699
700        // 7. Repeat, while index < thisSize,
701        while index < this_size {
702            // a. Let e be O.[[SetData]][index].
703            let e = this.borrow().data().get_index(index).cloned();
704
705            // b. Set index to index + 1.
706            index += 1;
707
708            // c. If e is not empty, then
709            if let Some(e) = e {
710                // i. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
711                let in_other = other_rec.has.call(other, &[e], context)?.to_boolean();
712
713                // ii. If inOther is false, return false.
714                if !in_other {
715                    return Ok(JsValue::from(false));
716                }
717
718                // iii. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]].
719                // iv. Set thisSize to the number of elements in O.[[SetData]].
720                this_size = this.borrow().data().full_len();
721            }
722        }
723
724        // 8. Return true.
725        Ok(JsValue::from(true))
726    }
727
728    /// `Set.prototype.isSupersetOf ( other )`
729    ///
730    /// This method checks whether the current Set is a superset of the given iterable `other`.
731    /// It returns `true` if the current Set contains all elements from the given iterable,
732    /// and `false` otherwise.
733    ///
734    /// More information:
735    /// - [ECMAScript reference][spec]
736    /// - [MDN documentation][mdn]
737    ///
738    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.issupersetof
739    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/isSupersetOf
740    pub(crate) fn is_superset_of(
741        this: &JsValue,
742        args: &[JsValue],
743        context: &mut Context,
744    ) -> JsResult<JsValue> {
745        // 1. Let O be the this value.
746        // 2. Perform ? RequireInternalSlot(O, [[SetData]]).
747
748        let this = this
749            .as_object()
750            .and_then(|o| o.downcast::<OrderedSet>().ok())
751            .ok_or_else(|| js_error!(TypeError: "method `Set.prototype.isSupersetOf` called on incompatible receiver"))?;
752
753        // 3. Let otherRec be ? GetSetRecord(other).
754        let other = args.get_or_undefined(0);
755        let other_rec = get_set_record(other, context)?;
756
757        // 4. If SetDataSize(O.[[SetData]]) < otherRec.[[Size]], return false.
758        if this.borrow().data().len() < other_rec.size {
759            return Ok(JsValue::from(false));
760        }
761
762        // 5. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
763        let mut keys_iter = other.get_iterator_from_method(&other_rec.keys, context)?;
764
765        // 6. Let next be not-started.
766        // 7. Repeat, while next is not done,
767        //    a. Set next to ? IteratorStepValue(keysIter).
768        while let Some(next) = keys_iter.step_value(context)? {
769            //  b. If next is not done, then
770            //     i. If SetDataHas(O.[[SetData]], next) is false, then
771            let next = canonicalize_keyed_collection_key(next);
772            if !this.borrow().data().contains(&next) {
773                // 1. Perform ? IteratorClose(keysIter, NormalCompletion(unused)).
774                keys_iter.close(Ok(JsValue::undefined()), context)?;
775                // 2. Return false.
776                return Ok(JsValue::from(false));
777            }
778        }
779
780        // 8. Return true.
781        Ok(JsValue::from(true))
782    }
783
784    /// ` Set.prototype.symmetricDifference(other)`
785    ///
786    /// Returns a new set containing the symmetric difference between the current set (`this`)
787    /// and the provided set (`other`)
788    ///
789    /// More information:
790    /// - [ECMAScript reference][spec]
791    /// - [MDN documentation][mdn]
792    ///
793    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.symmetricdifference
794    /// [mdn]: https://developer.mozilla.org/en-USSet/docs/Web/JavaScript/Reference/Global_Objects/Set/symmetricDifference
795    pub(crate) fn symmetric_difference(
796        this: &JsValue,
797        args: &[JsValue],
798        context: &mut Context,
799    ) -> JsResult<JsValue> {
800        // 1. Let O be the this value.
801        // 2. Perform ? RequireInternalSlot(O, [[SetData]]).
802        let this = this
803            .as_object()
804            .and_then(|o| o.downcast::<OrderedSet>().ok())
805            .ok_or_else(|| js_error!(TypeError: "method `Set.prototype.symmetricDifference` called on incompatible receiver"))?;
806
807        // 3. Let otherRec be ? GetSetRecord(other).
808        let other = args.get_or_undefined(0);
809        let other_rec = get_set_record(other, context)?;
810
811        // 4. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
812        let mut keys_iter = other.get_iterator_from_method(&other_rec.keys, context)?;
813
814        // 5. Let resultSetData be a copy of O.[[SetData]].
815        let mut result_set = this.borrow().data().clone();
816
817        // 6. Let next be not-started.
818        // 7. Repeat, while next is not done,
819        while let Some(next) = keys_iter.step_value(context)? {
820            //  a. Set next to ? IteratorStepValue(keysIter).
821            //  b. If next is not done, then
822            //    i. Set next to CanonicalizeKeyedCollectionKey(next).
823            let next = canonicalize_keyed_collection_key(next);
824
825            //    ii. Let resultIndex be SetDataIndex(resultSetData, next).
826            //    iii. If resultIndex is not-found, let alreadyInResult be false. Otherwise let alreadyInResult be true.
827
828            //    iv. If SetDataHas(O.[[SetData]], next) is true, then
829            //  1. If alreadyInResult is true, set resultSetData[resultIndex] to empty.
830            if this.borrow().data().contains(&next) {
831                result_set.delete(&next);
832            }
833            //    v. Else,
834            //      1. If alreadyInResult is false, append next to resultSetData.
835            else {
836                result_set.add(next);
837            }
838        }
839
840        //     8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
841        //     9. Set result.[[SetData]] to resultSetData.
842        //     10. Return result.
843        Ok(JsObject::from_proto_and_data_with_shared_shape(
844            context.root_shape(),
845            context.intrinsics().constructors().set().prototype(),
846            result_set,
847        )
848        .into())
849    }
850
851    /// `Set.prototype.union ( other )`
852    ///
853    /// Returns a new set containing the union of the elements in the current set (`this`)
854    /// and the set provided as the argument (`other`).
855    ///
856    /// More information:
857    /// - [ECMAScript reference][spec]
858    /// - [MDN documentation][mdn]
859    ///
860    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.union
861    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/union
862    pub(crate) fn union(
863        this: &JsValue,
864        args: &[JsValue],
865        context: &mut Context,
866    ) -> JsResult<JsValue> {
867        // 1. Let O be the this value.
868        // 2. Perform ? RequireInternalSlot(O, [[SetData]]).
869
870        let this = this
871            .as_object()
872            .and_then(|o| o.downcast::<OrderedSet>().ok())
873            .ok_or_else(|| js_error!(TypeError: "method `Set.prototype.union` called on incompatible receiver"))?;
874
875        // 3. Let otherRec be ? GetSetRecord(other).
876        let other = args.get_or_undefined(0);
877        let other_rec = get_set_record(other, context)?;
878
879        // 4. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
880        let mut keys_iter = other.get_iterator_from_method(&other_rec.keys, context)?;
881
882        // 5. Let resultSetData be a copy of O.[[SetData]].
883        let mut result_set = this.borrow().data().clone();
884
885        // 6. Let next be not-started.
886        // 7. Repeat, while next is not done,
887        //        a. Set next to ? IteratorStepValue(keysIter).
888        //        b. If next is not done, then
889        //               i. Set next to CanonicalizeKeyedCollectionKey(next).
890        //               ii. If SetDataHas(resultSetData, next) is false, then
891        //                       1. Append next to resultSetData.
892        while let Some(next) = keys_iter.step_value(context)? {
893            result_set.add(canonicalize_keyed_collection_key(next));
894        }
895
896        // 8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
897        // 9. Set result.[[SetData]] to resultSetData.
898        // 10. Return result.
899        Ok(JsObject::from_proto_and_data_with_shared_shape(
900            context.root_shape(),
901            context.intrinsics().constructors().set().prototype(),
902            result_set,
903        )
904        .into())
905    }
906
907    /// `Set.prototype.intersection ( other )`
908    ///
909    /// This method returns a new Set containing all elements that are present in both
910    /// the current Set and the given iterable `other`.
911    ///
912    /// It effectively computes the intersection of the two Sets.
913    ///
914    /// More information:
915    /// - [ECMAScript reference][spec]
916    /// - [MDN documentation][mdn]
917    ///
918    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.intersection
919    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/intersection
920    pub(crate) fn intersection(
921        this: &JsValue,
922        args: &[JsValue],
923        context: &mut Context,
924    ) -> JsResult<JsValue> {
925        // 1. Let S be the this value.
926        // 2. Perform ? RequireInternalSlot(O, [[SetData]]).
927        let this = this
928            .as_object()
929            .and_then(|o| o.downcast::<OrderedSet>().ok())
930            .ok_or_else(|| js_error!(TypeError: "method `Set.prototype.intersection` called on incompatible receiver"))?;
931
932        // 3. Let otherRec be ? GetSetRecord(other).
933        let other = args.get_or_undefined(0);
934        let other_rec = get_set_record(other, context)?;
935
936        // 4. Let resultSetData be a new empty List.
937        let mut result_set = OrderedSet::new();
938
939        // 5. If SetDataSize(O.[[SetData]]) ≤ otherRec.[[Size]], then
940        if this.borrow().data().len() <= other_rec.size {
941            // a. Let thisSize be the number of elements in O.[[SetData]].
942            let mut this_size = this.borrow().data().full_len();
943            // b. Let index be 0.
944            let mut index = 0;
945            // c. Repeat, while index < thisSize,
946            while index < this_size {
947                // i. Let e be O.[[SetData]][index].
948                let e = this.borrow().data().get_index(index).cloned();
949                // ii. Set index to index + 1.
950                index += 1;
951
952                // iii. If e is not empty, then
953                let Some(e) = e else {
954                    continue;
955                };
956
957                //      1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
958                let in_other = other_rec
959                    .has
960                    .call(other, std::slice::from_ref(&e), context)?;
961                //      2. If inOther is true, then
962                //         a. NOTE: It is possible for earlier calls to otherRec.[[Has]] to remove and re-add an element of O.[[SetData]], which can cause the same element to be visited twice during this iteration.
963                if in_other.to_boolean() {
964                    //     b. If SetDataHas(resultSetData, e) is false, then
965                    //        i. Append e to resultSetData.
966                    result_set.add(e);
967                    //  3. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]].
968                    //  4. Set thisSize to the number of elements in O.[[SetData]].
969                    this_size = this.borrow().data().full_len();
970                }
971            }
972
973        // 6. Else,
974        } else {
975            // a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
976            let mut keys_iter = other.get_iterator_from_method(&other_rec.keys, context)?;
977            // b. Let next be not-started.
978            // c. Repeat, while next is not done,
979            while let Some(next) = keys_iter.step_value(context)? {
980                // i. Set next to ? IteratorStepValue(keysIter).
981                // ii. If next is not done, then
982                //     1. Set next to CanonicalizeKeyedCollectionKey(next).
983                let next = canonicalize_keyed_collection_key(next);
984                //     2. Let inThis be SetDataHas(O.[[SetData]], next).
985                //     3. If inThis is true, then
986                if this.borrow().data().contains(&next) {
987                    //        a. NOTE: Because other is an arbitrary object, it is possible for its "keys" iterator to produce the same value more than once.
988                    //        b. If SetDataHas(resultSetData, next) is false, then
989                    //           i. Append next to resultSetData.
990                    result_set.add(next);
991                }
992            }
993        }
994
995        // 7. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
996        // 8. Set result.[[SetData]] to resultSetData.
997        // 9. Return result.
998        Ok(JsObject::from_proto_and_data_with_shared_shape(
999            context.root_shape(),
1000            context.intrinsics().constructors().set().prototype(),
1001            result_set,
1002        )
1003        .into())
1004    }
1005
1006    /// ` Set.prototype.difference ( other ) `
1007    ///
1008    /// This method returns a new Set containing all elements that are in the current Set
1009    /// but not in the given iterable `other`.
1010    ///
1011    /// More information:
1012    ///  - [ECMAScript reference][spec]
1013    ///  - [MDN documentation][mdn]
1014    ///
1015    /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.difference
1016    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/difference
1017    pub(crate) fn difference(
1018        this: &JsValue,
1019        args: &[JsValue],
1020        context: &mut Context,
1021    ) -> JsResult<JsValue> {
1022        // 1. Let O be the this value.
1023        // 2. Perform ? RequireInternalSlot(O, [[SetData]]).
1024        let this = this
1025            .as_object()
1026            .and_then(|o| o.downcast::<OrderedSet>().ok())
1027            .ok_or_else(|| {
1028                js_error!(
1029                    TypeError: "method `Set.prototype.difference` called on incompatible receiver"
1030                )
1031            })?;
1032
1033        // 3. Let otherRec be ? GetSetRecord(other).
1034        let other = args.get_or_undefined(0);
1035        let other_rec = get_set_record(other, context)?;
1036
1037        // 4. Let resultSetData be a copy of O.[[SetData]].
1038        let mut result_set = this.borrow().data().clone();
1039
1040        // 5. If SetDataSize(O.[[SetData]]) ≤ otherRec.[[Size]], then
1041        if result_set.len() <= other_rec.size {
1042            // a. Let thisSize be the number of elements in O.[[SetData]].
1043            let this_size = this.borrow().data().full_len();
1044            // b. Let index be 0.
1045            let mut index = 0;
1046
1047            //  c. Repeat, while index < thisSize,
1048            while index < this_size {
1049                // i. Let e be resultSetData[index].
1050                let e = result_set.get_index(index).cloned();
1051
1052                // ii. If e is not empty, then
1053                if let Some(e) = e {
1054                    // 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
1055                    let in_other = other_rec
1056                        .has
1057                        .call(other, std::slice::from_ref(&e), context)?
1058                        .to_boolean();
1059                    // 2. If inOther is true, then
1060                    if in_other {
1061                        // a. Set resultSetData[index] to empty.
1062                        result_set.delete(&e);
1063
1064                        // No need to increment the index, after deletion we're
1065                        // in the correct next position.
1066                        continue;
1067                    }
1068                }
1069
1070                // iii. Set index to index + 1.
1071                index += 1;
1072            }
1073        }
1074        // 6. Else,
1075        else {
1076            // a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
1077            let mut keys_iter = other.get_iterator_from_method(&other_rec.keys, context)?;
1078            // b. Let next be not-started.
1079            // c. Repeat, while next is not done,
1080            //     i. Set next to ? IteratorStepValue(keysIter).
1081            while let Some(next) = keys_iter.step_value(context)? {
1082                // ii. If next is not done, then
1083                //     1. Set next to CanonicalizeKeyedCollectionKey(next).
1084                let next = canonicalize_keyed_collection_key(next);
1085                //     2. Let valueIndex be SetDataIndex(resultSetData, next).
1086                //     3. If valueIndex is not not-found, then
1087                //        a. Set resultSetData[valueIndex] to empty.
1088                result_set.delete(&next);
1089            }
1090        }
1091
1092        // 7. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
1093        // 8. Set result.[[SetData]] to resultSetData.
1094        // 9. Return result.
1095        Ok(JsObject::from_proto_and_data_with_shared_shape(
1096            context.root_shape(),
1097            context.intrinsics().constructors().set().prototype(),
1098            result_set,
1099        )
1100        .into())
1101    }
1102
1103    fn size_getter(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
1104        Ok(this
1105            .as_object()
1106            .and_then(|o| o.downcast_ref::<OrderedSet>().map(|o| o.len()))
1107            .ok_or_else(|| js_error!(TypeError: "method `get Set.prototype.size` called on incompatible receiver"))?.into())
1108    }
1109}