use crate::{
Context, JsArgs, JsNativeError, JsResult, JsString, JsValue,
builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
object::{ErasedVTableObject, JsObject, internal_methods::get_prototype_from_constructor},
property::Attribute,
realm::Realm,
string::StaticJsStrings,
symbol::JsSymbol,
};
use boa_gc::{Finalize, Trace};
use super::iterable::IteratorHint;
type NativeWeakSet = boa_gc::WeakMap<ErasedVTableObject, ()>;
#[derive(Debug, Trace, Finalize)]
pub(crate) struct WeakSet;
impl IntrinsicObject for WeakSet {
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
fn init(realm: &Realm) {
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.property(
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.method(Self::add, js_string!("add"), 1)
.method(Self::delete, js_string!("delete"), 1)
.method(Self::has, js_string!("has"), 1)
.build();
}
}
impl BuiltInObject for WeakSet {
const NAME: JsString = StaticJsStrings::WEAK_SET;
const ATTRIBUTE: Attribute = Attribute::WRITABLE.union(Attribute::CONFIGURABLE);
}
impl BuiltInConstructor for WeakSet {
const CONSTRUCTOR_ARGUMENTS: usize = 0;
const PROTOTYPE_STORAGE_SLOTS: usize = 4;
const CONSTRUCTOR_STORAGE_SLOTS: usize = 0;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::weak_set;
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
if new_target.is_undefined() {
return Err(JsNativeError::typ()
.with_message("WeakSet: cannot call constructor without `new`")
.into());
}
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::weak_set, context)?;
let weak_set = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
NativeWeakSet::new(),
);
let iterable = args.get_or_undefined(0);
if iterable.is_null_or_undefined() {
return Ok(weak_set.into());
}
let adder = weak_set.get(js_string!("add"), context)?;
let adder = adder
.as_callable()
.ok_or_else(|| JsNativeError::typ().with_message("WeakSet: 'add' is not a function"))?;
let mut iterator_record = iterable.clone().get_iterator(IteratorHint::Sync, context)?;
while let Some(next) = iterator_record.step_value(context)? {
if let Err(status) = adder.call(&weak_set.clone().into(), &[next], context) {
return iterator_record.close(Err(status), context);
}
}
Ok(weak_set.into())
}
}
impl WeakSet {
pub(crate) fn add(
this: &JsValue,
args: &[JsValue],
_context: &mut Context,
) -> JsResult<JsValue> {
let object = this.as_object();
let mut set = object
.as_ref()
.and_then(JsObject::downcast_mut::<NativeWeakSet>)
.ok_or_else(|| {
JsNativeError::typ().with_message("WeakSet.add: called with non-object value")
})?;
let value = args.get_or_undefined(0);
let Some(value) = value.as_object() else {
return Err(JsNativeError::typ()
.with_message(format!(
"WeakSet.add: expected target argument of type `object`, got target of type `{}`",
value.type_of()
)).into());
};
if set.contains_key(value.inner()) {
return Ok(this.clone());
}
set.insert(value.inner(), ());
Ok(this.clone())
}
pub(crate) fn delete(
this: &JsValue,
args: &[JsValue],
_context: &mut Context,
) -> JsResult<JsValue> {
let object = this.as_object();
let mut set = object
.as_ref()
.and_then(JsObject::downcast_mut::<NativeWeakSet>)
.ok_or_else(|| {
JsNativeError::typ().with_message("WeakSet.delete: called with non-object value")
})?;
let value = args.get_or_undefined(0);
let Some(value) = value.as_object() else {
return Ok(false.into());
};
Ok(set.remove(value.inner()).is_some().into())
}
pub(crate) fn has(
this: &JsValue,
args: &[JsValue],
_context: &mut Context,
) -> JsResult<JsValue> {
let object = this.as_object();
let set = object
.as_ref()
.and_then(JsObject::downcast_ref::<NativeWeakSet>)
.ok_or_else(|| {
JsNativeError::typ().with_message("WeakSet.has: called with non-object value")
})?;
let value = args.get_or_undefined(0);
let Some(value) = value.as_object() else {
return Ok(false.into());
};
Ok(set.contains_key(value.inner()).into())
}
}