boa_engine/builtins/weak_set/mod.rs
1//! Boa's implementation of ECMAScript's `WeakSet` builtin object.
2//!
3//! More information:
4//! - [ECMAScript reference][spec]
5//! - [MDN documentation][mdn]
6//!
7//! [spec]: https://tc39.es/ecma262/#sec-weakset-objects
8//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet
9
10use crate::{
11 Context, JsArgs, JsResult, JsString, JsValue,
12 builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject},
13 context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
14 js_error, js_string,
15 object::{ErasedVTableObject, JsObject, internal_methods::get_prototype_from_constructor},
16 property::Attribute,
17 realm::Realm,
18 string::StaticJsStrings,
19 symbol::JsSymbol,
20};
21use boa_gc::{Finalize, Trace};
22
23use super::iterable::IteratorHint;
24
25pub(crate) type NativeWeakSet = boa_gc::WeakMap<ErasedVTableObject, ()>;
26
27#[derive(Debug, Trace, Finalize)]
28pub(crate) struct WeakSet;
29
30impl IntrinsicObject for WeakSet {
31 fn get(intrinsics: &Intrinsics) -> JsObject {
32 Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
33 }
34
35 fn init(realm: &Realm) {
36 BuiltInBuilder::from_standard_constructor::<Self>(realm)
37 .property(
38 JsSymbol::to_string_tag(),
39 Self::NAME,
40 Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
41 )
42 .method(Self::add, js_string!("add"), 1)
43 .method(Self::delete, js_string!("delete"), 1)
44 .method(Self::has, js_string!("has"), 1)
45 .build();
46 }
47}
48
49impl BuiltInObject for WeakSet {
50 const NAME: JsString = StaticJsStrings::WEAK_SET;
51
52 const ATTRIBUTE: Attribute = Attribute::WRITABLE.union(Attribute::CONFIGURABLE);
53}
54
55impl BuiltInConstructor for WeakSet {
56 /// The amount of arguments the `WeakSet` constructor takes.
57 const CONSTRUCTOR_ARGUMENTS: usize = 0;
58 const PROTOTYPE_STORAGE_SLOTS: usize = 4;
59 const CONSTRUCTOR_STORAGE_SLOTS: usize = 0;
60
61 const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
62 StandardConstructors::weak_set;
63
64 /// `WeakSet ( [ iterable ] )`
65 ///
66 /// More information:
67 /// - [ECMAScript reference][spec]
68 /// - [MDN documentation][mdn]
69 ///
70 /// [spec]: https://tc39.es/ecma262/#sec-weakset-iterable
71 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet/WeakSet
72 fn constructor(
73 new_target: &JsValue,
74 args: &[JsValue],
75 context: &mut Context,
76 ) -> JsResult<JsValue> {
77 // 1. If NewTarget is undefined, throw a TypeError exception.
78 if new_target.is_undefined() {
79 return Err(js_error!(TypeError: "WeakSet: cannot call constructor without `new`"));
80 }
81
82 // 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakSet.prototype%", « [[WeakSetData]] »).
83 // 3. Set set.[[WeakSetData]] to a new empty List.
84 let prototype =
85 get_prototype_from_constructor(new_target, StandardConstructors::weak_set, context)?;
86 let weak_set = JsObject::from_proto_and_data_with_shared_shape(
87 context.root_shape(),
88 prototype,
89 NativeWeakSet::new(),
90 )
91 .upcast();
92
93 // 4. If iterable is either undefined or null, return set.
94 let iterable = args.get_or_undefined(0);
95 if iterable.is_null_or_undefined() {
96 return Ok(weak_set.into());
97 }
98
99 // 5. Let adder be ? Get(set, "add").
100 let adder = weak_set.get(js_string!("add"), context)?;
101
102 // 6. If IsCallable(adder) is false, throw a TypeError exception.
103 let adder = adder
104 .as_callable()
105 .ok_or_else(|| js_error!(TypeError: "WeakSet: 'add' is not a function"))?;
106
107 // 7. Let iteratorRecord be ? GetIterator(iterable, sync).
108 let mut iterator_record = iterable.clone().get_iterator(IteratorHint::Sync, context)?;
109
110 // 8. Repeat,
111 // a. Let next be ? IteratorStepValue(iteratorRecord).
112 while let Some(next) = iterator_record.step_value(context)? {
113 // c. Let status be Completion(Call(adder, set, « next »)).
114 if let Err(status) = adder.call(&weak_set.clone().into(), &[next], context) {
115 // d. IfAbruptCloseIterator(status, iteratorRecord).
116 return iterator_record.close(Err(status), context);
117 }
118 }
119
120 // b. If next is done, return set.
121 Ok(weak_set.into())
122 }
123}
124
125impl WeakSet {
126 /// `WeakSet.prototype.add( value )`
127 ///
128 /// The `add()` method appends a new object to the end of a `WeakSet` object.
129 ///
130 /// More information:
131 /// - [ECMAScript reference][spec]
132 /// - [MDN documentation][mdn]
133 ///
134 /// [spec]: https://tc39.es/ecma262/#sec-weakset.prototype.add
135 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet/add
136 pub(crate) fn add(
137 this: &JsValue,
138 args: &[JsValue],
139 _context: &mut Context,
140 ) -> JsResult<JsValue> {
141 // 1. Let S be the this value.
142 // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]).
143 let object = this.as_object();
144 let mut set = object
145 .as_ref()
146 .and_then(JsObject::downcast_mut::<NativeWeakSet>)
147 .ok_or_else(|| {
148 js_error!(TypeError:
149 "WeakSet.prototype.add: expected 'this' to be a WeakSet object")
150 })?;
151
152 // 3. If Type(value) is not Object, throw a TypeError exception.
153 let value = args.get_or_undefined(0);
154 let Some(value) = value.as_object() else {
155 return Err(js_error!(TypeError:
156 "WeakSet.add: expected target argument of type `object`, got target of type `{}`",
157 value.type_of()
158 ));
159 };
160
161 // 4. Let entries be the List that is S.[[WeakSetData]].
162 // 5. For each element e of entries, do
163 if set.contains_key(value.inner()) {
164 // a. If e is not empty and SameValue(e, value) is true, then
165 // i. Return S.
166 return Ok(this.clone());
167 }
168
169 // 6. Append value as the last element of entries.
170 set.insert(value.inner(), ());
171
172 // 7. Return S.
173 Ok(this.clone())
174 }
175
176 /// `WeakSet.prototype.delete( value )`
177 ///
178 /// The `delete()` method removes the specified element from a `WeakSet` object.
179 ///
180 /// More information:
181 /// - [ECMAScript reference][spec]
182 /// - [MDN documentation][mdn]
183 ///
184 /// [spec]: https://tc39.es/ecma262/#sec-weakset.prototype.delete
185 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet/delete
186 pub(crate) fn delete(
187 this: &JsValue,
188 args: &[JsValue],
189 _context: &mut Context,
190 ) -> JsResult<JsValue> {
191 // 1. Let S be the this value.
192 // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]).
193 let object = this.as_object();
194 let mut set = object
195 .as_ref()
196 .and_then(JsObject::downcast_mut::<NativeWeakSet>)
197 .ok_or_else(|| {
198 js_error!(TypeError:
199 "WeakSet.prototype.delete: expected 'this' to be a WeakSet object",
200 )
201 })?;
202
203 // 3. If Type(value) is not Object, return false.
204 let value = args.get_or_undefined(0);
205 let Some(value) = value.as_object() else {
206 return Ok(false.into());
207 };
208
209 // 4. Let entries be the List that is S.[[WeakSetData]].
210 // 5. For each element e of entries, do
211 // a. If e is not empty and SameValue(e, value) is true, then
212 // i. Replace the element of entries whose value is e with an element whose value is empty.
213 // ii. Return true.
214 // 6. Return false.
215 Ok(set.remove(value.inner()).into())
216 }
217
218 /// `WeakSet.prototype.has( value )`
219 ///
220 /// The `has()` method returns a boolean indicating whether an object exists in a `WeakSet` or not.
221 ///
222 /// More information:
223 /// - [ECMAScript reference][spec]
224 /// - [MDN documentation][mdn]
225 ///
226 /// [spec]: https://tc39.es/ecma262/#sec-weakset.prototype.has
227 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet/has
228 pub(crate) fn has(
229 this: &JsValue,
230 args: &[JsValue],
231 _context: &mut Context,
232 ) -> JsResult<JsValue> {
233 // 1. Let S be the this value.
234 // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]).
235 let object = this.as_object();
236 let set = object
237 .as_ref()
238 .and_then(JsObject::downcast_ref::<NativeWeakSet>)
239 .ok_or_else(|| {
240 js_error!(TypeError:
241 "WeakSet.prototype.has: expected 'this' to be a WeakSet object")
242 })?;
243
244 // 3. Let entries be the List that is S.[[WeakSetData]].
245 // 4. If Type(value) is not Object, return false.
246 let value = args.get_or_undefined(0);
247 let Some(value) = value.as_object() else {
248 return Ok(false.into());
249 };
250
251 // 5. For each element e of entries, do
252 // a. If e is not empty and SameValue(e, value) is true, return true.
253 // 6. Return false.
254 Ok(set.contains_key(value.inner()).into())
255 }
256}
257
258#[cfg(test)]
259mod tests;