Skip to content

Commit ffa854c

Browse files
lupdraskad
andcommitted
Implement WeakSet (#2586)
This Pull Request changes the following: - Implement `WeakSet` buildin object. - Supersedes #2009 Co-authored-by: raskad <[email protected]>
1 parent c357ae7 commit ffa854c

File tree

12 files changed

+648
-8
lines changed

12 files changed

+648
-8
lines changed

boa_engine/src/builtins/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod symbol;
3232
pub mod typed_array;
3333
pub mod uri;
3434
pub mod weak;
35+
pub mod weak_set;
3536

3637
#[cfg(feature = "intl")]
3738
pub mod intl;
@@ -85,6 +86,7 @@ use crate::{
8586
typed_array::TypedArray,
8687
uri::{DecodeUri, DecodeUriComponent, EncodeUri, EncodeUriComponent},
8788
weak::WeakRef,
89+
weak_set::WeakSet,
8890
},
8991
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
9092
js_string,
@@ -246,6 +248,7 @@ impl Intrinsics {
246248
DecodeUri::init(&intrinsics);
247249
DecodeUriComponent::init(&intrinsics);
248250
WeakRef::init(&intrinsics);
251+
WeakSet::init(&intrinsics);
249252
#[cfg(feature = "intl")]
250253
{
251254
intl::Intl::init(&intrinsics);
@@ -340,6 +343,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult
340343
global_binding::<DecodeUri>(context)?;
341344
global_binding::<DecodeUriComponent>(context)?;
342345
global_binding::<WeakRef>(context)?;
346+
global_binding::<WeakSet>(context)?;
343347

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

boa_engine/src/context/intrinsics.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub struct StandardConstructors {
114114
date_time_format: StandardConstructor,
115115
promise: StandardConstructor,
116116
weak_ref: StandardConstructor,
117+
weak_set: StandardConstructor,
117118
#[cfg(feature = "intl")]
118119
collator: StandardConstructor,
119120
#[cfg(feature = "intl")]
@@ -180,6 +181,7 @@ impl Default for StandardConstructors {
180181
date_time_format: StandardConstructor::default(),
181182
promise: StandardConstructor::default(),
182183
weak_ref: StandardConstructor::default(),
184+
weak_set: StandardConstructor::default(),
183185
#[cfg(feature = "intl")]
184186
collator: StandardConstructor::default(),
185187
#[cfg(feature = "intl")]
@@ -644,6 +646,17 @@ impl StandardConstructors {
644646
&self.weak_ref
645647
}
646648

649+
/// Returns the `WeakSet` constructor.
650+
///
651+
/// More information:
652+
/// - [ECMAScript reference][spec]
653+
///
654+
/// [spec]: https://tc39.es/ecma262/#sec-weakset-constructor
655+
#[inline]
656+
pub const fn weak_set(&self) -> &StandardConstructor {
657+
&self.weak_set
658+
}
659+
647660
/// Returns the `Intl.Collator` constructor.
648661
///
649662
/// More information:

0 commit comments

Comments
 (0)