use crate::error::JsError;
use crate::interpreter::Interpreter;
use crate::interpreter::builtins::proxy::{
is_proxy, proxy_define_property, proxy_get_own_property_descriptor, proxy_get_prototype_of,
proxy_is_extensible, proxy_own_keys, proxy_prevent_extensions, proxy_set_prototype_of,
};
use crate::prelude::{String, ToString, Vec, format, vec};
use crate::value::{
CheapClone, ExoticObject, Guarded, JsObjectRef, JsString, JsValue, Property, PropertyKey,
};
pub fn init_object_prototype(interp: &mut Interpreter) {
let proto = interp.object_prototype.clone();
interp.register_method(&proto, "hasOwnProperty", object_has_own_property, 1);
interp.register_method(&proto, "isPrototypeOf", object_is_prototype_of, 1);
interp.register_method(&proto, "toString", object_to_string, 0);
interp.register_method(&proto, "toLocaleString", object_to_locale_string, 0);
interp.register_method(&proto, "valueOf", object_value_of, 0);
}
pub fn create_object_constructor(interp: &mut Interpreter) -> JsObjectRef {
let constructor = interp.create_native_function("Object", object_constructor, 1);
interp.register_method(&constructor, "keys", object_keys, 1);
interp.register_method(&constructor, "values", object_values, 1);
interp.register_method(&constructor, "entries", object_entries, 1);
interp.register_method(&constructor, "assign", object_assign, 2);
interp.register_method(&constructor, "fromEntries", object_from_entries, 1);
interp.register_method(&constructor, "create", object_create, 1);
interp.register_method(&constructor, "groupBy", object_group_by, 2);
interp.register_method(&constructor, "hasOwn", object_has_own, 2);
interp.register_method(&constructor, "freeze", object_freeze, 1);
interp.register_method(&constructor, "isFrozen", object_is_frozen, 1);
interp.register_method(&constructor, "seal", object_seal, 1);
interp.register_method(&constructor, "isSealed", object_is_sealed, 1);
interp.register_method(
&constructor,
"preventExtensions",
object_prevent_extensions,
1,
);
interp.register_method(&constructor, "isExtensible", object_is_extensible, 1);
interp.register_method(&constructor, "is", object_is, 2);
interp.register_method(
&constructor,
"getOwnPropertyDescriptor",
object_get_own_property_descriptor,
2,
);
interp.register_method(
&constructor,
"getOwnPropertyNames",
object_get_own_property_names,
1,
);
interp.register_method(
&constructor,
"getOwnPropertySymbols",
object_get_own_property_symbols,
1,
);
interp.register_method(
&constructor,
"getOwnPropertyDescriptors",
object_get_own_property_descriptors,
1,
);
interp.register_method(&constructor, "defineProperty", object_define_property, 3);
interp.register_method(
&constructor,
"defineProperties",
object_define_properties,
2,
);
interp.register_method(&constructor, "getPrototypeOf", object_get_prototype_of, 1);
interp.register_method(&constructor, "setPrototypeOf", object_set_prototype_of, 2);
let proto_key = PropertyKey::String(interp.intern("prototype"));
constructor
.borrow_mut()
.set_property(proto_key, JsValue::Object(interp.object_prototype.clone()));
let constructor_key = PropertyKey::String(interp.intern("constructor"));
interp
.object_prototype
.borrow_mut()
.set_property(constructor_key, JsValue::Object(constructor.clone()));
constructor
}
pub fn object_constructor(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let value = args.first().cloned().unwrap_or(JsValue::Undefined);
match value {
JsValue::Null | JsValue::Undefined => {
let guard = interp.heap.create_guard();
let obj = interp.create_object(&guard);
Ok(Guarded::with_guard(JsValue::Object(obj), guard))
}
JsValue::Object(_) => {
Ok(Guarded::unguarded(value))
}
JsValue::Boolean(b) => {
let guard = interp.heap.create_guard();
let obj = interp.create_object(&guard);
obj.borrow_mut().prototype = Some(interp.boolean_prototype.clone());
obj.borrow_mut().exotic = ExoticObject::Boolean(b);
Ok(Guarded::with_guard(JsValue::Object(obj), guard))
}
JsValue::Number(n) => {
let guard = interp.heap.create_guard();
let obj = interp.create_object(&guard);
obj.borrow_mut().prototype = Some(interp.number_prototype.clone());
obj.borrow_mut().exotic = ExoticObject::Number(n);
Ok(Guarded::with_guard(JsValue::Object(obj), guard))
}
JsValue::String(ref s) => {
let guard = interp.heap.create_guard();
let obj = interp.create_object(&guard);
obj.borrow_mut().prototype = Some(interp.string_prototype.clone());
obj.borrow_mut().exotic = ExoticObject::StringObj(s.clone());
let len = s.len();
let length_key = PropertyKey::String(interp.intern("length"));
obj.borrow_mut()
.set_property(length_key, JsValue::Number(len as f64));
Ok(Guarded::with_guard(JsValue::Object(obj), guard))
}
JsValue::Symbol(_) => {
let guard = interp.heap.create_guard();
let obj = interp.create_object(&guard);
Ok(Guarded::with_guard(JsValue::Object(obj), guard))
}
}
}
pub fn object_keys(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let arg = args.first().cloned().unwrap_or(JsValue::Undefined);
let to_obj_guarded = interp.to_object(arg)?;
let obj_ref = match &to_obj_guarded.value {
JsValue::Object(obj) => obj.cheap_clone(),
_ => return Err(JsError::internal_error("to_object returned non-object")),
};
let _guard = to_obj_guarded;
if is_proxy(&obj_ref) {
let Guarded {
value: keys_result, ..
} = proxy_own_keys(interp, obj_ref)?;
if let JsValue::Object(keys_arr) = keys_result {
let keys_ref = keys_arr.borrow();
if let Some(elements) = keys_ref.array_elements() {
let string_keys: Vec<JsValue> = elements
.iter()
.filter(|k| matches!(k, JsValue::String(_)))
.cloned()
.collect();
drop(keys_ref);
let guard = interp.heap.create_guard();
let arr = interp.create_array_from(&guard, string_keys);
return Ok(Guarded::with_guard(JsValue::Object(arr), guard));
}
}
let guard = interp.heap.create_guard();
let arr = interp.create_array_from(&guard, vec![]);
return Ok(Guarded::with_guard(JsValue::Object(arr), guard));
}
let keys: Vec<JsValue> = {
let obj = obj_ref.borrow();
if let ExoticObject::Enum(ref data) = obj.exotic {
data.keys()
.into_iter()
.filter(|k| !k.is_symbol())
.map(|k| JsValue::String(JsString::from(k.to_string())))
.collect()
} else if let ExoticObject::Array { ref elements } = obj.exotic {
let len = elements.len();
let mut result: Vec<JsValue> = (0..len)
.map(|i| JsValue::String(JsString::from(i.to_string())))
.collect();
for (key, prop) in obj.properties.iter() {
if prop.enumerable() && !key.is_symbol() {
let is_covered_index = match key {
PropertyKey::Index(idx) => (*idx as usize) < len,
PropertyKey::String(s) => {
if let Ok(idx) = s.as_str().parse::<usize>() {
idx < len
} else {
false
}
}
PropertyKey::Symbol(_) => false,
};
if !is_covered_index {
result.push(JsValue::String(JsString::from(key.to_string())));
}
}
}
result
} else {
obj.properties
.iter()
.filter(|(key, prop)| prop.enumerable() && !key.is_symbol())
.map(|(key, _)| JsValue::String(JsString::from(key.to_string())))
.collect()
}
};
let guard = interp.heap.create_guard();
let arr = interp.create_array_from(&guard, keys);
Ok(Guarded::with_guard(JsValue::Object(arr), guard))
}
pub fn object_values(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(obj_ref) = obj else {
return Err(JsError::type_error("Object.values requires an object"));
};
let values: Vec<JsValue> = {
let obj = obj_ref.borrow();
if let ExoticObject::Enum(ref data) = obj.exotic {
data.values()
} else {
obj.properties
.iter()
.filter(|(key, prop)| prop.enumerable() && !key.is_symbol())
.map(|(_, prop)| prop.value.clone())
.collect()
}
};
let guard = interp.heap.create_guard();
let arr = interp.create_array_from(&guard, values);
Ok(Guarded::with_guard(JsValue::Object(arr), guard))
}
pub fn object_entries(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(obj_ref) = obj else {
return Err(JsError::type_error("Object.entries requires an object"));
};
let pairs: Vec<(String, JsValue)> = {
let obj = obj_ref.borrow();
if let ExoticObject::Enum(ref data) = obj.exotic {
data.entries()
} else {
obj.properties
.iter()
.filter(|(key, prop)| prop.enumerable() && !key.is_symbol())
.map(|(key, prop)| (key.to_string(), prop.value.clone()))
.collect()
}
};
let guard = interp.heap.create_guard();
let mut entries: Vec<JsValue> = Vec::with_capacity(pairs.len());
for (key, value) in pairs {
let arr =
interp.create_array_from(&guard, vec![JsValue::String(JsString::from(key)), value]);
entries.push(JsValue::Object(arr));
}
let result = interp.create_array_from(&guard, entries);
Ok(Guarded::with_guard(JsValue::Object(result), guard))
}
pub fn object_assign(
_interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let target = args.first().cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(target_ref) = target.clone() else {
return Err(JsError::type_error(
"Object.assign requires an object target",
));
};
for source in args.iter().skip(1) {
if let JsValue::Object(src_ref) = source {
let src = src_ref.borrow();
for (key, prop) in src.properties.iter() {
if prop.enumerable() {
target_ref
.borrow_mut()
.set_property(key.clone(), prop.value.clone());
}
}
}
}
Ok(Guarded::unguarded(target))
}
pub fn object_from_entries(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let iterable = args.first().cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(arr) = iterable else {
return Err(JsError::type_error(
"Object.fromEntries requires an iterable",
));
};
let _arr_guard = interp.guard_value(&JsValue::Object(arr.clone()));
let result_guard = interp.heap.create_guard();
let result = interp.create_object(&result_guard);
let length = arr
.borrow()
.array_length()
.ok_or_else(|| JsError::type_error("Object.fromEntries requires an array-like"))?;
for i in 0..length {
let entry = arr
.borrow()
.get_property(&PropertyKey::Index(i))
.unwrap_or(JsValue::Undefined);
if let JsValue::Object(entry_ref) = entry {
let entry_borrow = entry_ref.borrow();
if entry_borrow.is_array() {
let key = entry_borrow
.get_property(&PropertyKey::Index(0))
.unwrap_or(JsValue::Undefined);
let value = entry_borrow
.get_property(&PropertyKey::Index(1))
.unwrap_or(JsValue::Undefined);
drop(entry_borrow);
let key_str = interp.to_js_string(&key).to_string();
let interned_key = interp.property_key(&key_str);
result.borrow_mut().set_property(interned_key, value);
}
}
}
Ok(Guarded::with_guard(JsValue::Object(result), result_guard))
}
pub fn object_has_own(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let key = args.get(1).cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(obj_ref) = obj else {
return Ok(Guarded::unguarded(JsValue::Boolean(false)));
};
let key_str = interp.to_js_string(&key).to_string();
let interned_key = interp.property_key(&key_str);
let borrowed = obj_ref.borrow();
let has = if let ExoticObject::Enum(ref data) = borrowed.exotic {
data.has_property(&interned_key)
} else {
borrowed.properties.contains_key(&interned_key)
};
Ok(Guarded::unguarded(JsValue::Boolean(has)))
}
pub fn object_create(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let proto = args.first().cloned().unwrap_or(JsValue::Undefined);
let properties = args.get(1).cloned();
let result_guard = interp.heap.create_guard();
let result = interp.create_object(&result_guard);
match proto {
JsValue::Null => {
let mut obj = result.borrow_mut();
obj.prototype = None;
obj.null_prototype = true;
}
JsValue::Object(proto_ref) => {
result.borrow_mut().prototype = Some(proto_ref);
}
_ => {
return Err(JsError::type_error(
"Object prototype may only be an Object or null",
));
}
}
if let Some(props) = properties
&& !matches!(props, JsValue::Undefined)
{
let JsValue::Object(props_ref) = props else {
return Err(JsError::type_error(
"Property descriptors must be an object",
));
};
let value_key = PropertyKey::String(interp.intern("value"));
let writable_key = PropertyKey::String(interp.intern("writable"));
let enumerable_key = PropertyKey::String(interp.intern("enumerable"));
let configurable_key = PropertyKey::String(interp.intern("configurable"));
let get_key = PropertyKey::String(interp.intern("get"));
let set_key = PropertyKey::String(interp.intern("set"));
let prop_keys: Vec<PropertyKey> = {
let props_borrowed = props_ref.borrow();
props_borrowed.properties.keys().cloned().collect()
};
for key in prop_keys {
let descriptor = {
let props_borrowed = props_ref.borrow();
props_borrowed
.get_property(&key)
.unwrap_or(JsValue::Undefined)
};
let JsValue::Object(desc_ref) = descriptor else {
continue; };
let desc_borrowed = desc_ref.borrow();
let value = desc_borrowed
.get_property(&value_key)
.unwrap_or(JsValue::Undefined);
let writable = desc_borrowed
.get_property(&writable_key)
.map(|v| v.to_boolean())
.unwrap_or(false);
let enumerable = desc_borrowed
.get_property(&enumerable_key)
.map(|v| v.to_boolean())
.unwrap_or(false);
let configurable = desc_borrowed
.get_property(&configurable_key)
.map(|v| v.to_boolean())
.unwrap_or(false);
let getter = desc_borrowed.get_property(&get_key);
let setter = desc_borrowed.get_property(&set_key);
drop(desc_borrowed);
let is_accessor = getter.is_some() || setter.is_some();
if is_accessor {
let getter_ref = match getter {
Some(JsValue::Object(g)) => Some(g),
_ => None,
};
let setter_ref = match setter {
Some(JsValue::Object(s)) => Some(s),
_ => None,
};
let mut prop = Property::accessor(getter_ref, setter_ref);
prop.set_enumerable(enumerable);
prop.set_configurable(configurable);
result.borrow_mut().define_property(key, prop);
} else {
let prop = Property::with_attributes(value, writable, enumerable, configurable);
result.borrow_mut().define_property(key, prop);
}
}
}
Ok(Guarded::with_guard(JsValue::Object(result), result_guard))
}
pub fn object_freeze(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
if let JsValue::Object(obj_ref) = &obj {
let mut obj_mut = obj_ref.borrow_mut();
obj_mut.frozen = true;
obj_mut.extensible = false; for (_, prop) in obj_mut.properties.iter_mut() {
prop.set_writable(false);
prop.set_configurable(false);
}
}
let guard = interp.guard_value(&obj);
Ok(Guarded { value: obj, guard })
}
pub fn object_is_frozen(
_interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let is_frozen = match obj {
JsValue::Object(obj_ref) => obj_ref.borrow().frozen,
_ => true, };
Ok(Guarded::unguarded(JsValue::Boolean(is_frozen)))
}
pub fn object_seal(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
if let JsValue::Object(obj_ref) = &obj {
let mut obj_mut = obj_ref.borrow_mut();
obj_mut.sealed = true;
obj_mut.extensible = false; for (_, prop) in obj_mut.properties.iter_mut() {
prop.set_configurable(false);
}
}
let guard = interp.guard_value(&obj);
Ok(Guarded { value: obj, guard })
}
pub fn object_is_sealed(
_interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let is_sealed = match obj {
JsValue::Object(obj_ref) => obj_ref.borrow().sealed,
_ => true, };
Ok(Guarded::unguarded(JsValue::Boolean(is_sealed)))
}
pub fn object_has_own_property(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let JsValue::Object(obj) = this else {
return Ok(Guarded::unguarded(JsValue::Boolean(false)));
};
let arg = args.first().cloned().unwrap_or(JsValue::Undefined);
let key = if let JsValue::Symbol(ref sym) = arg {
PropertyKey::Symbol(sym.clone())
} else {
let prop_name = interp.to_js_string(&arg).to_string();
interp.property_key(&prop_name)
};
let obj_ref = obj.borrow();
let has_prop = if let ExoticObject::Enum(ref data) = obj_ref.exotic {
data.has_property(&key)
} else if let ExoticObject::Array { ref elements } = obj_ref.exotic {
match &key {
PropertyKey::Index(index) => {
(*index as usize) < elements.len()
}
PropertyKey::String(key_str) => {
if let Ok(index) = key_str.as_str().parse::<usize>() {
index < elements.len()
} else {
obj_ref.properties.contains_key(&key)
}
}
PropertyKey::Symbol(_) => {
obj_ref.properties.contains_key(&key)
}
}
} else {
obj_ref.properties.contains_key(&key)
};
Ok(Guarded::unguarded(JsValue::Boolean(has_prop)))
}
pub fn object_is_prototype_of(
_interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let JsValue::Object(this_obj) = this else {
return Ok(Guarded::unguarded(JsValue::Boolean(false)));
};
let arg = args.first().cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(mut check_obj) = arg else {
return Ok(Guarded::unguarded(JsValue::Boolean(false)));
};
loop {
let proto = check_obj.borrow().prototype.clone();
match proto {
Some(p) => {
if crate::gc::Gc::ptr_eq(&this_obj, &p) {
return Ok(Guarded::unguarded(JsValue::Boolean(true)));
}
check_obj = p;
}
None => {
return Ok(Guarded::unguarded(JsValue::Boolean(false)));
}
}
}
}
pub fn object_to_string(
_interp: &mut Interpreter,
this: JsValue,
_args: &[JsValue],
) -> Result<Guarded, JsError> {
let tag = match &this {
JsValue::Undefined => "Undefined",
JsValue::Null => "Null",
JsValue::Boolean(_) => "Boolean",
JsValue::Number(_) => "Number",
JsValue::String(_) => "String",
JsValue::Symbol(_) => "Symbol",
JsValue::Object(obj) => {
let obj_ref = obj.borrow();
match &obj_ref.exotic {
ExoticObject::Array { .. } => "Array",
ExoticObject::Function(_) => "Function",
ExoticObject::Ordinary => "Object",
ExoticObject::Map { .. } => "Map",
ExoticObject::Set { .. } => "Set",
ExoticObject::Date { .. } => "Date",
ExoticObject::RegExp { .. } => "RegExp",
ExoticObject::Generator(_) | ExoticObject::BytecodeGenerator(_) => "Generator",
ExoticObject::Promise(_) => "Promise",
ExoticObject::Environment(_) => "Object",
ExoticObject::Enum(_) => "Object",
ExoticObject::Proxy(_) => "Object",
ExoticObject::Boolean(_) => "Boolean",
ExoticObject::Number(_) => "Number",
ExoticObject::StringObj(_) => "String",
ExoticObject::Symbol(_) => "Symbol",
ExoticObject::RawJSON(_) => "Object", ExoticObject::PendingOrder { .. } => "Object", }
}
};
Ok(Guarded::unguarded(JsValue::String(JsString::from(
format!("[object {}]", tag),
))))
}
pub fn object_value_of(
_interp: &mut Interpreter,
this: JsValue,
_args: &[JsValue],
) -> Result<Guarded, JsError> {
Ok(Guarded::unguarded(this))
}
pub fn object_to_locale_string(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
object_to_string(interp, this, args)
}
pub fn object_get_own_property_descriptor(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let prop = args.get(1).cloned().unwrap_or(JsValue::Undefined);
let to_obj_guarded = interp.to_object(obj)?;
let obj_ref = match &to_obj_guarded.value {
JsValue::Object(obj) => obj.cheap_clone(),
_ => return Err(JsError::internal_error("to_object returned non-object")),
};
let _guard = to_obj_guarded;
let key = PropertyKey::from_value(&prop);
if is_proxy(&obj_ref) {
return proxy_get_own_property_descriptor(interp, obj_ref, &key);
}
let obj_borrowed = obj_ref.borrow();
if let Some((property, in_prototype)) = obj_borrowed.get_property_descriptor(&key) {
if in_prototype {
return Ok(Guarded::unguarded(JsValue::Undefined));
}
let get_key = PropertyKey::String(interp.intern("get"));
let set_key = PropertyKey::String(interp.intern("set"));
let value_key = PropertyKey::String(interp.intern("value"));
let writable_key = PropertyKey::String(interp.intern("writable"));
let enumerable_key = PropertyKey::String(interp.intern("enumerable"));
let configurable_key = PropertyKey::String(interp.intern("configurable"));
let desc_guard = interp.heap.create_guard();
let desc = interp.create_object(&desc_guard);
{
let mut desc_ref = desc.borrow_mut();
if property.is_accessor() {
if let Some(getter) = property.getter() {
desc_ref.set_property(get_key, JsValue::Object(getter.clone()));
} else {
desc_ref.set_property(get_key, JsValue::Undefined);
}
if let Some(setter) = property.setter() {
desc_ref.set_property(set_key, JsValue::Object(setter.clone()));
} else {
desc_ref.set_property(set_key, JsValue::Undefined);
}
} else {
desc_ref.set_property(value_key, property.value.clone());
desc_ref.set_property(writable_key, JsValue::Boolean(property.writable()));
}
desc_ref.set_property(enumerable_key, JsValue::Boolean(property.enumerable()));
desc_ref.set_property(configurable_key, JsValue::Boolean(property.configurable()));
}
Ok(Guarded::with_guard(JsValue::Object(desc), desc_guard))
} else {
Ok(Guarded::unguarded(JsValue::Undefined))
}
}
pub fn object_get_own_property_names(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let to_obj_guarded = interp.to_object(obj)?;
let obj_ref = match &to_obj_guarded.value {
JsValue::Object(obj) => obj.cheap_clone(),
_ => return Err(JsError::internal_error("to_object returned non-object")),
};
let _guard = to_obj_guarded;
let names: Vec<JsValue> = obj_ref
.borrow()
.properties
.keys()
.filter(|key| !key.is_symbol())
.map(|key| JsValue::String(JsString::from(key.to_string())))
.collect();
let guard = interp.heap.create_guard();
let arr = interp.create_array_from(&guard, names);
Ok(Guarded::with_guard(JsValue::Object(arr), guard))
}
pub fn object_get_own_property_symbols(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let to_obj_guarded = interp.to_object(obj)?;
let obj_ref = match &to_obj_guarded.value {
JsValue::Object(obj) => obj.cheap_clone(),
_ => return Err(JsError::internal_error("to_object returned non-object")),
};
let _guard = to_obj_guarded;
let symbols: Vec<JsValue> = obj_ref
.borrow()
.properties
.keys()
.filter_map(|key| {
if let PropertyKey::Symbol(s) = key {
Some(JsValue::Symbol(s.clone()))
} else {
None
}
})
.collect();
let guard = interp.heap.create_guard();
let arr = interp.create_array_from(&guard, symbols);
Ok(Guarded::with_guard(JsValue::Object(arr), guard))
}
pub fn object_define_property(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let prop = args.get(1).cloned().unwrap_or(JsValue::Undefined);
let descriptor = args.get(2).cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(obj_ref) = obj.clone() else {
return Err(JsError::type_error(
"Object.defineProperty requires an object",
));
};
let JsValue::Object(ref desc_ref) = descriptor else {
return Err(JsError::type_error("Property descriptor must be an object"));
};
let key = PropertyKey::from_value(&prop);
if is_proxy(&obj_ref) {
proxy_define_property(interp, obj_ref, key, descriptor)?;
return Ok(Guarded::unguarded(obj));
}
let value_key = PropertyKey::String(interp.intern("value"));
let writable_key = PropertyKey::String(interp.intern("writable"));
let enumerable_key = PropertyKey::String(interp.intern("enumerable"));
let configurable_key = PropertyKey::String(interp.intern("configurable"));
let get_key = PropertyKey::String(interp.intern("get"));
let set_key = PropertyKey::String(interp.intern("set"));
let desc_borrowed = desc_ref.borrow();
let value = desc_borrowed
.get_property(&value_key)
.unwrap_or(JsValue::Undefined);
let writable = desc_borrowed
.get_property(&writable_key)
.map(|v| v.to_boolean())
.unwrap_or(false);
let enumerable = desc_borrowed
.get_property(&enumerable_key)
.map(|v| v.to_boolean())
.unwrap_or(false);
let configurable = desc_borrowed
.get_property(&configurable_key)
.map(|v| v.to_boolean())
.unwrap_or(false);
let getter = desc_borrowed.get_property(&get_key);
let setter = desc_borrowed.get_property(&set_key);
drop(desc_borrowed);
let is_accessor = getter.is_some() || setter.is_some();
if is_accessor {
let getter_ref = match getter {
Some(JsValue::Object(g)) => Some(g),
_ => None,
};
let setter_ref = match setter {
Some(JsValue::Object(s)) => Some(s),
_ => None,
};
let mut prop = Property::accessor(getter_ref, setter_ref);
prop.set_enumerable(enumerable);
prop.set_configurable(configurable);
obj_ref.borrow_mut().define_property(key, prop);
} else {
let mut obj = obj_ref.borrow_mut();
if let ExoticObject::Array { ref mut elements } = obj.exotic {
let maybe_index = match &key {
PropertyKey::Index(idx) => Some(*idx as usize),
PropertyKey::String(key_str) => key_str.as_str().parse::<usize>().ok(),
PropertyKey::Symbol(_) => None,
};
if let Some(index) = maybe_index {
while elements.len() <= index {
elements.push(JsValue::Undefined);
}
if let Some(elem) = elements.get_mut(index) {
*elem = value.clone();
}
}
}
let prop = Property::with_attributes(value, writable, enumerable, configurable);
obj.define_property(key, prop);
}
Ok(Guarded::unguarded(obj))
}
pub fn object_define_properties(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let props = args.get(1).cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(obj_ref) = obj.clone() else {
return Err(JsError::type_error(
"Object.defineProperties requires an object",
));
};
let JsValue::Object(props_ref) = props else {
return Err(JsError::type_error(
"Property descriptors must be an object",
));
};
let value_key = PropertyKey::String(interp.intern("value"));
let writable_key = PropertyKey::String(interp.intern("writable"));
let enumerable_key = PropertyKey::String(interp.intern("enumerable"));
let configurable_key = PropertyKey::String(interp.intern("configurable"));
let get_key = PropertyKey::String(interp.intern("get"));
let set_key = PropertyKey::String(interp.intern("set"));
let prop_keys: Vec<PropertyKey> = {
let props_borrowed = props_ref.borrow();
props_borrowed.properties.keys().cloned().collect()
};
for key in prop_keys {
let descriptor = {
let props_borrowed = props_ref.borrow();
props_borrowed
.get_property(&key)
.unwrap_or(JsValue::Undefined)
};
let JsValue::Object(desc_ref) = descriptor else {
continue; };
let desc_borrowed = desc_ref.borrow();
let value = desc_borrowed
.get_property(&value_key)
.unwrap_or(JsValue::Undefined);
let writable = desc_borrowed
.get_property(&writable_key)
.map(|v| v.to_boolean())
.unwrap_or(false);
let enumerable = desc_borrowed
.get_property(&enumerable_key)
.map(|v| v.to_boolean())
.unwrap_or(false);
let configurable = desc_borrowed
.get_property(&configurable_key)
.map(|v| v.to_boolean())
.unwrap_or(false);
let getter = desc_borrowed.get_property(&get_key);
let setter = desc_borrowed.get_property(&set_key);
drop(desc_borrowed);
let is_accessor = getter.is_some() || setter.is_some();
if is_accessor {
let getter_ref = match getter {
Some(JsValue::Object(g)) => Some(g),
_ => None,
};
let setter_ref = match setter {
Some(JsValue::Object(s)) => Some(s),
_ => None,
};
let mut prop = Property::accessor(getter_ref, setter_ref);
prop.set_enumerable(enumerable);
prop.set_configurable(configurable);
obj_ref.borrow_mut().define_property(key, prop);
} else {
let prop = Property::with_attributes(value, writable, enumerable, configurable);
obj_ref.borrow_mut().define_property(key, prop);
}
}
Ok(Guarded::unguarded(obj))
}
pub fn object_get_prototype_of(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(obj_ref) = obj else {
return Err(JsError::type_error(
"Object.getPrototypeOf requires an object",
));
};
if is_proxy(&obj_ref) {
return proxy_get_prototype_of(interp, obj_ref);
}
let obj_borrowed = obj_ref.borrow();
match &obj_borrowed.prototype {
Some(proto) => Ok(Guarded::unguarded(JsValue::Object(proto.clone()))),
None => Ok(Guarded::unguarded(JsValue::Null)),
}
}
pub fn object_set_prototype_of(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let proto = args.get(1).cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(obj_ref) = obj.clone() else {
return Err(JsError::type_error(
"Object.setPrototypeOf requires an object",
));
};
if is_proxy(&obj_ref) {
proxy_set_prototype_of(interp, obj_ref, proto)?;
return Ok(Guarded::unguarded(obj));
}
let new_proto = match proto {
JsValue::Object(p) => Some(p),
JsValue::Null => None,
_ => {
return Err(JsError::type_error(
"Object prototype may only be an Object or null",
));
}
};
obj_ref.borrow_mut().prototype = new_proto;
Ok(Guarded::unguarded(obj))
}
pub fn object_is(
_interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let value1 = args.first().cloned().unwrap_or(JsValue::Undefined);
let value2 = args.get(1).cloned().unwrap_or(JsValue::Undefined);
let result = same_value(&value1, &value2);
Ok(Guarded::unguarded(JsValue::Boolean(result)))
}
fn same_value(x: &JsValue, y: &JsValue) -> bool {
match (x, y) {
(JsValue::Undefined, JsValue::Undefined) => true,
(JsValue::Null, JsValue::Null) => true,
(JsValue::Boolean(a), JsValue::Boolean(b)) => a == b,
(JsValue::Number(a), JsValue::Number(b)) => {
if a.is_nan() && b.is_nan() {
return true;
}
if *a == 0.0 && *b == 0.0 {
return a.signum() == b.signum() || (a.is_nan() && b.is_nan());
}
a == b
}
(JsValue::String(a), JsValue::String(b)) => a == b,
(JsValue::Symbol(a), JsValue::Symbol(b)) => a == b,
(JsValue::Object(a), JsValue::Object(b)) => a == b,
_ => false,
}
}
pub fn object_prevent_extensions(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
if let JsValue::Object(obj_ref) = &obj {
if is_proxy(obj_ref) {
proxy_prevent_extensions(interp, obj_ref.clone())?;
} else {
obj_ref.borrow_mut().extensible = false;
}
}
let guard = interp.guard_value(&obj);
Ok(Guarded { value: obj, guard })
}
pub fn object_is_extensible(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let is_extensible = match obj {
JsValue::Object(ref obj_ref) if is_proxy(obj_ref) => {
proxy_is_extensible(interp, obj_ref.clone())?
}
JsValue::Object(obj_ref) => obj_ref.borrow().extensible,
_ => false, };
Ok(Guarded::unguarded(JsValue::Boolean(is_extensible)))
}
pub fn object_get_own_property_descriptors(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let obj = args.first().cloned().unwrap_or(JsValue::Undefined);
let to_obj_guarded = interp.to_object(obj)?;
let obj_ref = match &to_obj_guarded.value {
JsValue::Object(obj) => obj.cheap_clone(),
_ => return Err(JsError::internal_error("to_object returned non-object")),
};
let _guard = to_obj_guarded;
let get_key = PropertyKey::String(interp.intern("get"));
let set_key = PropertyKey::String(interp.intern("set"));
let value_key = PropertyKey::String(interp.intern("value"));
let writable_key = PropertyKey::String(interp.intern("writable"));
let enumerable_key = PropertyKey::String(interp.intern("enumerable"));
let configurable_key = PropertyKey::String(interp.intern("configurable"));
let prop_keys: Vec<PropertyKey> = {
let obj_borrowed = obj_ref.borrow();
obj_borrowed.properties.keys().cloned().collect()
};
let result_guard = interp.heap.create_guard();
let result = interp.create_object(&result_guard);
for key in prop_keys {
let property = {
let obj_borrowed = obj_ref.borrow();
obj_borrowed.get_own_property(&key).cloned()
};
if let Some(property) = property {
let desc = interp.create_object(&result_guard);
{
let mut desc_ref = desc.borrow_mut();
if property.is_accessor() {
if let Some(getter) = property.getter() {
desc_ref.set_property(get_key.clone(), JsValue::Object(getter.clone()));
} else {
desc_ref.set_property(get_key.clone(), JsValue::Undefined);
}
if let Some(setter) = property.setter() {
desc_ref.set_property(set_key.clone(), JsValue::Object(setter.clone()));
} else {
desc_ref.set_property(set_key.clone(), JsValue::Undefined);
}
} else {
desc_ref.set_property(value_key.clone(), property.value.clone());
desc_ref
.set_property(writable_key.clone(), JsValue::Boolean(property.writable()));
}
desc_ref.set_property(
enumerable_key.clone(),
JsValue::Boolean(property.enumerable()),
);
desc_ref.set_property(
configurable_key.clone(),
JsValue::Boolean(property.configurable()),
);
}
result.borrow_mut().set_property(key, JsValue::Object(desc));
}
}
Ok(Guarded::with_guard(JsValue::Object(result), result_guard))
}
pub fn object_group_by(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let items = args.first().cloned().unwrap_or(JsValue::Undefined);
let callback = args.get(1).cloned().unwrap_or(JsValue::Undefined);
let JsValue::Object(items_ref) = items else {
return Err(JsError::type_error("Object.groupBy requires an iterable"));
};
let guard = interp.heap.create_guard();
guard.guard(items_ref.clone());
if let JsValue::Object(cb_obj) = &callback {
guard.guard(cb_obj.clone());
}
let elements: Vec<JsValue> = {
let items_borrowed = items_ref.borrow();
if let Some(elems) = items_borrowed.array_elements() {
elems.to_vec()
} else {
return Err(JsError::type_error(
"Object.groupBy requires an array-like object",
));
}
};
let result = interp.create_object(&guard);
{
let mut result_ref = result.borrow_mut();
result_ref.prototype = None;
result_ref.null_prototype = true;
}
let mut group_keys: Vec<String> = Vec::new();
let mut group_items: Vec<Vec<JsValue>> = Vec::new();
for (index, item) in elements.into_iter().enumerate() {
if let JsValue::Object(item_obj) = &item {
guard.guard(item_obj.clone());
}
let key_result = interp.call_function(
callback.clone(),
JsValue::Undefined,
&[item.clone(), JsValue::Number(index as f64)],
)?;
let key_str = interp.to_js_string(&key_result.value).to_string();
let found_idx = group_keys.iter().position(|k| k == &key_str);
match found_idx {
Some(idx) => {
if let Some(items_vec) = group_items.get_mut(idx) {
items_vec.push(item);
}
}
None => {
group_keys.push(key_str);
group_items.push(vec![item]);
}
}
}
for (key, items) in group_keys.into_iter().zip(group_items.into_iter()) {
let arr = interp.create_array_from(&guard, items);
let prop_key = PropertyKey::String(interp.intern(&key));
result
.borrow_mut()
.set_property(prop_key, JsValue::Object(arr));
}
Ok(Guarded::with_guard(JsValue::Object(result), guard))
}