1use crate::{
14 builtins::{iterable::get_iterator, BuiltIn},
15 context::StandardObjects,
16 object::{
17 internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
18 ObjectData,
19 },
20 property::{Attribute, PropertyNameKind},
21 symbol::WellKnownSymbols,
22 BoaProfiler, Context, JsResult, JsValue,
23};
24use ordered_set::OrderedSet;
25
26pub mod set_iterator;
27use set_iterator::SetIterator;
28
29use super::JsArgs;
30
31pub mod ordered_set;
32#[cfg(test)]
33mod tests;
34
35#[derive(Debug, Clone)]
36pub(crate) struct Set(OrderedSet<JsValue>);
37
38impl BuiltIn for Set {
39 const NAME: &'static str = "Set";
40
41 fn attribute() -> Attribute {
42 Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
43 }
44
45 fn init(context: &mut Context) -> (&'static str, JsValue, Attribute) {
46 let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
47
48 let get_species = FunctionBuilder::native(context, Self::get_species)
49 .name("get [Symbol.species]")
50 .constructable(false)
51 .build();
52
53 let size_getter = FunctionBuilder::native(context, Self::size_getter)
54 .constructable(false)
55 .name("get size")
56 .build();
57
58 let iterator_symbol = WellKnownSymbols::iterator();
59
60 let to_string_tag = WellKnownSymbols::to_string_tag();
61
62 let values_function = FunctionBuilder::native(context, Self::values)
63 .name("values")
64 .length(0)
65 .constructable(false)
66 .build();
67
68 let set_object = ConstructorBuilder::with_standard_object(
69 context,
70 Self::constructor,
71 context.standard_objects().set_object().clone(),
72 )
73 .name(Self::NAME)
74 .length(Self::LENGTH)
75 .static_accessor(
76 WellKnownSymbols::species(),
77 Some(get_species),
78 None,
79 Attribute::CONFIGURABLE,
80 )
81 .method(Self::add, "add", 1)
82 .method(Self::clear, "clear", 0)
83 .method(Self::delete, "delete", 1)
84 .method(Self::entries, "entries", 0)
85 .method(Self::for_each, "forEach", 1)
86 .method(Self::has, "has", 1)
87 .property(
88 "keys",
89 values_function.clone(),
90 Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
91 )
92 .accessor("size", Some(size_getter), None, Attribute::CONFIGURABLE)
93 .property(
94 "values",
95 values_function.clone(),
96 Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
97 )
98 .property(
99 iterator_symbol,
100 values_function,
101 Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
102 )
103 .property(
104 to_string_tag,
105 Self::NAME,
106 Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
107 )
108 .build();
109
110 (Self::NAME, set_object.into(), Self::attribute())
111 }
112}
113
114impl Set {
115 pub(crate) const LENGTH: usize = 0;
116
117 pub(crate) fn constructor(
119 new_target: &JsValue,
120 args: &[JsValue],
121 context: &mut Context,
122 ) -> JsResult<JsValue> {
123 if new_target.is_undefined() {
125 return context
126 .throw_type_error("calling a builtin Set constructor without new is forbidden");
127 }
128
129 let prototype =
131 get_prototype_from_constructor(new_target, StandardObjects::set_object, context)?;
132
133 let obj = context.construct_object();
134 obj.set_prototype_instance(prototype.into());
135
136 let set = JsValue::new(obj);
137 set.set_data(ObjectData::set(OrderedSet::default()));
139
140 let iterable = args.get_or_undefined(0);
141 if iterable.is_null_or_undefined() {
143 return Ok(set);
144 }
145
146 let adder = set.get_field("add", context)?;
148
149 if !adder.is_function() {
151 return context.throw_type_error("'add' of 'newTarget' is not a function");
152 }
153
154 let iterator_record = get_iterator(iterable, context)?;
156
157 let mut next = iterator_record.next(context)?;
159
160 while !next.done {
162 let next_value = next.value;
164
165 if let Err(status) = context.call(&adder, &set, &[next_value]) {
167 return iterator_record.close(Err(status), context);
168 }
169
170 next = iterator_record.next(context)?
171 }
172
173 Ok(set)
175 }
176
177 fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
188 Ok(this.clone())
190 }
191
192 pub(crate) fn add(
203 this: &JsValue,
204 args: &[JsValue],
205 context: &mut Context,
206 ) -> JsResult<JsValue> {
207 let value = args.get_or_undefined(0);
208
209 if let Some(object) = this.as_object() {
210 if let Some(set) = object.borrow_mut().as_set_mut() {
211 set.add(if value.as_number().map(|n| n == -0f64).unwrap_or(false) {
212 JsValue::Integer(0)
213 } else {
214 value.clone()
215 });
216 } else {
217 return context.throw_type_error("'this' is not a Set");
218 }
219 } else {
220 return context.throw_type_error("'this' is not a Set");
221 };
222
223 Ok(this.clone())
224 }
225
226 pub(crate) fn clear(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
237 if let Some(object) = this.as_object() {
238 if object.borrow().is_set() {
239 this.set_data(ObjectData::set(OrderedSet::new()));
240 Ok(JsValue::undefined())
241 } else {
242 context.throw_type_error("'this' is not a Set")
243 }
244 } else {
245 context.throw_type_error("'this' is not a Set")
246 }
247 }
248
249 pub(crate) fn delete(
261 this: &JsValue,
262 args: &[JsValue],
263 context: &mut Context,
264 ) -> JsResult<JsValue> {
265 let value = args.get_or_undefined(0);
266
267 let res = if let Some(object) = this.as_object() {
268 if let Some(set) = object.borrow_mut().as_set_mut() {
269 set.delete(value)
270 } else {
271 return context.throw_type_error("'this' is not a Set");
272 }
273 } else {
274 return context.throw_type_error("'this' is not a Set");
275 };
276
277 Ok(res.into())
278 }
279
280 pub(crate) fn entries(
291 this: &JsValue,
292 _: &[JsValue],
293 context: &mut Context,
294 ) -> JsResult<JsValue> {
295 if let Some(object) = this.as_object() {
296 let object = object.borrow();
297 if !object.is_set() {
298 return context.throw_type_error(
299 "Method Set.prototype.entries called on incompatible receiver",
300 );
301 }
302 } else {
303 return context
304 .throw_type_error("Method Set.prototype.entries called on incompatible receiver");
305 }
306
307 Ok(SetIterator::create_set_iterator(
308 this.clone(),
309 PropertyNameKind::KeyAndValue,
310 context,
311 ))
312 }
313
314 pub(crate) fn for_each(
325 this: &JsValue,
326 args: &[JsValue],
327 context: &mut Context,
328 ) -> JsResult<JsValue> {
329 if args.is_empty() {
330 return Err(JsValue::new("Missing argument for Set.prototype.forEach"));
331 }
332
333 let callback_arg = &args[0];
334 let this_arg = args.get_or_undefined(1);
335 let this_arg = if this_arg.is_undefined() {
337 JsValue::Object(context.global_object())
338 } else {
339 this_arg.clone()
340 };
341
342 let mut index = 0;
343
344 while index < Set::get_size(this, context)? {
345 let arguments = if let JsValue::Object(ref object) = this {
346 let object = object.borrow();
347 if let Some(set) = object.as_set_ref() {
348 set.get_index(index)
349 .map(|value| [value.clone(), value.clone(), this.clone()])
350 } else {
351 return context.throw_type_error("'this' is not a Set");
352 }
353 } else {
354 return context.throw_type_error("'this' is not a Set");
355 };
356
357 if let Some(arguments) = arguments {
358 context.call(callback_arg, &this_arg, &arguments)?;
359 }
360
361 index += 1;
362 }
363
364 Ok(JsValue::Undefined)
365 }
366
367 pub(crate) fn has(
378 this: &JsValue,
379 args: &[JsValue],
380 context: &mut Context,
381 ) -> JsResult<JsValue> {
382 let value = args.get_or_undefined(0);
383
384 if let JsValue::Object(ref object) = this {
385 let object = object.borrow();
386 if let Some(set) = object.as_set_ref() {
387 return Ok(set.contains(value).into());
388 }
389 }
390
391 Err(context.construct_type_error("'this' is not a Set"))
392 }
393
394 pub(crate) fn values(
405 this: &JsValue,
406 _: &[JsValue],
407 context: &mut Context,
408 ) -> JsResult<JsValue> {
409 if let Some(object) = this.as_object() {
410 let object = object.borrow();
411 if !object.is_set() {
412 return context.throw_type_error(
413 "Method Set.prototype.values called on incompatible receiver",
414 );
415 }
416 } else {
417 return context
418 .throw_type_error("Method Set.prototype.values called on incompatible receiver");
419 }
420
421 Ok(SetIterator::create_set_iterator(
422 this.clone(),
423 PropertyNameKind::Value,
424 context,
425 ))
426 }
427
428 fn size_getter(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
429 Set::get_size(this, context).map(JsValue::from)
430 }
431
432 fn get_size(set: &JsValue, context: &mut Context) -> JsResult<usize> {
434 if let JsValue::Object(ref object) = set {
435 let object = object.borrow();
436 if let Some(set) = object.as_set_ref() {
437 Ok(set.size())
438 } else {
439 Err(context.construct_type_error("'this' is not a Set"))
440 }
441 } else {
442 Err(context.construct_type_error("'this' is not a Set"))
443 }
444 }
445}