Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1338 lines
46 KiB

use crate::{
builtins::{
function::{BoundFunction, ClassFieldDefinition, OrdinaryFunction},
Array, Proxy,
},
context::intrinsics::{StandardConstructor, StandardConstructors},
error::JsNativeError,
native_function::NativeFunctionObject,
object::{JsObject, PrivateElement, PrivateName, CONSTRUCTOR, PROTOTYPE},
property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
realm::Realm,
string::common::StaticJsStrings,
value::Type,
Context, JsResult, JsSymbol, JsValue,
};
use super::internal_methods::InternalMethodContext;
/// Object integrity level.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntegrityLevel {
/// Sealed object integrity level.
///
/// Preventing new properties from being added to it and marking all existing
/// properties as non-configurable. Values of present properties can still be
/// changed as long as they are writable.
Sealed,
/// Frozen object integrity level
///
/// A frozen object can no longer be changed; freezing an object prevents new
/// properties from being added to it, existing properties from being removed,
/// prevents changing the enumerability, configurability, or writability of
/// existing properties, and prevents the values of existing properties from
/// being changed. In addition, freezing an object also prevents its prototype
/// from being changed.
Frozen,
}
impl IntegrityLevel {
/// Returns `true` if the integrity level is sealed.
#[must_use]
pub const fn is_sealed(&self) -> bool {
matches!(self, Self::Sealed)
}
/// Returns `true` if the integrity level is frozen.
#[must_use]
pub const fn is_frozen(&self) -> bool {
matches!(self, Self::Frozen)
}
}
impl JsObject {
/// Check if object is extensible.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isextensible-o
#[inline]
pub fn is_extensible(&self, context: &mut Context) -> JsResult<bool> {
// 1. Return ? O.[[IsExtensible]]().
self.__is_extensible__(context)
}
/// Get property from object or throw.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-o-p
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(),
&mut InternalMethodContext::new(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
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(),
&mut InternalMethodContext::new(context),
)?;
// 5. If success is false and Throw is true, throw a TypeError exception.
if !success && throw {
return Err(JsNativeError::typ()
.with_message(format!("cannot set non-writable property: {key}"))
.into());
}
// 6. Return success.
Ok(success)
}
/// Create data property
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createdataproperty
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(),
&mut InternalMethodContext::new(context),
)
}
/// Create data property
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createdataproperty
pub(crate) fn create_data_property_with_slot<K, V>(
&self,
key: K,
value: V,
context: &mut InternalMethodContext<'_>,
) -> 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-createdatapropertyorthrow
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(JsNativeError::typ()
.with_message(format!("cannot redefine property: {key}"))
.into());
}
// 5. Return success.
Ok(success)
}
/// Create non-enumerable data property or throw
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createnonenumerabledatapropertyinfallibly
pub(crate) fn create_non_enumerable_data_property_or_throw<K, V>(
&self,
key: K,
value: V,
context: &mut Context,
) where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
// 1. Assert: O is an ordinary, extensible object with no non-configurable properties.
// 2. Let newDesc be the PropertyDescriptor {
// [[Value]]: V,
// [[Writable]]: true,
// [[Enumerable]]: false,
// [[Configurable]]: true
// }.
let new_desc = PropertyDescriptorBuilder::new()
.value(value)
.writable(true)
.enumerable(false)
.configurable(true)
.build();
// 3. Perform ! DefinePropertyOrThrow(O, P, newDesc).
self.define_property_or_throw(key, new_desc, context)
.expect("should not fail according to spec");
// 4. Return unused.
}
/// Define property or throw.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
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,
desc.into(),
&mut InternalMethodContext::new(context),
)?;
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(JsNativeError::typ()
.with_message(format!("cannot redefine property: {key}"))
.into());
}
// 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-deletepropertyorthrow
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, &mut InternalMethodContext::new(context))?;
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(JsNativeError::typ()
.with_message(format!("cannot delete non-configurable property: {key}"))
.into());
}
// 5. Return success.
Ok(success)
}
/// Check if object has property.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-hasproperty
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).
self.__has_property__(&key.into(), &mut InternalMethodContext::new(context))
}
/// Check if object has an own property.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
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, &mut InternalMethodContext::new(context))?;
// 4. If desc is undefined, return false.
// 5. Return true.
Ok(desc.is_some())
}
/// `Call ( F, V [ , argumentsList ] )`
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-call
#[track_caller]
#[inline]
pub fn call(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// SKIP: 1. If argumentsList is not present, set argumentsList to a new empty List.
// SKIP: 2. If IsCallable(F) is false, throw a TypeError exception.
// NOTE(HalidOdat): For object's that are not callable we implement a special __call__ internal method
// that throws on call.
context.vm.push(this.clone()); // this
context.vm.push(self.clone()); // func
let argument_count = args.len();
context.vm.push_values(args);
// 3. Return ? F.[[Call]](V, argumentsList).
let frame_index = context.vm.frames.len();
let is_complete = self.__call__(argument_count).resolve(context)?;
if is_complete {
return Ok(context.vm.pop());
}
context.vm.frames[frame_index].set_exit_early(true);
let result = context.run().consume();
context.vm.pop_frame().expect("frame must exist");
result
}
/// `Construct ( F [ , argumentsList [ , newTarget ] ] )`
///
/// Construct an instance of this object with the specified arguments.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-construct
#[track_caller]
#[inline]
pub fn construct(
&self,
args: &[JsValue],
new_target: Option<&Self>,
context: &mut Context,
) -> JsResult<Self> {
// 1. If newTarget is not present, set newTarget to F.
let new_target = new_target.unwrap_or(self);
context.vm.push(self.clone()); // func
let argument_count = args.len();
context.vm.push_values(args);
context.vm.push(new_target.clone());
// 2. If argumentsList is not present, set argumentsList to a new empty List.
// 3. Return ? F.[[Construct]](argumentsList, newTarget).
let frame_index = context.vm.frames.len();
let is_complete = self.__construct__(argument_count).resolve(context)?;
if is_complete {
let result = context.vm.pop();
return Ok(result
.as_object()
.expect("construct value should be an object")
.clone());
}
context.vm.frames[frame_index].set_exit_early(true);
let result = context.run().consume();
context.vm.pop_frame().expect("frame must exist");
Ok(result?.as_object().expect("should be an object").clone())
}
/// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-setintegritylevel
pub fn set_integrity_level(
&self,
level: IntegrityLevel,
context: &mut Context,
) -> JsResult<bool> {
// 1. Assert: Type(O) is Object.
// 2. Assert: level is either sealed or frozen.
// 3. Let status be ? O.[[PreventExtensions]]().
let status = self.__prevent_extensions__(&mut InternalMethodContext::new(context))?;
// 4. If status is false, return false.
if !status {
return Ok(false);
}
// 5. Let keys be ? O.[[OwnPropertyKeys]]().
let keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
match level {
// 6. If level is sealed, then
IntegrityLevel::Sealed => {
// a. For each element k of keys, do
for k in keys {
// i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor { [[Configurable]]: false }).
self.define_property_or_throw(
k,
PropertyDescriptor::builder().configurable(false).build(),
context,
)?;
}
}
// 7. Else,
// a. Assert: level is frozen.
IntegrityLevel::Frozen => {
// b. For each element k of keys, do
for k in keys {
// i. Let currentDesc be ? O.[[GetOwnProperty]](k).
let current_desc =
self.__get_own_property__(&k, &mut InternalMethodContext::new(context))?;
// ii. If currentDesc is not undefined, then
if let Some(current_desc) = current_desc {
// 1. If IsAccessorDescriptor(currentDesc) is true, then
let desc = if current_desc.is_accessor_descriptor() {
// a. Let desc be the PropertyDescriptor { [[Configurable]]: false }.
PropertyDescriptor::builder().configurable(false).build()
// 2. Else,
} else {
// a. Let desc be the PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }.
PropertyDescriptor::builder()
.configurable(false)
.writable(false)
.build()
};
// 3. Perform ? DefinePropertyOrThrow(O, k, desc).
self.define_property_or_throw(k, desc, context)?;
}
}
}
}
// 8. Return true.
Ok(true)
}
/// Check if the object is [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-testintegritylevel
pub fn test_integrity_level(
&self,
level: IntegrityLevel,
context: &mut Context,
) -> JsResult<bool> {
// 1. Assert: Type(O) is Object.
// 2. Assert: level is either sealed or frozen.
// 3. Let extensible be ? IsExtensible(O).
let extensible = self.is_extensible(context)?;
// 4. If extensible is true, return false.
if extensible {
return Ok(false);
}
// 5. NOTE: If the object is extensible, none of its properties are examined.
// 6. Let keys be ? O.[[OwnPropertyKeys]]().
let keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
// 7. For each element k of keys, do
for k in keys {
// a. Let currentDesc be ? O.[[GetOwnProperty]](k).
let current_desc =
self.__get_own_property__(&k, &mut InternalMethodContext::new(context))?;
// b. If currentDesc is not undefined, then
if let Some(current_desc) = current_desc {
// i. If currentDesc.[[Configurable]] is true, return false.
if current_desc.expect_configurable() {
return Ok(false);
}
// ii. If level is frozen and IsDataDescriptor(currentDesc) is true, then
if level.is_frozen() && current_desc.is_data_descriptor() {
// 1. If currentDesc.[[Writable]] is true, return false.
if current_desc.expect_writable() {
return Ok(false);
}
}
}
}
// 8. Return true.
Ok(true)
}
/// Abstract operation [`LengthOfArrayLike ( obj )`][spec].
///
/// Returns the value of the "length" property of an array-like object.
///
/// [spec]: https://tc39.es/ecma262/#sec-lengthofarraylike
pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult<u64> {
// 1. Assert: Type(obj) is Object.
// NOTE: This is an optimization, most of the cases that `LengthOfArrayLike` will be called
// is for arrays. The "length" property of an array is stored in the first index.
if self.is_array() {
let borrowed_object = self.borrow();
// NOTE: using `to_u32` instead of `to_length` is an optimization,
// since arrays are limited to [0, 2^32 - 1] range.
return borrowed_object.properties().storage[0]
.to_u32(context)
.map(u64::from);
}
// 2. Return ℝ(? ToLength(? Get(obj, "length"))).
self.get(StaticJsStrings::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<F>(
&self,
default_constructor: F,
context: &mut Context,
) -> JsResult<Self>
where
F: FnOnce(&StandardConstructors) -> &StandardConstructor,
{
// 1. Assert: Type(O) is Object.
// 2. Let C be ? Get(O, "constructor").
let c = self.get(CONSTRUCTOR, context)?;
// 3. If C is undefined, return defaultConstructor.
if c.is_undefined() {
return Ok(default_constructor(context.intrinsics().constructors()).constructor());
}
// 4. If Type(C) is not Object, throw a TypeError exception.
let c = c.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("property 'constructor' is not an object")
})?;
// 5. Let S be ? Get(C, @@species).
let s = c.get(JsSymbol::species(), context)?;
// 6. If S is either undefined or null, return defaultConstructor.
if s.is_null_or_undefined() {
return Ok(default_constructor(context.intrinsics().constructors()).constructor());
}
// 7. If IsConstructor(S) is true, return S.
if let Some(s) = s.as_constructor() {
return Ok(s.clone());
}
// 8. Throw a TypeError exception.
Err(JsNativeError::typ()
.with_message("property 'constructor' is not a constructor")
.into())
}
/// 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__(&mut InternalMethodContext::new(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.get().to_string().into()),
PropertyKey::Symbol(_) => None,
};
if let Some(key_str) = key_str {
// i. Let desc be ? O.[[GetOwnProperty]](key).
let desc =
self.__get_own_property__(&key, &mut InternalMethodContext::new(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)
}
/// Abstract operation `GetMethod ( V, P )`
///
/// Retrieves the value of a 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
pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<Option<Self>>
where
K: Into<PropertyKey>,
{
// Note: The spec specifies this function for JsValue.
// It is implemented for JsObject for convenience.
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let func be ? GetV(V, P).
match &self.__get__(
&key.into(),
self.clone().into(),
&mut InternalMethodContext::new(context),
)? {
// 3. If func is either undefined or null, return undefined.
JsValue::Undefined | JsValue::Null => Ok(None),
// 5. Return func.
JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())),
// 4. If IsCallable(func) is false, throw a TypeError exception.
_ => Err(JsNativeError::typ()
.with_message("value returned for property of object is not a function")
.into()),
}
}
/// Abstract operation `IsArray ( argument )`
///
/// Check if a value is an array.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isarray
pub(crate) fn is_array_abstract(&self) -> JsResult<bool> {
// Note: The spec specifies this function for JsValue.
// It is implemented for JsObject for convenience.
// 2. If argument is an Array exotic object, return true.
if self.is_array() {
return Ok(true);
}
// 3. If argument is a Proxy exotic object, then
let object = self.borrow();
if let Some(proxy) = object.downcast_ref::<Proxy>() {
// a. If argument.[[ProxyHandler]] is null, throw a TypeError exception.
// b. Let target be argument.[[ProxyTarget]].
let (target, _) = proxy.try_data()?;
// c. Return ? IsArray(target).
return target.is_array_abstract();
}
// 4. Return false.
Ok(false)
}
/// Abstract operation [`GetFunctionRealm`][spec].
///
/// [spec]: https://tc39.es/ecma262/#sec-getfunctionrealm
pub(crate) fn get_function_realm(&self, context: &mut Context) -> JsResult<Realm> {
let constructor = self.borrow();
if let Some(fun) = constructor.downcast_ref::<OrdinaryFunction>() {
return Ok(fun.realm().clone());
}
if let Some(f) = constructor.downcast_ref::<NativeFunctionObject>() {
return Ok(f.realm.clone().unwrap_or_else(|| context.realm().clone()));
}
if let Some(bound) = constructor.downcast_ref::<BoundFunction>() {
let fun = bound.target_function().clone();
drop(constructor);
return fun.get_function_realm(context);
}
if let Some(proxy) = constructor.downcast_ref::<Proxy>() {
let (fun, _) = proxy.try_data()?;
drop(constructor);
return fun.get_function_realm(context);
}
Ok(context.realm().clone())
}
// todo: CopyDataProperties
/// Abstract operation `PrivateElementFind ( O, P )`
///
/// Get the private element from an object.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-privateelementfind
#[allow(clippy::similar_names)]
pub(crate) fn private_element_find(
&self,
name: &PrivateName,
is_getter: bool,
is_setter: bool,
) -> Option<PrivateElement> {
// 1. If O.[[PrivateElements]] contains a PrivateElement whose [[Key]] is P, then
for (key, value) in &self.borrow().private_elements {
if key == name {
// a. Let entry be that PrivateElement.
// b. Return entry.
if let PrivateElement::Accessor { getter, setter } = value {
if getter.is_some() && is_getter || setter.is_some() && is_setter {
return Some(value.clone());
}
} else {
return Some(value.clone());
}
}
}
// 2. Return empty.
None
}
/// Abstract operation `PrivateFieldAdd ( O, P, value )`
///
/// Add private field to an object.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-privatefieldadd
pub(crate) fn private_field_add(
&self,
name: &PrivateName,
value: JsValue,
context: &mut Context,
) -> JsResult<()> {
// 1. If the host is a web browser, then
// a. Perform ? HostEnsureCanAddPrivateElement(O).
context
.host_hooks()
.ensure_can_add_private_element(self, context)?;
// 2. Let entry be PrivateElementFind(O, P).
let entry = self.private_element_find(name, false, false);
// 3. If entry is not empty, throw a TypeError exception.
if entry.is_some() {
return Err(JsNativeError::typ()
.with_message("Private field already exists on prototype")
.into());
}
// 4. Append PrivateElement { [[Key]]: P, [[Kind]]: field, [[Value]]: value } to O.[[PrivateElements]].
self.borrow_mut()
.private_elements
.push((name.clone(), PrivateElement::Field(value)));
// 5. Return unused.
Ok(())
}
/// Abstract operation `PrivateMethodOrAccessorAdd ( O, method )`
///
/// Add private method or accessor to an object.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-privatemethodoraccessoradd
pub(crate) fn private_method_or_accessor_add(
&self,
name: &PrivateName,
method: &PrivateElement,
context: &mut Context,
) -> JsResult<()> {
// 1. Assert: method.[[Kind]] is either method or accessor.
assert!(matches!(
method,
PrivateElement::Method(_) | PrivateElement::Accessor { .. }
));
let (getter, setter) = if let PrivateElement::Accessor { getter, setter } = method {
(getter.is_some(), setter.is_some())
} else {
(false, false)
};
// 2. If the host is a web browser, then
// a. Perform ? HostEnsureCanAddPrivateElement(O).
context
.host_hooks()
.ensure_can_add_private_element(self, context)?;
// 3. Let entry be PrivateElementFind(O, method.[[Key]]).
let entry = self.private_element_find(name, getter, setter);
// 4. If entry is not empty, throw a TypeError exception.
if entry.is_some() {
return Err(JsNativeError::typ()
.with_message("Private method already exists on prototype")
.into());
}
// 5. Append method to O.[[PrivateElements]].
self.borrow_mut()
.append_private_element(name.clone(), method.clone());
// 6. Return unused.
Ok(())
}
/// Abstract operation `PrivateGet ( O, P )`
///
/// Get the value of a private element.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-privateget
pub(crate) fn private_get(
&self,
name: &PrivateName,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let entry be PrivateElementFind(O, P).
let entry = self.private_element_find(name, true, true);
match &entry {
// 2. If entry is empty, throw a TypeError exception.
None => Err(JsNativeError::typ()
.with_message("Private element does not exist on object")
.into()),
// 3. If entry.[[Kind]] is field or method, then
// a. Return entry.[[Value]].
Some(PrivateElement::Field(value)) => Ok(value.clone()),
Some(PrivateElement::Method(value)) => Ok(value.clone().into()),
// 4. Assert: entry.[[Kind]] is accessor.
Some(PrivateElement::Accessor { getter, .. }) => {
// 5. If entry.[[Get]] is undefined, throw a TypeError exception.
// 6. Let getter be entry.[[Get]].
let getter = getter.as_ref().ok_or_else(|| {
JsNativeError::typ()
.with_message("private property was defined without a getter")
})?;
// 7. Return ? Call(getter, O).
getter.call(&self.clone().into(), &[], context)
}
}
}
/// Abstract operation `PrivateSet ( O, P, value )`
///
/// Set the value of a private element.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-privateset
pub(crate) fn private_set(
&self,
name: &PrivateName,
value: JsValue,
context: &mut Context,
) -> JsResult<()> {
// 1. Let entry be PrivateElementFind(O, P).
// Note: This function is inlined here for mutable access.
let mut object_mut = self.borrow_mut();
let entry = object_mut
.private_elements
.iter_mut()
.find_map(|(key, value)| if key == name { Some(value) } else { None });
match entry {
// 2. If entry is empty, throw a TypeError exception.
None => {
return Err(JsNativeError::typ()
.with_message("Private element does not exist on object")
.into())
}
// 3. If entry.[[Kind]] is field, then
// a. Set entry.[[Value]] to value.
Some(PrivateElement::Field(field)) => {
*field = value;
}
// 4. Else if entry.[[Kind]] is method, then
// a. Throw a TypeError exception.
Some(PrivateElement::Method(_)) => {
return Err(JsNativeError::typ()
.with_message("private method is not writable")
.into())
}
// 5. Else,
Some(PrivateElement::Accessor { setter, .. }) => {
// a. Assert: entry.[[Kind]] is accessor.
// b. If entry.[[Set]] is undefined, throw a TypeError exception.
// c. Let setter be entry.[[Set]].
let setter = setter.clone().ok_or_else(|| {
JsNativeError::typ()
.with_message("private property was defined without a setter")
})?;
// d. Perform ? Call(setter, O, « value »).
drop(object_mut);
setter.call(&self.clone().into(), &[value], context)?;
}
}
// 6. Return unused.
Ok(())
}
/// Abstract operation `DefineField ( receiver, fieldRecord )`
///
/// Define a field on an object.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-definefield
pub(crate) fn define_field(
&self,
field_record: &ClassFieldDefinition,
context: &mut Context,
) -> JsResult<()> {
// 2. Let initializer be fieldRecord.[[Initializer]].
let initializer = match field_record {
ClassFieldDefinition::Public(_, function)
| ClassFieldDefinition::Private(_, function) => function,
};
// 3. If initializer is not empty, then
// a. Let initValue be ? Call(initializer, receiver).
// 4. Else, let initValue be undefined.
let init_value = initializer.call(&self.clone().into(), &[], context)?;
match field_record {
// 1. Let fieldName be fieldRecord.[[Name]].
// 5. If fieldName is a Private Name, then
ClassFieldDefinition::Private(field_name, _) => {
// a. Perform ? PrivateFieldAdd(receiver, fieldName, initValue).
self.private_field_add(field_name, init_value, context)?;
}
// 1. Let fieldName be fieldRecord.[[Name]].
// 6. Else,
ClassFieldDefinition::Public(field_name, _) => {
// a. Assert: IsPropertyKey(fieldName) is true.
// b. Perform ? CreateDataPropertyOrThrow(receiver, fieldName, initValue).
self.create_data_property_or_throw(field_name.clone(), init_value, context)?;
}
}
// 7. Return unused.
Ok(())
}
/// Abstract operation `InitializeInstanceElements ( O, constructor )`
///
/// Add private methods and fields from a class constructor to an object.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-initializeinstanceelements
pub(crate) fn initialize_instance_elements(
&self,
constructor: &Self,
context: &mut Context,
) -> JsResult<()> {
let constructor_function = constructor
.downcast_ref::<OrdinaryFunction>()
.expect("class constructor must be function object");
// 1. Let methods be the value of constructor.[[PrivateMethods]].
// 2. For each PrivateElement method of methods, do
for (name, method) in constructor_function.get_private_methods() {
// a. Perform ? PrivateMethodOrAccessorAdd(O, method).
self.private_method_or_accessor_add(name, method, context)?;
}
// 3. Let fields be the value of constructor.[[Fields]].
// 4. For each element fieldRecord of fields, do
for field_record in constructor_function.get_fields() {
// a. Perform ? DefineField(O, fieldRecord).
self.define_field(field_record, context)?;
}
// 5. Return unused.
Ok(())
}
/// Abstract operation `Invoke ( V, P [ , argumentsList ] )`
///
/// Calls a method property of an ECMAScript object.
///
/// Equivalent to the [`JsValue::invoke`] method, but specialized for objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-invoke
pub(crate) fn invoke<K>(
&self,
key: K,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue>
where
K: Into<PropertyKey>,
{
let this_value: JsValue = self.clone().into();
// 1. If argumentsList is not present, set argumentsList to a new empty List.
// 2. Let func be ? GetV(V, P).
let func = self.__get__(
&key.into(),
this_value.clone(),
&mut InternalMethodContext::new(context),
)?;
// 3. Return ? Call(func, V, argumentsList)
func.call(&this_value, args, context)
}
}
impl JsValue {
/// Abstract operation `GetV ( V, P )`.
///
/// Retrieves the value of a specific property of an ECMAScript language value. If the value is
/// not an object, the property lookup is performed using a wrapper object appropriate for the
/// type of the value.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getv
pub(crate) fn get_v<K>(&self, key: K, context: &mut Context) -> JsResult<Self>
where
K: Into<PropertyKey>,
{
// 1. Let O be ? ToObject(V).
let o = self.to_object(context)?;
// 2. Return ? O.[[Get]](P, V).
o.__get__(
&key.into(),
self.clone(),
&mut InternalMethodContext::new(context),
)
}
/// Abstract operation `GetMethod ( V, P )`
///
/// Retrieves the value of a 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
pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<Option<JsObject>>
where
K: Into<PropertyKey>,
{
// Note: The spec specifies this function for JsValue.
// The main part of the function is implemented for JsObject.
self.to_object(context)?.get_method(key, context)
}
/// 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<Self>> {
// 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(|| {
JsNativeError::typ().with_message("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 as usize);
// 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(JsNativeError::typ().with_message("bad type").into());
}
// d. Append next as the last element of list.
list.push(next.clone());
// e. Set index to index + 1.
}
// 7. Return list.
Ok(list)
}
/// Abstract operation [`Call ( F, V [ , argumentsList ] )`][call].
///
/// Calls this value if the value is a callable object.
///
/// # Note
///
/// It is almost always better to try to obtain a callable object first with [`JsValue::as_callable`],
/// then calling [`JsObject::call`], since that allows reusing the unwrapped function for other
/// operations. This method is only an utility method for when the spec directly uses `Call`
/// without using the value as a proper object.
///
/// [call]: https://tc39.es/ecma262/#sec-call
#[inline]
pub(crate) fn call(&self, this: &Self, args: &[Self], context: &mut Context) -> JsResult<Self> {
let Some(object) = self.as_object() else {
return Err(JsNativeError::typ()
.with_message(format!(
"value with type `{}` is not callable",
self.type_of()
))
.into());
};
object.call(this, args, context)
}
/// Abstract operation `( V, P [ , argumentsList ] )`
///
/// Calls a method property of an ECMAScript language value.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-invoke
pub(crate) fn invoke<K>(&self, key: K, args: &[Self], context: &mut Context) -> JsResult<Self>
where
K: Into<PropertyKey>,
{
// 1. If argumentsList is not present, set argumentsList to a new empty List.
// 2. Let func be ? GetV(V, P).
let func = self.get_v(key, context)?;
// 3. Return ? Call(func, V, argumentsList)
func.call(self, args, context)
}
/// Abstract operation `OrdinaryHasInstance ( C, O )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance
pub fn ordinary_has_instance(
function: &Self,
object: &Self,
context: &mut Context,
) -> JsResult<bool> {
// 1. If IsCallable(C) is false, return false.
let Some(function) = function.as_callable() else {
return Ok(false);
};
// 2. If C has a [[BoundTargetFunction]] internal slot, then
if let Some(bound_function) = function.downcast_ref::<BoundFunction>() {
// a. Let BC be C.[[BoundTargetFunction]].
// b. Return ? InstanceofOperator(O, BC).
return object.instance_of(&bound_function.target_function().clone().into(), context);
}
let Some(mut object) = object.as_object().cloned() else {
// 3. If Type(O) is not Object, return false.
return Ok(false);
};
// 4. Let P be ? Get(C, "prototype").
let prototype = function.get(PROTOTYPE, context)?;
// 5. If Type(P) is not Object, throw a TypeError exception.
let prototype = prototype.as_object().ok_or_else(|| {
JsNativeError::typ()
.with_message("function has non-object prototype in instanceof check")
})?;
// 6. Repeat,
loop {
// a. Set O to ? O.[[GetPrototypeOf]]().
object = match object.__get_prototype_of__(&mut InternalMethodContext::new(context))? {
Some(obj) => obj,
// b. If O is null, return false.
None => return Ok(false),
};
// c. If SameValue(P, O) is true, return true.
if JsObject::equals(&object, prototype) {
return Ok(true);
}
}
}
}