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}