mirror of https://github.com/boa-dev/boa.git
Browse Source
* Implement `InternalObjectMethods` struct for `Object` * Implement remaining string internal methods * Split object internal methods and operations in modules * Store internal object methods as static struct * Document `ObjectData` and order indices on `[[OwnPropertyKeys]]` * Document and rearrange internal object methodspull/1518/head
jedel1043
3 years ago
committed by
GitHub
45 changed files with 2609 additions and 1381 deletions
@ -1,983 +0,0 @@
|
||||
//! This module defines the object internal methods.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
|
||||
|
||||
use crate::{ |
||||
builtins::Array, |
||||
object::{JsObject, Object, ObjectData}, |
||||
property::{DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind}, |
||||
value::{JsValue, Type}, |
||||
BoaProfiler, Context, JsResult, |
||||
}; |
||||
|
||||
impl JsObject { |
||||
/// Check if object has property.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-hasproperty
|
||||
// NOTE: for now context is not used but it will in the future.
|
||||
#[inline] |
||||
pub fn has_property<K>(&self, key: K, _context: &mut Context) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
{ |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Return ? O.[[HasProperty]](P).
|
||||
Ok(self.__has_property__(&key.into())) |
||||
} |
||||
|
||||
/// Check if it is extensible.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isextensible-o
|
||||
// NOTE: for now context is not used but it will in the future.
|
||||
#[inline] |
||||
pub fn is_extensible(&self, _context: &mut Context) -> JsResult<bool> { |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Return ? O.[[IsExtensible]]().
|
||||
Ok(self.__is_extensible__()) |
||||
} |
||||
|
||||
/// Delete property, if deleted return `true`.
|
||||
#[inline] |
||||
pub fn delete<K>(&self, key: K) -> bool |
||||
where |
||||
K: Into<PropertyKey>, |
||||
{ |
||||
self.__delete__(&key.into()) |
||||
} |
||||
|
||||
/// Defines the property or throws a `TypeError` if the operation fails.
|
||||
///
|
||||
/// More information:
|
||||
/// - [EcmaScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
|
||||
#[inline] |
||||
pub fn delete_property_or_throw<K>(&self, key: K, context: &mut Context) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
{ |
||||
let key = key.into(); |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Let success be ? O.[[Delete]](P).
|
||||
let success = self.__delete__(&key); |
||||
// 4. If success is false, throw a TypeError exception.
|
||||
if !success { |
||||
return Err(context.construct_type_error(format!("cannot delete property: {}", key))); |
||||
} |
||||
// 5. Return success.
|
||||
Ok(success) |
||||
} |
||||
|
||||
/// Check if object has an own property.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
|
||||
#[inline] |
||||
pub fn has_own_property<K>(&self, key: K, _context: &mut Context) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
{ |
||||
let key = key.into(); |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
let desc = self.__get_own_property__(&key); |
||||
// 4. If desc is undefined, return false.
|
||||
// 5. Return true.
|
||||
Ok(desc.is_some()) |
||||
} |
||||
|
||||
/// Get property from object or throw.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-o-p
|
||||
#[inline] |
||||
pub fn get<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
{ |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Return ? O.[[Get]](P, O).
|
||||
self.__get__(&key.into(), self.clone().into(), context) |
||||
} |
||||
|
||||
/// set property of object or throw if bool flag is passed.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
|
||||
#[inline] |
||||
pub fn set<K, V>(&self, key: K, value: V, throw: bool, context: &mut Context) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
V: Into<JsValue>, |
||||
{ |
||||
let key = key.into(); |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Assert: Type(Throw) is Boolean.
|
||||
// 4. Let success be ? O.[[Set]](P, V, O).
|
||||
let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?; |
||||
// 5. If success is false and Throw is true, throw a TypeError exception.
|
||||
if !success && throw { |
||||
return Err( |
||||
context.construct_type_error(format!("cannot set non-writable property: {}", key)) |
||||
); |
||||
} |
||||
// 6. Return success.
|
||||
Ok(success) |
||||
} |
||||
|
||||
/// Define property or throw.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
|
||||
#[inline] |
||||
pub fn define_property_or_throw<K, P>( |
||||
&self, |
||||
key: K, |
||||
desc: P, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
P: Into<PropertyDescriptor>, |
||||
{ |
||||
let key = key.into(); |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
|
||||
let success = self.__define_own_property__(key.clone(), desc.into(), context)?; |
||||
// 4. If success is false, throw a TypeError exception.
|
||||
if !success { |
||||
return Err(context.construct_type_error(format!("cannot redefine property: {}", key))); |
||||
} |
||||
// 5. Return success.
|
||||
Ok(success) |
||||
} |
||||
|
||||
/// Create data property
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
|
||||
pub fn create_data_property<K, V>( |
||||
&self, |
||||
key: K, |
||||
value: V, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
V: Into<JsValue>, |
||||
{ |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
|
||||
let new_desc = PropertyDescriptor::builder() |
||||
.value(value) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(true); |
||||
// 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
|
||||
self.__define_own_property__(key.into(), new_desc.into(), context) |
||||
} |
||||
|
||||
/// Create data property or throw
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
|
||||
pub fn create_data_property_or_throw<K, V>( |
||||
&self, |
||||
key: K, |
||||
value: V, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
V: Into<JsValue>, |
||||
{ |
||||
let key = key.into(); |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Let success be ? CreateDataProperty(O, P, V).
|
||||
let success = self.create_data_property(key.clone(), value, context)?; |
||||
// 4. If success is false, throw a TypeError exception.
|
||||
if !success { |
||||
return Err(context.construct_type_error(format!("cannot redefine property: {}", key))); |
||||
} |
||||
// 5. Return success.
|
||||
Ok(success) |
||||
} |
||||
|
||||
/// `[[hasProperty]]`
|
||||
#[inline] |
||||
pub(crate) fn __has_property__(&self, key: &PropertyKey) -> bool { |
||||
let prop = self.__get_own_property__(key); |
||||
if prop.is_none() { |
||||
let parent = self.__get_prototype_of__(); |
||||
return if let JsValue::Object(ref object) = parent { |
||||
object.__has_property__(key) |
||||
} else { |
||||
false |
||||
}; |
||||
} |
||||
true |
||||
} |
||||
|
||||
/// Check if it is extensible.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible
|
||||
#[inline] |
||||
pub(crate) fn __is_extensible__(&self) -> bool { |
||||
self.borrow().extensible |
||||
} |
||||
|
||||
/// Disable extensibility.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions
|
||||
#[inline] |
||||
pub fn __prevent_extensions__(&mut self) -> bool { |
||||
self.borrow_mut().extensible = false; |
||||
true |
||||
} |
||||
|
||||
/// Delete property.
|
||||
#[inline] |
||||
pub(crate) fn __delete__(&self, key: &PropertyKey) -> bool { |
||||
match self.__get_own_property__(key) { |
||||
Some(desc) if desc.expect_configurable() => { |
||||
self.remove(key); |
||||
true |
||||
} |
||||
Some(_) => false, |
||||
None => true, |
||||
} |
||||
} |
||||
|
||||
/// `[[Get]]`
|
||||
pub fn __get__( |
||||
&self, |
||||
key: &PropertyKey, |
||||
receiver: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
match self.__get_own_property__(key) { |
||||
None => { |
||||
// parent will either be null or an Object
|
||||
if let Some(parent) = self.__get_prototype_of__().as_object() { |
||||
Ok(parent.__get__(key, receiver, context)?) |
||||
} else { |
||||
Ok(JsValue::undefined()) |
||||
} |
||||
} |
||||
Some(ref desc) => match desc.kind() { |
||||
DescriptorKind::Data { |
||||
value: Some(value), .. |
||||
} => Ok(value.clone()), |
||||
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { |
||||
context.call(get, &receiver, &[]) |
||||
} |
||||
_ => Ok(JsValue::undefined()), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
/// `[[Set]]`
|
||||
pub fn __set__( |
||||
&self, |
||||
key: PropertyKey, |
||||
value: JsValue, |
||||
receiver: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
let _timer = BoaProfiler::global().start_event("Object::set", "object"); |
||||
|
||||
// Fetch property key
|
||||
let own_desc = if let Some(desc) = self.__get_own_property__(&key) { |
||||
desc |
||||
} else if let Some(ref mut parent) = self.__get_prototype_of__().as_object() { |
||||
return parent.__set__(key, value, receiver, context); |
||||
} else { |
||||
PropertyDescriptor::builder() |
||||
.value(JsValue::undefined()) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(true) |
||||
.build() |
||||
}; |
||||
|
||||
if own_desc.is_data_descriptor() { |
||||
if !own_desc.expect_writable() { |
||||
return Ok(false); |
||||
} |
||||
|
||||
let receiver = match receiver.as_object() { |
||||
Some(obj) => obj, |
||||
_ => return Ok(false), |
||||
}; |
||||
|
||||
if let Some(ref existing_desc) = receiver.__get_own_property__(&key) { |
||||
if existing_desc.is_accessor_descriptor() { |
||||
return Ok(false); |
||||
} |
||||
if !existing_desc.expect_writable() { |
||||
return Ok(false); |
||||
} |
||||
return receiver.__define_own_property__( |
||||
key, |
||||
PropertyDescriptor::builder().value(value).build(), |
||||
context, |
||||
); |
||||
} else { |
||||
return receiver.create_data_property(key, value, context); |
||||
} |
||||
} |
||||
|
||||
match own_desc.set() { |
||||
Some(set) if !set.is_undefined() => { |
||||
context.call(set, &receiver, &[value])?; |
||||
Ok(true) |
||||
} |
||||
_ => Ok(false), |
||||
} |
||||
} |
||||
|
||||
/// `[[defineOwnProperty]]`
|
||||
pub fn __define_own_property__( |
||||
&self, |
||||
key: PropertyKey, |
||||
desc: PropertyDescriptor, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
if self.is_array() { |
||||
self.array_define_own_property(key, desc, context) |
||||
} else { |
||||
Ok(self.ordinary_define_own_property(key, desc)) |
||||
} |
||||
} |
||||
|
||||
/// Define an own property for an ordinary object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty
|
||||
pub fn ordinary_define_own_property(&self, key: PropertyKey, desc: PropertyDescriptor) -> bool { |
||||
let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object"); |
||||
|
||||
let extensible = self.__is_extensible__(); |
||||
|
||||
let mut current = if let Some(own) = self.__get_own_property__(&key) { |
||||
own |
||||
} else { |
||||
if !extensible { |
||||
return false; |
||||
} |
||||
|
||||
self.insert( |
||||
key, |
||||
if desc.is_generic_descriptor() || desc.is_data_descriptor() { |
||||
desc.into_data_defaulted() |
||||
} else { |
||||
desc.into_accessor_defaulted() |
||||
}, |
||||
); |
||||
|
||||
return true; |
||||
}; |
||||
|
||||
// 3
|
||||
if desc.is_empty() { |
||||
return true; |
||||
} |
||||
|
||||
// 4
|
||||
if !current.expect_configurable() { |
||||
if matches!(desc.configurable(), Some(true)) { |
||||
return false; |
||||
} |
||||
|
||||
if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable()) |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// 5
|
||||
if desc.is_generic_descriptor() { |
||||
// no further validation required
|
||||
} else if current.is_data_descriptor() != desc.is_data_descriptor() { |
||||
if !current.expect_configurable() { |
||||
return false; |
||||
} |
||||
if current.is_data_descriptor() { |
||||
current = current.into_accessor_defaulted(); |
||||
} else { |
||||
current = current.into_data_defaulted(); |
||||
} |
||||
} else if current.is_data_descriptor() && desc.is_data_descriptor() { |
||||
if !current.expect_configurable() && !current.expect_writable() { |
||||
if matches!(desc.writable(), Some(true)) { |
||||
return false; |
||||
} |
||||
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value())) |
||||
{ |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
} else if !current.expect_configurable() { |
||||
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) { |
||||
return false; |
||||
} |
||||
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) { |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
current.fill_with(desc); |
||||
self.insert(key, current); |
||||
|
||||
true |
||||
} |
||||
|
||||
/// Define an own property for an array.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
|
||||
fn array_define_own_property( |
||||
&self, |
||||
key: PropertyKey, |
||||
desc: PropertyDescriptor, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
match key { |
||||
PropertyKey::String(ref s) if s == "length" => { |
||||
let new_len_val = match desc.value() { |
||||
Some(value) => value, |
||||
_ => return Ok(self.ordinary_define_own_property("length".into(), desc)), |
||||
}; |
||||
|
||||
let new_len = new_len_val.to_u32(context)?; |
||||
let number_len = new_len_val.to_number(context)?; |
||||
|
||||
#[allow(clippy::float_cmp)] |
||||
if new_len as f64 != number_len { |
||||
return Err(context.construct_range_error("bad length for array")); |
||||
} |
||||
|
||||
let mut new_len_desc = PropertyDescriptor::builder() |
||||
.value(new_len) |
||||
.maybe_writable(desc.writable()) |
||||
.maybe_enumerable(desc.enumerable()) |
||||
.maybe_configurable(desc.configurable()); |
||||
let old_len_desc = self.__get_own_property__(&"length".into()).unwrap(); |
||||
let old_len = old_len_desc.expect_value(); |
||||
if new_len >= old_len.to_u32(context)? { |
||||
return Ok( |
||||
self.ordinary_define_own_property("length".into(), new_len_desc.build()) |
||||
); |
||||
} |
||||
|
||||
if !old_len_desc.expect_writable() { |
||||
return Ok(false); |
||||
} |
||||
|
||||
let new_writable = if new_len_desc.inner().writable().unwrap_or(true) { |
||||
true |
||||
} else { |
||||
new_len_desc = new_len_desc.writable(true); |
||||
false |
||||
}; |
||||
|
||||
if !self.ordinary_define_own_property("length".into(), new_len_desc.clone().build()) |
||||
{ |
||||
return Ok(false); |
||||
} |
||||
|
||||
let max_value = self |
||||
.borrow() |
||||
.properties |
||||
.index_property_keys() |
||||
.max() |
||||
.copied(); |
||||
|
||||
if let Some(mut index) = max_value { |
||||
while index >= new_len { |
||||
let contains_index = self.borrow().properties.contains_key(&index.into()); |
||||
if contains_index && !self.__delete__(&index.into()) { |
||||
new_len_desc = new_len_desc.value(index + 1); |
||||
if !new_writable { |
||||
new_len_desc = new_len_desc.writable(false); |
||||
} |
||||
self.ordinary_define_own_property( |
||||
"length".into(), |
||||
new_len_desc.build(), |
||||
); |
||||
return Ok(false); |
||||
} |
||||
|
||||
index = if let Some(sub) = index.checked_sub(1) { |
||||
sub |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if !new_writable { |
||||
self.ordinary_define_own_property( |
||||
"length".into(), |
||||
PropertyDescriptor::builder().writable(false).build(), |
||||
); |
||||
} |
||||
Ok(true) |
||||
} |
||||
PropertyKey::Index(index) => { |
||||
let old_len_desc = self.__get_own_property__(&"length".into()).unwrap(); |
||||
let old_len = old_len_desc.expect_value().to_u32(context)?; |
||||
if index >= old_len && !old_len_desc.expect_writable() { |
||||
return Ok(false); |
||||
} |
||||
if self.ordinary_define_own_property(key, desc) { |
||||
if index >= old_len && index < u32::MAX { |
||||
let desc = PropertyDescriptor::builder() |
||||
.value(index + 1) |
||||
.maybe_writable(old_len_desc.writable()) |
||||
.maybe_enumerable(old_len_desc.enumerable()) |
||||
.maybe_configurable(old_len_desc.configurable()); |
||||
self.ordinary_define_own_property("length".into(), desc.into()); |
||||
} |
||||
Ok(true) |
||||
} else { |
||||
Ok(false) |
||||
} |
||||
} |
||||
_ => Ok(self.ordinary_define_own_property(key, desc)), |
||||
} |
||||
} |
||||
|
||||
/// Gets own property of 'Object'
|
||||
///
|
||||
#[inline] |
||||
pub fn __get_own_property__(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
||||
let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); |
||||
|
||||
let object = self.borrow(); |
||||
match object.data { |
||||
ObjectData::String(_) => self.string_exotic_get_own_property(key), |
||||
_ => self.ordinary_get_own_property(key), |
||||
} |
||||
} |
||||
|
||||
/// StringGetOwnProperty abstract operation
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-stringgetownproperty
|
||||
#[inline] |
||||
pub fn string_get_own_property(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
||||
let object = self.borrow(); |
||||
|
||||
match key { |
||||
PropertyKey::Index(index) => { |
||||
let string = object.as_string().unwrap(); |
||||
let pos = *index as usize; |
||||
|
||||
if pos >= string.len() { |
||||
return None; |
||||
} |
||||
|
||||
let result_str = string |
||||
.encode_utf16() |
||||
.nth(pos) |
||||
.map(|utf16_val| JsValue::from(String::from_utf16_lossy(&[utf16_val])))?; |
||||
|
||||
let desc = PropertyDescriptor::builder() |
||||
.value(result_str) |
||||
.writable(false) |
||||
.enumerable(true) |
||||
.configurable(false) |
||||
.build(); |
||||
|
||||
Some(desc) |
||||
} |
||||
_ => None, |
||||
} |
||||
} |
||||
|
||||
/// Gets own property of 'String' exotic object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-getownproperty-p
|
||||
#[inline] |
||||
pub fn string_exotic_get_own_property(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
||||
let desc = self.ordinary_get_own_property(key); |
||||
|
||||
if desc.is_some() { |
||||
desc |
||||
} else { |
||||
self.string_get_own_property(key) |
||||
} |
||||
} |
||||
|
||||
/// The specification returns a Property Descriptor or Undefined.
|
||||
///
|
||||
/// These are 2 separate types and we can't do that here.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p
|
||||
#[inline] |
||||
pub fn ordinary_get_own_property(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
||||
let object = self.borrow(); |
||||
let property = object.properties.get(key); |
||||
|
||||
property.cloned() |
||||
} |
||||
|
||||
/// Essential internal method OwnPropertyKeys
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#table-essential-internal-methods
|
||||
#[inline] |
||||
#[track_caller] |
||||
pub fn own_property_keys(&self) -> Vec<PropertyKey> { |
||||
self.borrow().properties.keys().collect() |
||||
} |
||||
|
||||
/// The abstract operation ObjectDefineProperties
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties
|
||||
#[inline] |
||||
pub fn define_properties(&mut self, props: JsValue, context: &mut Context) -> JsResult<()> { |
||||
let props = &props.to_object(context)?; |
||||
let keys = props.own_property_keys(); |
||||
let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new(); |
||||
|
||||
for next_key in keys { |
||||
if let Some(prop_desc) = props.__get_own_property__(&next_key) { |
||||
if prop_desc.expect_enumerable() { |
||||
let desc_obj = props.get(next_key.clone(), context)?; |
||||
let desc = desc_obj.to_property_descriptor(context)?; |
||||
descriptors.push((next_key, desc)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
for (p, d) in descriptors { |
||||
self.define_property_or_throw(p, d, context)?; |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// `Object.setPropertyOf(obj, prototype)`
|
||||
///
|
||||
/// This method sets the prototype (i.e., the internal `[[Prototype]]` property)
|
||||
/// of a specified object to another object or `null`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
|
||||
#[inline] |
||||
pub fn __set_prototype_of__(&mut self, val: JsValue) -> bool { |
||||
debug_assert!(val.is_object() || val.is_null()); |
||||
let current = self.__get_prototype_of__(); |
||||
if JsValue::same_value(¤t, &val) { |
||||
return true; |
||||
} |
||||
if !self.__is_extensible__() { |
||||
return false; |
||||
} |
||||
let mut p = val.clone(); |
||||
let mut done = false; |
||||
while !done { |
||||
if p.is_null() { |
||||
done = true |
||||
} else if JsValue::same_value(&JsValue::new(self.clone()), &p) { |
||||
return false; |
||||
} else { |
||||
let prototype = p |
||||
.as_object() |
||||
.expect("prototype should be null or object") |
||||
.__get_prototype_of__(); |
||||
p = prototype; |
||||
} |
||||
} |
||||
self.set_prototype_instance(val); |
||||
true |
||||
} |
||||
|
||||
/// Returns either the prototype or null
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf
|
||||
#[inline] |
||||
#[track_caller] |
||||
pub fn __get_prototype_of__(&self) -> JsValue { |
||||
self.borrow().prototype.clone() |
||||
} |
||||
|
||||
/// Helper function for property insertion.
|
||||
#[inline] |
||||
#[track_caller] |
||||
pub(crate) fn insert<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
P: Into<PropertyDescriptor>, |
||||
{ |
||||
self.borrow_mut().insert(key, property) |
||||
} |
||||
|
||||
/// Helper function for property removal.
|
||||
#[inline] |
||||
#[track_caller] |
||||
pub(crate) fn remove(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
||||
self.borrow_mut().remove(key) |
||||
} |
||||
|
||||
/// Inserts a field in the object `properties` without checking if it's writable.
|
||||
///
|
||||
/// If a field was already in the object with the same name that a `Some` is returned
|
||||
/// with that field, otherwise None is returned.
|
||||
#[inline] |
||||
pub fn insert_property<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
P: Into<PropertyDescriptor>, |
||||
{ |
||||
self.insert(key.into(), property) |
||||
} |
||||
|
||||
/// It determines if Object is a callable function with a `[[Call]]` internal method.
|
||||
///
|
||||
/// More information:
|
||||
/// - [EcmaScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
|
||||
#[inline] |
||||
#[track_caller] |
||||
pub fn is_callable(&self) -> bool { |
||||
self.borrow().is_callable() |
||||
} |
||||
|
||||
/// It determines if Object is a function object with a `[[Construct]]` internal method.
|
||||
///
|
||||
/// More information:
|
||||
/// - [EcmaScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor
|
||||
#[inline] |
||||
#[track_caller] |
||||
pub fn is_constructable(&self) -> bool { |
||||
self.borrow().is_constructable() |
||||
} |
||||
|
||||
/// Returns true if the JsObject is the global for a Realm
|
||||
pub fn is_global(&self) -> bool { |
||||
matches!(self.borrow().data, ObjectData::Global) |
||||
} |
||||
|
||||
/// It is used to create List value whose elements are provided by the indexed properties of
|
||||
/// self.
|
||||
///
|
||||
/// More information:
|
||||
/// - [EcmaScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createlistfromarraylike
|
||||
pub(crate) fn create_list_from_array_like( |
||||
&self, |
||||
element_types: &[Type], |
||||
context: &mut Context, |
||||
) -> JsResult<Vec<JsValue>> { |
||||
// 1. If elementTypes is not present, set elementTypes to « Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object ».
|
||||
let types = if element_types.is_empty() { |
||||
&[ |
||||
Type::Undefined, |
||||
Type::Null, |
||||
Type::Boolean, |
||||
Type::String, |
||||
Type::Symbol, |
||||
Type::Number, |
||||
Type::BigInt, |
||||
Type::Object, |
||||
] |
||||
} else { |
||||
element_types |
||||
}; |
||||
|
||||
// TODO: 2. If Type(obj) is not Object, throw a TypeError exception.
|
||||
|
||||
// 3. Let len be ? LengthOfArrayLike(obj).
|
||||
let len = self.length_of_array_like(context)?; |
||||
|
||||
// 4. Let list be a new empty List.
|
||||
let mut list = Vec::with_capacity(len); |
||||
|
||||
// 5. Let index be 0.
|
||||
// 6. Repeat, while index < len,
|
||||
for index in 0..len { |
||||
// a. Let indexName be ! ToString(𝔽(index)).
|
||||
// b. Let next be ? Get(obj, indexName).
|
||||
let next = self.get(index, context)?; |
||||
// c. If Type(next) is not an element of elementTypes, throw a TypeError exception.
|
||||
if !types.contains(&next.get_type()) { |
||||
return Err(context.construct_type_error("bad type")); |
||||
} |
||||
// d. Append next as the last element of list.
|
||||
list.push(next.clone()); |
||||
// e. Set index to index + 1.
|
||||
} |
||||
|
||||
// 7. Return list.
|
||||
Ok(list) |
||||
} |
||||
|
||||
/// It is used to iterate over names of object's keys.
|
||||
///
|
||||
/// More information:
|
||||
/// - [EcmaScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-enumerableownpropertynames
|
||||
pub(crate) fn enumerable_own_property_names( |
||||
&self, |
||||
kind: PropertyNameKind, |
||||
context: &mut Context, |
||||
) -> JsResult<Vec<JsValue>> { |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
|
||||
let own_keys = self.own_property_keys(); |
||||
// 3. Let properties be a new empty List.
|
||||
let mut properties = vec![]; |
||||
|
||||
// 4. For each element key of ownKeys, do
|
||||
for key in own_keys { |
||||
// a. If Type(key) is String, then
|
||||
let key_str = match &key { |
||||
PropertyKey::String(s) => Some(s.clone()), |
||||
PropertyKey::Index(i) => Some(i.to_string().into()), |
||||
_ => None, |
||||
}; |
||||
|
||||
if let Some(key_str) = key_str { |
||||
// i. Let desc be ? O.[[GetOwnProperty]](key).
|
||||
let desc = self.__get_own_property__(&key); |
||||
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
|
||||
if let Some(desc) = desc { |
||||
if desc.expect_enumerable() { |
||||
match kind { |
||||
// 1. If kind is key, append key to properties.
|
||||
PropertyNameKind::Key => properties.push(key_str.into()), |
||||
// 2. Else,
|
||||
// a. Let value be ? Get(O, key).
|
||||
// b. If kind is value, append value to properties.
|
||||
PropertyNameKind::Value => { |
||||
properties.push(self.get(key.clone(), context)?) |
||||
} |
||||
// c. Else,
|
||||
// i. Assert: kind is key+value.
|
||||
// ii. Let entry be ! CreateArrayFromList(« key, value »).
|
||||
// iii. Append entry to properties.
|
||||
PropertyNameKind::KeyAndValue => properties.push( |
||||
Array::create_array_from_list( |
||||
[key_str.into(), self.get(key.clone(), context)?], |
||||
context, |
||||
) |
||||
.into(), |
||||
), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 5. Return properties.
|
||||
Ok(properties) |
||||
} |
||||
|
||||
pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult<usize> { |
||||
// 1. Assert: Type(obj) is Object.
|
||||
// 2. Return ℝ(? ToLength(? Get(obj, "length"))).
|
||||
self.get("length", context)?.to_length(context) |
||||
} |
||||
} |
||||
|
||||
impl Object { |
||||
/// Helper function for property insertion.
|
||||
#[inline] |
||||
pub(crate) fn insert<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
P: Into<PropertyDescriptor>, |
||||
{ |
||||
self.properties.insert(key.into(), property.into()) |
||||
} |
||||
|
||||
/// Helper function for property removal.
|
||||
#[inline] |
||||
pub(crate) fn remove(&mut self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
||||
self.properties.remove(key) |
||||
} |
||||
|
||||
/// Inserts a field in the object `properties` without checking if it's writable.
|
||||
///
|
||||
/// If a field was already in the object with the same name that a `Some` is returned
|
||||
/// with that field, otherwise None is retuned.
|
||||
#[inline] |
||||
pub fn insert_property<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
P: Into<PropertyDescriptor>, |
||||
{ |
||||
self.insert(key, property) |
||||
} |
||||
} |
@ -0,0 +1,248 @@
|
||||
use crate::{ |
||||
object::JsObject, |
||||
property::{PropertyDescriptor, PropertyKey}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; |
||||
|
||||
/// Definitions of the internal object methods for array exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects
|
||||
pub(crate) static ARRAY_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { |
||||
__define_own_property__: array_exotic_define_own_property, |
||||
..ORDINARY_INTERNAL_METHODS |
||||
}; |
||||
|
||||
/// Define an own property for an array exotic object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
|
||||
pub(crate) fn array_exotic_define_own_property( |
||||
obj: &JsObject, |
||||
key: PropertyKey, |
||||
desc: PropertyDescriptor, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
match key { |
||||
// 2. If P is "length", then
|
||||
PropertyKey::String(ref s) if s == "length" => { |
||||
// a. Return ? ArraySetLength(A, Desc).
|
||||
|
||||
// Abstract operation `ArraySetLength ( A, Desc )`
|
||||
//
|
||||
// https://tc39.es/ecma262/#sec-arraysetlength
|
||||
|
||||
// 1. If Desc.[[Value]] is absent, then
|
||||
let new_len_val = match desc.value() { |
||||
Some(value) => value, |
||||
_ => { |
||||
// a. Return OrdinaryDefineOwnProperty(A, "length", Desc).
|
||||
return super::ordinary_define_own_property( |
||||
obj, |
||||
"length".into(), |
||||
desc, |
||||
context, |
||||
); |
||||
} |
||||
}; |
||||
|
||||
// 3. Let newLen be ? ToUint32(Desc.[[Value]]).
|
||||
let new_len = new_len_val.to_u32(context)?; |
||||
|
||||
// 4. Let numberLen be ? ToNumber(Desc.[[Value]]).
|
||||
let number_len = new_len_val.to_number(context)?; |
||||
|
||||
// 5. If SameValueZero(newLen, numberLen) is false, throw a RangeError exception.
|
||||
#[allow(clippy::float_cmp)] |
||||
if new_len as f64 != number_len { |
||||
return Err(context.construct_range_error("bad length for array")); |
||||
} |
||||
|
||||
// 2. Let newLenDesc be a copy of Desc.
|
||||
// 6. Set newLenDesc.[[Value]] to newLen.
|
||||
let mut new_len_desc = PropertyDescriptor::builder() |
||||
.value(new_len) |
||||
.maybe_writable(desc.writable()) |
||||
.maybe_enumerable(desc.enumerable()) |
||||
.maybe_configurable(desc.configurable()); |
||||
|
||||
// 7. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
|
||||
let old_len_desc = |
||||
super::ordinary_get_own_property(obj, &"length".into(), context)?.unwrap(); |
||||
|
||||
// 8. Assert: ! IsDataDescriptor(oldLenDesc) is true.
|
||||
debug_assert!(old_len_desc.is_data_descriptor()); |
||||
|
||||
// 9. Assert: oldLenDesc.[[Configurable]] is false.
|
||||
debug_assert!(!old_len_desc.expect_configurable()); |
||||
|
||||
// 10. Let oldLen be oldLenDesc.[[Value]].
|
||||
let old_len = old_len_desc.expect_value(); |
||||
|
||||
// 11. If newLen ≥ oldLen, then
|
||||
if new_len >= old_len.to_u32(context)? { |
||||
// a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc).
|
||||
return super::ordinary_define_own_property( |
||||
obj, |
||||
"length".into(), |
||||
new_len_desc.build(), |
||||
context, |
||||
); |
||||
} |
||||
|
||||
// 12. If oldLenDesc.[[Writable]] is false, return false.
|
||||
if !old_len_desc.expect_writable() { |
||||
return Ok(false); |
||||
} |
||||
|
||||
// 13. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true.
|
||||
let new_writable = if new_len_desc.inner().writable().unwrap_or(true) { |
||||
true |
||||
} |
||||
// 14. Else,
|
||||
else { |
||||
// a. NOTE: Setting the [[Writable]] attribute to false is deferred in case any
|
||||
// elements cannot be deleted.
|
||||
// c. Set newLenDesc.[[Writable]] to true.
|
||||
new_len_desc = new_len_desc.writable(true); |
||||
|
||||
// b. Let newWritable be false.
|
||||
false |
||||
}; |
||||
|
||||
// 15. Let succeeded be ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
|
||||
// 16. If succeeded is false, return false.
|
||||
if !super::ordinary_define_own_property( |
||||
obj, |
||||
"length".into(), |
||||
new_len_desc.clone().build(), |
||||
context, |
||||
) |
||||
.unwrap() |
||||
{ |
||||
return Ok(false); |
||||
} |
||||
|
||||
// 17. For each own property key P of A that is an array index, whose numeric value is
|
||||
// greater than or equal to newLen, in descending numeric index order, do
|
||||
let ordered_keys = { |
||||
let mut keys: Vec<_> = obj |
||||
.borrow() |
||||
.properties |
||||
.index_property_keys() |
||||
.filter(|idx| new_len <= **idx && **idx < u32::MAX) |
||||
.copied() |
||||
.collect(); |
||||
keys.sort_unstable_by(|x, y| y.cmp(x)); |
||||
keys |
||||
}; |
||||
|
||||
for index in ordered_keys { |
||||
// a. Let deleteSucceeded be ! A.[[Delete]](P).
|
||||
// b. If deleteSucceeded is false, then
|
||||
if !obj.__delete__(&index.into(), context)? { |
||||
// i. Set newLenDesc.[[Value]] to ! ToUint32(P) + 1𝔽.
|
||||
new_len_desc = new_len_desc.value(index + 1); |
||||
|
||||
// ii. If newWritable is false, set newLenDesc.[[Writable]] to false.
|
||||
if !new_writable { |
||||
new_len_desc = new_len_desc.writable(false); |
||||
} |
||||
|
||||
// iii. Perform ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
|
||||
super::ordinary_define_own_property( |
||||
obj, |
||||
"length".into(), |
||||
new_len_desc.build(), |
||||
context, |
||||
) |
||||
.unwrap(); |
||||
|
||||
// iv. Return false.
|
||||
return Ok(false); |
||||
} |
||||
} |
||||
|
||||
// 18. If newWritable is false, then
|
||||
if !new_writable { |
||||
// a. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length",
|
||||
// PropertyDescriptor { [[Writable]]: false }).
|
||||
let succeeded = super::ordinary_define_own_property( |
||||
obj, |
||||
"length".into(), |
||||
PropertyDescriptor::builder().writable(false).build(), |
||||
context, |
||||
)?; |
||||
|
||||
// b. Assert: succeeded is true.
|
||||
debug_assert!(succeeded); |
||||
} |
||||
|
||||
// 19. Return true.
|
||||
Ok(true) |
||||
} |
||||
|
||||
// 3. Else if P is an array index, then
|
||||
PropertyKey::Index(index) if index < u32::MAX => { |
||||
// a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
|
||||
let old_len_desc = |
||||
super::ordinary_get_own_property(obj, &"length".into(), context)?.unwrap(); |
||||
|
||||
// b. Assert: ! IsDataDescriptor(oldLenDesc) is true.
|
||||
debug_assert!(old_len_desc.is_data_descriptor()); |
||||
|
||||
// c. Assert: oldLenDesc.[[Configurable]] is false.
|
||||
debug_assert!(!old_len_desc.expect_configurable()); |
||||
|
||||
// d. Let oldLen be oldLenDesc.[[Value]].
|
||||
// e. Assert: oldLen is a non-negative integral Number.
|
||||
// f. Let index be ! ToUint32(P).
|
||||
let old_len = old_len_desc.expect_value().to_u32(context).unwrap(); |
||||
|
||||
// g. If index ≥ oldLen and oldLenDesc.[[Writable]] is false, return false.
|
||||
if index >= old_len && !old_len_desc.expect_writable() { |
||||
return Ok(false); |
||||
} |
||||
|
||||
// h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc).
|
||||
if super::ordinary_define_own_property(obj, key, desc, context)? { |
||||
// j. If index ≥ oldLen, then
|
||||
if index >= old_len { |
||||
// i. Set oldLenDesc.[[Value]] to index + 1𝔽.
|
||||
let old_len_desc = PropertyDescriptor::builder() |
||||
.value(index + 1) |
||||
.maybe_writable(old_len_desc.writable()) |
||||
.maybe_enumerable(old_len_desc.enumerable()) |
||||
.maybe_configurable(old_len_desc.configurable()); |
||||
|
||||
// ii. Set succeeded to OrdinaryDefineOwnProperty(A, "length", oldLenDesc).
|
||||
let succeeded = super::ordinary_define_own_property( |
||||
obj, |
||||
"length".into(), |
||||
old_len_desc.into(), |
||||
context, |
||||
) |
||||
.unwrap(); |
||||
|
||||
// iii. Assert: succeeded is true.
|
||||
debug_assert!(succeeded); |
||||
} |
||||
|
||||
// k. Return true.
|
||||
Ok(true) |
||||
} else { |
||||
// i. If succeeded is false, return false.
|
||||
Ok(false) |
||||
} |
||||
} |
||||
// 4. Return OrdinaryDefineOwnProperty(A, P, Desc).
|
||||
_ => super::ordinary_define_own_property(obj, key, desc, context), |
||||
} |
||||
} |
@ -0,0 +1,861 @@
|
||||
//! This module defines the object internal methods.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
|
||||
|
||||
use crate::{ |
||||
object::JsObject, |
||||
property::{DescriptorKind, PropertyDescriptor, PropertyKey}, |
||||
value::JsValue, |
||||
BoaProfiler, Context, JsResult, |
||||
}; |
||||
|
||||
pub(super) mod array; |
||||
pub(super) mod string; |
||||
|
||||
impl JsObject { |
||||
/// Internal method `[[GetPrototypeOf]]`
|
||||
///
|
||||
/// Return either the prototype of this object or null.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
|
||||
#[inline] |
||||
#[track_caller] |
||||
pub(crate) fn __get_prototype_of__(&self, context: &mut Context) -> JsResult<JsValue> { |
||||
let func = self.borrow().data.internal_methods.__get_prototype_of__; |
||||
func(self, context) |
||||
} |
||||
|
||||
/// Internal method `[[SetPrototypeOf]]`
|
||||
///
|
||||
/// Set the property of a specified object to another object or `null`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v
|
||||
#[inline] |
||||
pub(crate) fn __set_prototype_of__( |
||||
&mut self, |
||||
val: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
let func = self.borrow().data.internal_methods.__set_prototype_of__; |
||||
func(self, val, context) |
||||
} |
||||
|
||||
/// Internal method `[[IsExtensible]]`
|
||||
///
|
||||
/// Check if the object is extensible.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible
|
||||
#[inline] |
||||
pub(crate) fn __is_extensible__(&self, context: &mut Context) -> JsResult<bool> { |
||||
let func = self.borrow().data.internal_methods.__is_extensible__; |
||||
func(self, context) |
||||
} |
||||
|
||||
/// Internal method `[[PreventExtensions]]`
|
||||
///
|
||||
/// Disable extensibility for this object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions
|
||||
#[inline] |
||||
pub(crate) fn __prevent_extensions__(&mut self, context: &mut Context) -> JsResult<bool> { |
||||
let func = self.borrow().data.internal_methods.__prevent_extensions__; |
||||
func(self, context) |
||||
} |
||||
|
||||
/// Internal method `[[GetOwnProperty]]`
|
||||
///
|
||||
/// Get the specified property of this object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p
|
||||
#[inline] |
||||
pub(crate) fn __get_own_property__( |
||||
&self, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<Option<PropertyDescriptor>> { |
||||
let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); |
||||
let func = self.borrow().data.internal_methods.__get_own_property__; |
||||
func(self, key, context) |
||||
} |
||||
|
||||
/// Internal method `[[DefineOwnProperty]]`
|
||||
///
|
||||
/// Define a new property of this object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc
|
||||
#[inline] |
||||
pub(crate) fn __define_own_property__( |
||||
&self, |
||||
key: PropertyKey, |
||||
desc: PropertyDescriptor, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
let func = self.borrow().data.internal_methods.__define_own_property__; |
||||
func(self, key, desc, context) |
||||
} |
||||
|
||||
/// Internal method `[[hasProperty]]`.
|
||||
///
|
||||
/// Check if the object or its prototype has the required property.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
|
||||
#[inline] |
||||
pub(crate) fn __has_property__( |
||||
&self, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
let func = self.borrow().data.internal_methods.__has_property__; |
||||
func(self, key, context) |
||||
} |
||||
|
||||
/// Internal method `[[Get]]`
|
||||
///
|
||||
/// Get the specified property of this object or its prototype.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver
|
||||
#[inline] |
||||
pub(crate) fn __get__( |
||||
&self, |
||||
key: &PropertyKey, |
||||
receiver: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
let func = self.borrow().data.internal_methods.__get__; |
||||
func(self, key, receiver, context) |
||||
} |
||||
|
||||
/// Internal method `[[Set]]`
|
||||
///
|
||||
/// Set the specified property of this object or its prototype to the provided value.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver
|
||||
#[inline] |
||||
pub(crate) fn __set__( |
||||
&self, |
||||
key: PropertyKey, |
||||
value: JsValue, |
||||
receiver: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
let _timer = BoaProfiler::global().start_event("Object::set", "object"); |
||||
let func = self.borrow().data.internal_methods.__set__; |
||||
func(self, key, value, receiver, context) |
||||
} |
||||
|
||||
/// Internal method `[[Delete]]`
|
||||
///
|
||||
/// Delete the specified own property of this object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p
|
||||
#[inline] |
||||
pub(crate) fn __delete__(&self, key: &PropertyKey, context: &mut Context) -> JsResult<bool> { |
||||
let func = self.borrow().data.internal_methods.__delete__; |
||||
func(self, key, context) |
||||
} |
||||
|
||||
/// Internal method `[[OwnPropertyKeys]]`
|
||||
///
|
||||
/// Get all the keys of the properties of this object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
|
||||
#[inline] |
||||
#[track_caller] |
||||
pub(crate) fn __own_property_keys__( |
||||
&self, |
||||
context: &mut Context, |
||||
) -> JsResult<Vec<PropertyKey>> { |
||||
let func = self.borrow().data.internal_methods.__own_property_keys__; |
||||
func(self, context) |
||||
} |
||||
} |
||||
|
||||
/// Definitions of the internal object methods for ordinary objects.
|
||||
///
|
||||
/// If you want to implement an exotic object, create a new `static InternalObjectMethods`
|
||||
/// overriding the desired internal methods with the definitions of the spec
|
||||
/// and set all other methods to the default ordinary values, if necessary.
|
||||
///
|
||||
/// E.g. `string::STRING_EXOTIC_INTERNAL_METHODS`
|
||||
///
|
||||
/// Then, reference this static in the creation phase of an `ObjectData`.
|
||||
///
|
||||
/// E.g. `ObjectData::string`
|
||||
pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { |
||||
__get_prototype_of__: ordinary_get_prototype_of, |
||||
__set_prototype_of__: ordinary_set_prototype_of, |
||||
__is_extensible__: ordinary_is_extensible, |
||||
__prevent_extensions__: ordinary_prevent_extensions, |
||||
__get_own_property__: ordinary_get_own_property, |
||||
__define_own_property__: ordinary_define_own_property, |
||||
__has_property__: ordinary_has_property, |
||||
__get__: ordinary_get, |
||||
__set__: ordinary_set, |
||||
__delete__: ordinary_delete, |
||||
__own_property_keys__: ordinary_own_property_keys, |
||||
}; |
||||
|
||||
/// The internal representation of the internal methods of a `JsObject`.
|
||||
///
|
||||
/// This struct allows us to dynamically dispatch exotic objects with their
|
||||
/// exclusive definitions of the internal methods, without having to
|
||||
/// resort to `dyn Object`.
|
||||
///
|
||||
/// For a guide on how to implement exotic internal methods, see `ORDINARY_INTERNAL_METHODS`.
|
||||
#[derive(Clone, Copy)] |
||||
pub(crate) struct InternalObjectMethods { |
||||
pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context) -> JsResult<JsValue>, |
||||
pub(crate) __set_prototype_of__: fn(&JsObject, JsValue, &mut Context) -> JsResult<bool>, |
||||
pub(crate) __is_extensible__: fn(&JsObject, &mut Context) -> JsResult<bool>, |
||||
pub(crate) __prevent_extensions__: fn(&JsObject, &mut Context) -> JsResult<bool>, |
||||
pub(crate) __get_own_property__: |
||||
fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<Option<PropertyDescriptor>>, |
||||
pub(crate) __define_own_property__: |
||||
fn(&JsObject, PropertyKey, PropertyDescriptor, &mut Context) -> JsResult<bool>, |
||||
pub(crate) __has_property__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<bool>, |
||||
pub(crate) __get__: fn(&JsObject, &PropertyKey, JsValue, &mut Context) -> JsResult<JsValue>, |
||||
pub(crate) __set__: |
||||
fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context) -> JsResult<bool>, |
||||
pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<bool>, |
||||
pub(crate) __own_property_keys__: fn(&JsObject, &mut Context) -> JsResult<Vec<PropertyKey>>, |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryGetPrototypeOf`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetprototypeof
|
||||
#[inline] |
||||
pub(crate) fn ordinary_get_prototype_of( |
||||
obj: &JsObject, |
||||
_context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Return O.[[Prototype]].
|
||||
Ok(obj.borrow().prototype.clone()) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinarySetPrototypeOf`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarysetprototypeof
|
||||
#[inline] |
||||
pub(crate) fn ordinary_set_prototype_of( |
||||
obj: &JsObject, |
||||
val: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. Assert: Either Type(V) is Object or Type(V) is Null.
|
||||
debug_assert!(val.is_object() || val.is_null()); |
||||
|
||||
// 2. Let current be O.[[Prototype]].
|
||||
let current = obj.__get_prototype_of__(context)?; |
||||
|
||||
// 3. If SameValue(V, current) is true, return true.
|
||||
if JsValue::same_value(¤t, &val) { |
||||
return Ok(true); |
||||
} |
||||
|
||||
// 4. Let extensible be O.[[Extensible]].
|
||||
// 5. If extensible is false, return false.
|
||||
if !obj.__is_extensible__(context)? { |
||||
return Ok(false); |
||||
} |
||||
|
||||
// 6. Let p be V.
|
||||
let mut p = val.clone(); |
||||
|
||||
// 7. Let done be false.
|
||||
let mut done = false; |
||||
|
||||
// 8. Repeat, while done is false,
|
||||
while !done { |
||||
match p { |
||||
// a. If p is null, set done to true.
|
||||
JsValue::Null => done = true, |
||||
JsValue::Object(ref proto) => { |
||||
// b. Else if SameValue(p, O) is true, return false.
|
||||
if JsObject::equals(proto, obj) { |
||||
return Ok(false); |
||||
} |
||||
// c. Else,
|
||||
// i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined
|
||||
// in 10.1.1, set done to true.
|
||||
else if proto.borrow().data.internal_methods.__get_prototype_of__ as usize |
||||
!= ordinary_get_prototype_of as usize |
||||
{ |
||||
done = true; |
||||
} |
||||
// ii. Else, set p to p.[[Prototype]].
|
||||
else { |
||||
p = proto.__get_prototype_of__(context)?; |
||||
} |
||||
} |
||||
_ => unreachable!(), |
||||
} |
||||
} |
||||
|
||||
// 9. Set O.[[Prototype]] to V.
|
||||
obj.borrow_mut().prototype = val; |
||||
|
||||
// 10. Return true.
|
||||
Ok(true) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryIsExtensible`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryisextensible
|
||||
#[inline] |
||||
pub(crate) fn ordinary_is_extensible(obj: &JsObject, _context: &mut Context) -> JsResult<bool> { |
||||
// 1. Return O.[[Extensible]].
|
||||
Ok(obj.borrow().extensible) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryPreventExtensions.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarypreventextensions
|
||||
#[inline] |
||||
pub(crate) fn ordinary_prevent_extensions( |
||||
obj: &JsObject, |
||||
_context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. Set O.[[Extensible]] to false.
|
||||
obj.borrow_mut().extensible = false; |
||||
|
||||
// 2. Return true.
|
||||
Ok(true) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryGetOwnProperty`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetownproperty
|
||||
#[inline] |
||||
pub(crate) fn ordinary_get_own_property( |
||||
obj: &JsObject, |
||||
key: &PropertyKey, |
||||
_context: &mut Context, |
||||
) -> JsResult<Option<PropertyDescriptor>> { |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. If O does not have an own property with key P, return undefined.
|
||||
// 3. Let D be a newly created Property Descriptor with no fields.
|
||||
// 4. Let X be O's own property whose key is P.
|
||||
// 5. If X is a data property, then
|
||||
// a. Set D.[[Value]] to the value of X's [[Value]] attribute.
|
||||
// b. Set D.[[Writable]] to the value of X's [[Writable]] attribute.
|
||||
// 6. Else,
|
||||
// a. Assert: X is an accessor property.
|
||||
// b. Set D.[[Get]] to the value of X's [[Get]] attribute.
|
||||
// c. Set D.[[Set]] to the value of X's [[Set]] attribute.
|
||||
// 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
|
||||
// 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
|
||||
// 9. Return D.
|
||||
Ok(obj.borrow().properties.get(key).cloned()) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryDefineOwnProperty`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty
|
||||
#[inline] |
||||
pub(crate) fn ordinary_define_own_property( |
||||
obj: &JsObject, |
||||
key: PropertyKey, |
||||
desc: PropertyDescriptor, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. Let current be ? O.[[GetOwnProperty]](P).
|
||||
let current = obj.__get_own_property__(&key, context)?; |
||||
|
||||
// 2. Let extensible be ? IsExtensible(O).
|
||||
let extensible = obj.__is_extensible__(context)?; |
||||
|
||||
// 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current).
|
||||
Ok(validate_and_apply_property_descriptor( |
||||
Some((obj, key)), |
||||
extensible, |
||||
desc, |
||||
current, |
||||
)) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryHasProperty`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasproperty
|
||||
#[inline] |
||||
pub(crate) fn ordinary_has_property( |
||||
obj: &JsObject, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let hasOwn be ? O.[[GetOwnProperty]](P).
|
||||
// 3. If hasOwn is not undefined, return true.
|
||||
if obj.__get_own_property__(key, context)?.is_some() { |
||||
Ok(true) |
||||
} else { |
||||
// 4. Let parent be ? O.[[GetPrototypeOf]]().
|
||||
let parent = obj.__get_prototype_of__(context)?; |
||||
|
||||
// 5. If parent is not null, then
|
||||
if let JsValue::Object(ref object) = parent { |
||||
// a. Return ? parent.[[HasProperty]](P).
|
||||
object.__has_property__(key, context) |
||||
} else { |
||||
// 6. Return false.
|
||||
Ok(false) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryGet`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryget
|
||||
#[inline] |
||||
pub(crate) fn ordinary_get( |
||||
obj: &JsObject, |
||||
key: &PropertyKey, |
||||
receiver: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
match obj.__get_own_property__(key, context)? { |
||||
// If desc is undefined, then
|
||||
None => { |
||||
// a. Let parent be ? O.[[GetPrototypeOf]]().
|
||||
if let Some(parent) = obj.__get_prototype_of__(context)?.as_object() { |
||||
// c. Return ? parent.[[Get]](P, Receiver).
|
||||
parent.__get__(key, receiver, context) |
||||
} |
||||
// b. If parent is null, return undefined.
|
||||
else { |
||||
Ok(JsValue::undefined()) |
||||
} |
||||
} |
||||
Some(ref desc) => match desc.kind() { |
||||
// 4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
|
||||
DescriptorKind::Data { |
||||
value: Some(value), .. |
||||
} => Ok(value.clone()), |
||||
// 5. Assert: IsAccessorDescriptor(desc) is true.
|
||||
// 6. Let getter be desc.[[Get]].
|
||||
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { |
||||
// 8. Return ? Call(getter, Receiver).
|
||||
context.call(get, &receiver, &[]) |
||||
} |
||||
// 7. If getter is undefined, return undefined.
|
||||
_ => Ok(JsValue::undefined()), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
/// Abstract operation `OrdinarySet`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryset
|
||||
#[inline] |
||||
pub(crate) fn ordinary_set( |
||||
obj: &JsObject, |
||||
key: PropertyKey, |
||||
value: JsValue, |
||||
receiver: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let ownDesc be ? O.[[GetOwnProperty]](P).
|
||||
// 3. Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).
|
||||
|
||||
// OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc )
|
||||
// https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ordinarysetwithowndescriptor
|
||||
|
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
let own_desc = if let Some(desc) = obj.__get_own_property__(&key, context)? { |
||||
desc |
||||
} |
||||
// 2. If ownDesc is undefined, then
|
||||
// a. Let parent be ? O.[[GetPrototypeOf]]().
|
||||
// b. If parent is not null, then
|
||||
else if let Some(ref mut parent) = obj.__get_prototype_of__(context)?.as_object() { |
||||
// i. Return ? parent.[[Set]](P, V, Receiver).
|
||||
return parent.__set__(key, value, receiver, context); |
||||
} |
||||
// c. Else,
|
||||
else { |
||||
// i. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true,
|
||||
// [[Enumerable]]: true, [[Configurable]]: true }.
|
||||
PropertyDescriptor::builder() |
||||
.value(JsValue::undefined()) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(true) |
||||
.build() |
||||
}; |
||||
|
||||
// 3. If IsDataDescriptor(ownDesc) is true, then
|
||||
if own_desc.is_data_descriptor() { |
||||
// a. If ownDesc.[[Writable]] is false, return false.
|
||||
if !own_desc.expect_writable() { |
||||
return Ok(false); |
||||
} |
||||
|
||||
let receiver = match receiver.as_object() { |
||||
Some(obj) => obj, |
||||
// b. If Type(Receiver) is not Object, return false.
|
||||
_ => return Ok(false), |
||||
}; |
||||
|
||||
// c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
|
||||
// d. If existingDescriptor is not undefined, then
|
||||
if let Some(ref existing_desc) = receiver.__get_own_property__(&key, context)? { |
||||
// i. If IsAccessorDescriptor(existingDescriptor) is true, return false.
|
||||
if existing_desc.is_accessor_descriptor() { |
||||
return Ok(false); |
||||
} |
||||
|
||||
// ii. If existingDescriptor.[[Writable]] is false, return false.
|
||||
if !existing_desc.expect_writable() { |
||||
return Ok(false); |
||||
} |
||||
|
||||
// iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }.
|
||||
// iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
|
||||
return receiver.__define_own_property__( |
||||
key, |
||||
PropertyDescriptor::builder().value(value).build(), |
||||
context, |
||||
); |
||||
} |
||||
// e. Else
|
||||
else { |
||||
// i. Assert: Receiver does not currently have a property P.
|
||||
// ii. Return ? CreateDataProperty(Receiver, P, V).
|
||||
return receiver.create_data_property(key, value, context); |
||||
} |
||||
} |
||||
|
||||
// 4. Assert: IsAccessorDescriptor(ownDesc) is true.
|
||||
debug_assert!(own_desc.is_accessor_descriptor()); |
||||
|
||||
// 5. Let setter be ownDesc.[[Set]].
|
||||
match own_desc.set() { |
||||
Some(set) if !set.is_undefined() => { |
||||
// 7. Perform ? Call(setter, Receiver, « V »).
|
||||
context.call(set, &receiver, &[value])?; |
||||
|
||||
// 8. Return true.
|
||||
Ok(true) |
||||
} |
||||
// 6. If setter is undefined, return false.
|
||||
_ => Ok(false), |
||||
} |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryDelete`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydelete
|
||||
#[inline] |
||||
pub(crate) fn ordinary_delete( |
||||
obj: &JsObject, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
Ok( |
||||
// 2. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
match obj.__get_own_property__(key, context)? { |
||||
// 4. If desc.[[Configurable]] is true, then
|
||||
Some(desc) if desc.expect_configurable() => { |
||||
// a. Remove the own property with name P from O.
|
||||
obj.borrow_mut().remove(key); |
||||
// b. Return true.
|
||||
true |
||||
} |
||||
// 5. Return false.
|
||||
Some(_) => false, |
||||
// 3. If desc is undefined, return true.
|
||||
None => true, |
||||
}, |
||||
) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryOwnPropertyKeys`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
|
||||
#[inline] |
||||
pub(crate) fn ordinary_own_property_keys( |
||||
obj: &JsObject, |
||||
_context: &mut Context, |
||||
) -> JsResult<Vec<PropertyKey>> { |
||||
// 1. Let keys be a new empty List.
|
||||
let mut keys = Vec::new(); |
||||
|
||||
let ordered_indexes = { |
||||
let mut indexes: Vec<_> = obj |
||||
.borrow() |
||||
.properties |
||||
.index_property_keys() |
||||
.copied() |
||||
.collect(); |
||||
indexes.sort_unstable(); |
||||
indexes |
||||
}; |
||||
|
||||
// 2. For each own property key P of O such that P is an array index, in ascending numeric index order, do
|
||||
// a. Add P as the last element of keys.
|
||||
keys.extend(ordered_indexes.into_iter().map(|idx| idx.into())); |
||||
|
||||
// 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do
|
||||
// a. Add P as the last element of keys.
|
||||
keys.extend( |
||||
obj.borrow() |
||||
.properties |
||||
.string_property_keys() |
||||
.cloned() |
||||
.map(|s| s.into()), |
||||
); |
||||
|
||||
// 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do
|
||||
// a. Add P as the last element of keys.
|
||||
keys.extend( |
||||
obj.borrow() |
||||
.properties |
||||
.symbol_property_keys() |
||||
.cloned() |
||||
.map(|sym| sym.into()), |
||||
); |
||||
|
||||
// 5. Return keys.
|
||||
Ok(keys) |
||||
} |
||||
|
||||
/// Abstract operation `IsCompatiblePropertyDescriptor`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-iscompatiblepropertydescriptor
|
||||
#[inline] |
||||
pub(crate) fn is_compatible_property_descriptor( |
||||
extensible: bool, |
||||
desc: PropertyDescriptor, |
||||
current: PropertyDescriptor, |
||||
) -> bool { |
||||
// 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current).
|
||||
validate_and_apply_property_descriptor(None, extensible, desc, Some(current)) |
||||
} |
||||
|
||||
/// Abstract operation `ValidateAndApplyPropertyDescriptor`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
|
||||
#[inline] |
||||
pub(crate) fn validate_and_apply_property_descriptor( |
||||
obj_and_key: Option<(&JsObject, PropertyKey)>, |
||||
extensible: bool, |
||||
desc: PropertyDescriptor, |
||||
current: Option<PropertyDescriptor>, |
||||
) -> bool { |
||||
// 1. Assert: If O is not undefined, then IsPropertyKey(P) is true.
|
||||
|
||||
let mut current = if let Some(own) = current { |
||||
own |
||||
} |
||||
// 2. If current is undefined, then
|
||||
else { |
||||
// a. If extensible is false, return false.
|
||||
if !extensible { |
||||
return false; |
||||
} |
||||
|
||||
// b. Assert: extensible is true.
|
||||
|
||||
if let Some((obj, key)) = obj_and_key { |
||||
obj.borrow_mut().properties.insert( |
||||
key, |
||||
// c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then
|
||||
if desc.is_generic_descriptor() || desc.is_data_descriptor() { |
||||
// i. If O is not undefined, create an own data property named P of
|
||||
// object O whose [[Value]], [[Writable]], [[Enumerable]], and
|
||||
// [[Configurable]] attribute values are described by Desc.
|
||||
// If the value of an attribute field of Desc is absent, the attribute
|
||||
// of the newly created property is set to its default value.
|
||||
desc.into_data_defaulted() |
||||
} |
||||
// d. Else,
|
||||
else { |
||||
// i. Assert: ! IsAccessorDescriptor(Desc) is true.
|
||||
|
||||
// ii. If O is not undefined, create an own accessor property named P
|
||||
// of object O whose [[Get]], [[Set]], [[Enumerable]], and [[Configurable]]
|
||||
// attribute values are described by Desc. If the value of an attribute field
|
||||
// of Desc is absent, the attribute of the newly created property is set to
|
||||
// its default value.
|
||||
desc.into_accessor_defaulted() |
||||
}, |
||||
); |
||||
} |
||||
|
||||
// e. Return true.
|
||||
return true; |
||||
}; |
||||
|
||||
// 3. If every field in Desc is absent, return true.
|
||||
if desc.is_empty() { |
||||
return true; |
||||
} |
||||
|
||||
// 4. If current.[[Configurable]] is false, then
|
||||
if !current.expect_configurable() { |
||||
// a. If Desc.[[Configurable]] is present and its value is true, return false.
|
||||
if matches!(desc.configurable(), Some(true)) { |
||||
return false; |
||||
} |
||||
|
||||
// b. If Desc.[[Enumerable]] is present and ! SameValue(Desc.[[Enumerable]], current.[[Enumerable]])
|
||||
// is false, return false.
|
||||
if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable()) |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// 5. If ! IsGenericDescriptor(Desc) is true, then
|
||||
if desc.is_generic_descriptor() { |
||||
// a. NOTE: No further validation is required.
|
||||
} |
||||
// 6. Else if ! SameValue(! IsDataDescriptor(current), ! IsDataDescriptor(Desc)) is false, then
|
||||
else if current.is_data_descriptor() != desc.is_data_descriptor() { |
||||
// a. If current.[[Configurable]] is false, return false.
|
||||
if !current.expect_configurable() { |
||||
return false; |
||||
} |
||||
|
||||
if obj_and_key.is_some() { |
||||
// b. If IsDataDescriptor(current) is true, then
|
||||
if current.is_data_descriptor() { |
||||
// i. If O is not undefined, convert the property named P of object O from a data
|
||||
// property to an accessor property. Preserve the existing values of the converted
|
||||
// property's [[Configurable]] and [[Enumerable]] attributes and set the rest of
|
||||
// the property's attributes to their default values.
|
||||
current = current.into_accessor_defaulted(); |
||||
} |
||||
// c. Else,
|
||||
else { |
||||
// i. If O is not undefined, convert the property named P of object O from an
|
||||
// accessor property to a data property. Preserve the existing values of the
|
||||
// converted property's [[Configurable]] and [[Enumerable]] attributes and set
|
||||
// the rest of the property's attributes to their default values.
|
||||
current = current.into_data_defaulted(); |
||||
} |
||||
} |
||||
} |
||||
// 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then
|
||||
else if current.is_data_descriptor() && desc.is_data_descriptor() { |
||||
// a. If current.[[Configurable]] is false and current.[[Writable]] is false, then
|
||||
if !current.expect_configurable() && !current.expect_writable() { |
||||
// i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, return false.
|
||||
if matches!(desc.writable(), Some(true)) { |
||||
return false; |
||||
} |
||||
// ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false.
|
||||
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value())) |
||||
{ |
||||
return false; |
||||
} |
||||
// iii. Return true.
|
||||
return true; |
||||
} |
||||
} |
||||
// 8. Else,
|
||||
// a. Assert: ! IsAccessorDescriptor(current) and ! IsAccessorDescriptor(Desc) are both true.
|
||||
// b. If current.[[Configurable]] is false, then
|
||||
else if !current.expect_configurable() { |
||||
// i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], current.[[Set]]) is false, return false.
|
||||
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) { |
||||
return false; |
||||
} |
||||
|
||||
// ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], current.[[Get]]) is false, return false.
|
||||
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) { |
||||
return false; |
||||
} |
||||
// iii. Return true.
|
||||
return true; |
||||
} |
||||
|
||||
// 9. If O is not undefined, then
|
||||
if let Some((obj, key)) = obj_and_key { |
||||
// a. For each field of Desc that is present, set the corresponding attribute of the
|
||||
// property named P of object O to the value of the field.
|
||||
current.fill_with(desc); |
||||
obj.borrow_mut().properties.insert(key, current); |
||||
} |
||||
|
||||
// 10. Return true.
|
||||
true |
||||
} |
@ -0,0 +1,189 @@
|
||||
use crate::{ |
||||
object::JsObject, |
||||
property::{PropertyDescriptor, PropertyKey}, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
|
||||
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; |
||||
|
||||
/// Definitions of the internal object methods for string exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects
|
||||
pub(crate) static STRING_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { |
||||
__get_own_property__: string_exotic_get_own_property, |
||||
__define_own_property__: string_exotic_define_own_property, |
||||
__own_property_keys__: string_exotic_own_property_keys, |
||||
..ORDINARY_INTERNAL_METHODS |
||||
}; |
||||
|
||||
/// Gets own property of 'String' exotic object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-getownproperty-p
|
||||
#[inline] |
||||
pub(crate) fn string_exotic_get_own_property( |
||||
obj: &JsObject, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<Option<PropertyDescriptor>> { |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let desc be OrdinaryGetOwnProperty(S, P).
|
||||
let desc = super::ordinary_get_own_property(obj, key, context)?; |
||||
|
||||
// 3. If desc is not undefined, return desc.
|
||||
if desc.is_some() { |
||||
Ok(desc) |
||||
} else { |
||||
// 4. Return ! StringGetOwnProperty(S, P).
|
||||
Ok(string_get_own_property(obj, key)) |
||||
} |
||||
} |
||||
|
||||
/// Defines own property of 'String' exotic object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-defineownproperty-p-desc
|
||||
#[inline] |
||||
pub(crate) fn string_exotic_define_own_property( |
||||
obj: &JsObject, |
||||
key: PropertyKey, |
||||
desc: PropertyDescriptor, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let stringDesc be ! StringGetOwnProperty(S, P).
|
||||
let string_desc = string_get_own_property(obj, &key); |
||||
|
||||
// 3. If stringDesc is not undefined, then
|
||||
if let Some(string_desc) = string_desc { |
||||
// a. Let extensible be S.[[Extensible]].
|
||||
let extensible = obj.borrow().extensible; |
||||
// b. Return ! IsCompatiblePropertyDescriptor(extensible, Desc, stringDesc).
|
||||
Ok(super::is_compatible_property_descriptor( |
||||
extensible, |
||||
desc, |
||||
string_desc, |
||||
)) |
||||
} else { |
||||
// 4. Return ! OrdinaryDefineOwnProperty(S, P, Desc).
|
||||
super::ordinary_define_own_property(obj, key, desc, context) |
||||
} |
||||
} |
||||
|
||||
/// Gets own property keys of 'String' exotic object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-ownpropertykeys
|
||||
#[inline] |
||||
pub(crate) fn string_exotic_own_property_keys( |
||||
obj: &JsObject, |
||||
_context: &mut Context, |
||||
) -> JsResult<Vec<PropertyKey>> { |
||||
let obj = obj.borrow(); |
||||
|
||||
// 2. Let str be O.[[StringData]].
|
||||
// 3. Assert: Type(str) is String.
|
||||
let string = obj |
||||
.as_string() |
||||
.expect("string exotic method should only be callable from string objects"); |
||||
// 4. Let len be the length of str.
|
||||
let len = string.encode_utf16().count(); |
||||
|
||||
// 1. Let keys be a new empty List.
|
||||
let mut keys = Vec::with_capacity(len); |
||||
|
||||
// 5. For each integer i starting with 0 such that i < len, in ascending order, do
|
||||
// a. Add ! ToString(𝔽(i)) as the last element of keys.
|
||||
keys.extend((0..len).into_iter().map(|idx| idx.into())); |
||||
|
||||
// 6. For each own property key P of O such that P is an array index
|
||||
// and ! ToIntegerOrInfinity(P) ≥ len, in ascending numeric index order, do
|
||||
// a. Add P as the last element of keys.
|
||||
let mut remaining_indices: Vec<_> = obj |
||||
.properties |
||||
.index_property_keys() |
||||
.cloned() |
||||
.filter(|idx| (*idx as usize) >= len) |
||||
.collect(); |
||||
remaining_indices.sort_unstable(); |
||||
keys.extend(remaining_indices.into_iter().map(|idx| idx.into())); |
||||
|
||||
// 7. For each own property key P of O such that Type(P) is String and P is not
|
||||
// an array index, in ascending chronological order of property creation, do
|
||||
// a. Add P as the last element of keys.
|
||||
keys.extend( |
||||
obj.properties |
||||
.string_property_keys() |
||||
.cloned() |
||||
.map(|s| s.into()), |
||||
); |
||||
|
||||
// 8. For each own property key P of O such that Type(P) is Symbol, in ascending
|
||||
// chronological order of property creation, do
|
||||
// a. Add P as the last element of keys.
|
||||
keys.extend( |
||||
obj.properties |
||||
.symbol_property_keys() |
||||
.cloned() |
||||
.map(|sym| sym.into()), |
||||
); |
||||
|
||||
// 9. Return keys.
|
||||
Ok(keys) |
||||
} |
||||
|
||||
/// StringGetOwnProperty abstract operation
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-stringgetownproperty
|
||||
#[allow(clippy::float_cmp)] |
||||
#[inline] |
||||
fn string_get_own_property(obj: &JsObject, key: &PropertyKey) -> Option<PropertyDescriptor> { |
||||
// 1. Assert: S is an Object that has a [[StringData]] internal slot.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. If Type(P) is not String, return undefined.
|
||||
// 4. Let index be ! CanonicalNumericIndexString(P).
|
||||
// 5. If index is undefined, return undefined.
|
||||
// 6. If IsIntegralNumber(index) is false, return undefined.
|
||||
// 7. If index is -0𝔽, return undefined.
|
||||
let pos = match key { |
||||
PropertyKey::Index(index) => *index as usize, |
||||
_ => return None, |
||||
}; |
||||
|
||||
// 8. Let str be S.[[StringData]].
|
||||
// 9. Assert: Type(str) is String.
|
||||
let string = obj |
||||
.borrow() |
||||
.as_string() |
||||
.expect("string exotic method should only be callable from string objects"); |
||||
|
||||
// 10. Let len be the length of str.
|
||||
// 11. If ℝ(index) < 0 or len ≤ ℝ(index), return undefined.
|
||||
// 12. Let resultStr be the String value of length 1, containing one code unit from str, specifically the code unit at index ℝ(index).
|
||||
let result_str = string |
||||
.encode_utf16() |
||||
.nth(pos) |
||||
.map(|c| JsValue::from(String::from_utf16_lossy(&[c])))?; |
||||
|
||||
// 13. Return the PropertyDescriptor { [[Value]]: resultStr, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }.
|
||||
let desc = PropertyDescriptor::builder() |
||||
.value(result_str) |
||||
.writable(false) |
||||
.enumerable(true) |
||||
.configurable(false) |
||||
.build(); |
||||
|
||||
Some(desc) |
||||
} |
@ -0,0 +1,487 @@
|
||||
use crate::{ |
||||
builtins::Array, |
||||
property::{PropertyDescriptor, PropertyKey, PropertyNameKind}, |
||||
symbol::WellKnownSymbols, |
||||
value::Type, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
|
||||
use super::JsObject; |
||||
|
||||
impl JsObject { |
||||
/// Get property from object or throw.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-o-p
|
||||
#[inline] |
||||
pub(crate) fn get<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
{ |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Return ? O.[[Get]](P, O).
|
||||
self.__get__(&key.into(), self.clone().into(), context) |
||||
} |
||||
|
||||
/// set property of object or throw if bool flag is passed.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
|
||||
#[inline] |
||||
pub(crate) fn set<K, V>( |
||||
&self, |
||||
key: K, |
||||
value: V, |
||||
throw: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
V: Into<JsValue>, |
||||
{ |
||||
let key = key.into(); |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Assert: Type(Throw) is Boolean.
|
||||
// 4. Let success be ? O.[[Set]](P, V, O).
|
||||
let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?; |
||||
// 5. If success is false and Throw is true, throw a TypeError exception.
|
||||
if !success && throw { |
||||
return Err( |
||||
context.construct_type_error(format!("cannot set non-writable property: {}", key)) |
||||
); |
||||
} |
||||
// 6. Return success.
|
||||
Ok(success) |
||||
} |
||||
|
||||
/// Create data property
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
|
||||
pub(crate) fn create_data_property<K, V>( |
||||
&self, |
||||
key: K, |
||||
value: V, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
V: Into<JsValue>, |
||||
{ |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
|
||||
let new_desc = PropertyDescriptor::builder() |
||||
.value(value) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(true); |
||||
// 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
|
||||
self.__define_own_property__(key.into(), new_desc.into(), context) |
||||
} |
||||
|
||||
// todo: CreateMethodProperty
|
||||
|
||||
/// Create data property or throw
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
|
||||
pub(crate) fn create_data_property_or_throw<K, V>( |
||||
&self, |
||||
key: K, |
||||
value: V, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
V: Into<JsValue>, |
||||
{ |
||||
let key = key.into(); |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Let success be ? CreateDataProperty(O, P, V).
|
||||
let success = self.create_data_property(key.clone(), value, context)?; |
||||
// 4. If success is false, throw a TypeError exception.
|
||||
if !success { |
||||
return Err(context.construct_type_error(format!("cannot redefine property: {}", key))); |
||||
} |
||||
// 5. Return success.
|
||||
Ok(success) |
||||
} |
||||
|
||||
/// Define property or throw.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
|
||||
#[inline] |
||||
pub(crate) fn define_property_or_throw<K, P>( |
||||
&self, |
||||
key: K, |
||||
desc: P, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
P: Into<PropertyDescriptor>, |
||||
{ |
||||
let key = key.into(); |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
|
||||
let success = self.__define_own_property__(key.clone(), desc.into(), context)?; |
||||
// 4. If success is false, throw a TypeError exception.
|
||||
if !success { |
||||
return Err(context.construct_type_error(format!("cannot redefine property: {}", key))); |
||||
} |
||||
// 5. Return success.
|
||||
Ok(success) |
||||
} |
||||
|
||||
/// Defines the property or throws a `TypeError` if the operation fails.
|
||||
///
|
||||
/// More information:
|
||||
/// - [EcmaScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
|
||||
#[inline] |
||||
pub(crate) fn delete_property_or_throw<K>( |
||||
&self, |
||||
key: K, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
{ |
||||
let key = key.into(); |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Let success be ? O.[[Delete]](P).
|
||||
let success = self.__delete__(&key, context)?; |
||||
// 4. If success is false, throw a TypeError exception.
|
||||
if !success { |
||||
return Err(context.construct_type_error(format!("cannot delete property: {}", key))); |
||||
} |
||||
// 5. Return success.
|
||||
Ok(success) |
||||
} |
||||
|
||||
/// Retrieves value of specific property, when the value of the property is expected to be a function.
|
||||
///
|
||||
/// More information:
|
||||
/// - [EcmaScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
|
||||
#[inline] |
||||
pub(crate) fn get_method<K>(&self, context: &mut Context, key: K) -> JsResult<Option<JsObject>> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
{ |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let func be ? GetV(V, P).
|
||||
let value = self.get(key, context)?; |
||||
|
||||
// 3. If func is either undefined or null, return undefined.
|
||||
if value.is_null_or_undefined() { |
||||
return Ok(None); |
||||
} |
||||
|
||||
// 4. If IsCallable(func) is false, throw a TypeError exception.
|
||||
// 5. Return func.
|
||||
match value.as_object() { |
||||
Some(object) if object.is_callable() => Ok(Some(object)), |
||||
_ => Err(context |
||||
.construct_type_error("value returned for property of object is not a function")), |
||||
} |
||||
} |
||||
|
||||
/// Check if object has property.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-hasproperty
|
||||
// NOTE: for now context is not used but it will in the future.
|
||||
#[inline] |
||||
pub(crate) fn has_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
{ |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Return ? O.[[HasProperty]](P).
|
||||
self.__has_property__(&key.into(), context) |
||||
} |
||||
|
||||
/// Check if object has an own property.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
|
||||
#[inline] |
||||
pub(crate) fn has_own_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool> |
||||
where |
||||
K: Into<PropertyKey>, |
||||
{ |
||||
let key = key.into(); |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: IsPropertyKey(P) is true.
|
||||
// 3. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
let desc = self.__get_own_property__(&key, context)?; |
||||
// 4. If desc is undefined, return false.
|
||||
// 5. Return true.
|
||||
Ok(desc.is_some()) |
||||
} |
||||
|
||||
/// Call this object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
|
||||
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
|
||||
#[track_caller] |
||||
#[inline] |
||||
pub(crate) fn call( |
||||
&self, |
||||
this: &JsValue, |
||||
args: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
self.call_construct(this, args, context, false) |
||||
} |
||||
|
||||
/// Construct an instance of this object with the specified arguments.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
|
||||
#[track_caller] |
||||
#[inline] |
||||
pub(crate) fn construct( |
||||
&self, |
||||
args: &[JsValue], |
||||
new_target: &JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
self.call_construct(new_target, args, context, true) |
||||
} |
||||
|
||||
// todo: SetIntegrityLevel
|
||||
|
||||
// todo: TestIntegrityLevel
|
||||
|
||||
pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult<usize> { |
||||
// 1. Assert: Type(obj) is Object.
|
||||
// 2. Return ℝ(? ToLength(? Get(obj, "length"))).
|
||||
self.get("length", context)?.to_length(context) |
||||
} |
||||
|
||||
/// `7.3.22 SpeciesConstructor ( O, defaultConstructor )`
|
||||
///
|
||||
/// The abstract operation SpeciesConstructor takes arguments O (an Object) and defaultConstructor (a constructor).
|
||||
/// It is used to retrieve the constructor that should be used to create new objects that are derived from O.
|
||||
/// defaultConstructor is the constructor to use if a constructor @@species property cannot be found starting from O.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-speciesconstructor
|
||||
pub(crate) fn species_constructor( |
||||
&self, |
||||
default_constructor: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Assert: Type(O) is Object.
|
||||
|
||||
// 2. Let C be ? Get(O, "constructor").
|
||||
let c = self.clone().get("constructor", context)?; |
||||
|
||||
// 3. If C is undefined, return defaultConstructor.
|
||||
if c.is_undefined() { |
||||
return Ok(default_constructor); |
||||
} |
||||
|
||||
// 4. If Type(C) is not Object, throw a TypeError exception.
|
||||
if !c.is_object() { |
||||
return context.throw_type_error("property 'constructor' is not an object"); |
||||
} |
||||
|
||||
// 5. Let S be ? Get(C, @@species).
|
||||
let s = c.get_field(WellKnownSymbols::species(), context)?; |
||||
|
||||
// 6. If S is either undefined or null, return defaultConstructor.
|
||||
if s.is_null_or_undefined() { |
||||
return Ok(default_constructor); |
||||
} |
||||
|
||||
// 7. If IsConstructor(S) is true, return S.
|
||||
// 8. Throw a TypeError exception.
|
||||
if let Some(obj) = s.as_object() { |
||||
if obj.is_constructable() { |
||||
Ok(s) |
||||
} else { |
||||
context.throw_type_error("property 'constructor' is not a constructor") |
||||
} |
||||
} else { |
||||
context.throw_type_error("property 'constructor' is not an object") |
||||
} |
||||
} |
||||
|
||||
/// It is used to iterate over names of object's keys.
|
||||
///
|
||||
/// More information:
|
||||
/// - [EcmaScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-enumerableownpropertynames
|
||||
pub(crate) fn enumerable_own_property_names( |
||||
&self, |
||||
kind: PropertyNameKind, |
||||
context: &mut Context, |
||||
) -> JsResult<Vec<JsValue>> { |
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
|
||||
let own_keys = self.__own_property_keys__(context)?; |
||||
// 3. Let properties be a new empty List.
|
||||
let mut properties = vec![]; |
||||
|
||||
// 4. For each element key of ownKeys, do
|
||||
for key in own_keys { |
||||
// a. If Type(key) is String, then
|
||||
let key_str = match &key { |
||||
PropertyKey::String(s) => Some(s.clone()), |
||||
PropertyKey::Index(i) => Some(i.to_string().into()), |
||||
_ => None, |
||||
}; |
||||
|
||||
if let Some(key_str) = key_str { |
||||
// i. Let desc be ? O.[[GetOwnProperty]](key).
|
||||
let desc = self.__get_own_property__(&key, context)?; |
||||
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
|
||||
if let Some(desc) = desc { |
||||
if desc.expect_enumerable() { |
||||
match kind { |
||||
// 1. If kind is key, append key to properties.
|
||||
PropertyNameKind::Key => properties.push(key_str.into()), |
||||
// 2. Else,
|
||||
// a. Let value be ? Get(O, key).
|
||||
// b. If kind is value, append value to properties.
|
||||
PropertyNameKind::Value => { |
||||
properties.push(self.get(key.clone(), context)?) |
||||
} |
||||
// c. Else,
|
||||
// i. Assert: kind is key+value.
|
||||
// ii. Let entry be ! CreateArrayFromList(« key, value »).
|
||||
// iii. Append entry to properties.
|
||||
PropertyNameKind::KeyAndValue => properties.push( |
||||
Array::create_array_from_list( |
||||
[key_str.into(), self.get(key.clone(), context)?], |
||||
context, |
||||
) |
||||
.into(), |
||||
), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 5. Return properties.
|
||||
Ok(properties) |
||||
} |
||||
|
||||
// todo: GetFunctionRealm
|
||||
|
||||
// todo: CopyDataProperties
|
||||
|
||||
// todo: PrivateElementFind
|
||||
|
||||
// todo: PrivateFieldAdd
|
||||
|
||||
// todo: PrivateMethodOrAccessorAdd
|
||||
|
||||
// todo: PrivateGet
|
||||
|
||||
// todo: PrivateSet
|
||||
|
||||
// todo: DefineField
|
||||
|
||||
// todo: InitializeInstanceElements
|
||||
} |
||||
|
||||
impl JsValue { |
||||
// todo: GetV
|
||||
|
||||
// todo: GetMethod
|
||||
|
||||
/// It is used to create List value whose elements are provided by the indexed properties of
|
||||
/// self.
|
||||
///
|
||||
/// More information:
|
||||
/// - [EcmaScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createlistfromarraylike
|
||||
pub(crate) fn create_list_from_array_like( |
||||
&self, |
||||
element_types: &[Type], |
||||
context: &mut Context, |
||||
) -> JsResult<Vec<JsValue>> { |
||||
// 1. If elementTypes is not present, set elementTypes to « Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object ».
|
||||
let types = if element_types.is_empty() { |
||||
&[ |
||||
Type::Undefined, |
||||
Type::Null, |
||||
Type::Boolean, |
||||
Type::String, |
||||
Type::Symbol, |
||||
Type::Number, |
||||
Type::BigInt, |
||||
Type::Object, |
||||
] |
||||
} else { |
||||
element_types |
||||
}; |
||||
|
||||
// 2. If Type(obj) is not Object, throw a TypeError exception.
|
||||
let obj = self |
||||
.as_object() |
||||
.ok_or_else(|| context.construct_type_error("cannot create list from a primitive"))?; |
||||
|
||||
// 3. Let len be ? LengthOfArrayLike(obj).
|
||||
let len = obj.length_of_array_like(context)?; |
||||
|
||||
// 4. Let list be a new empty List.
|
||||
let mut list = Vec::with_capacity(len); |
||||
|
||||
// 5. Let index be 0.
|
||||
// 6. Repeat, while index < len,
|
||||
for index in 0..len { |
||||
// a. Let indexName be ! ToString(𝔽(index)).
|
||||
// b. Let next be ? Get(obj, indexName).
|
||||
let next = obj.get(index, context)?; |
||||
// c. If Type(next) is not an element of elementTypes, throw a TypeError exception.
|
||||
if !types.contains(&next.get_type()) { |
||||
return Err(context.construct_type_error("bad type")); |
||||
} |
||||
// d. Append next as the last element of list.
|
||||
list.push(next.clone()); |
||||
// e. Set index to index + 1.
|
||||
} |
||||
|
||||
// 7. Return list.
|
||||
Ok(list) |
||||
} |
||||
} |
Loading…
Reference in new issue