Browse Source

Combine `HasProperty` and `Get` operations when possible (#3883)

pull/3884/head
raskad 5 months ago committed by GitHub
parent
commit
9738d44749
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 157
      core/engine/src/builtins/array/mod.rs
  2. 6
      core/engine/src/builtins/error/mod.rs
  3. 39
      core/engine/src/builtins/function/arguments.rs
  4. 7
      core/engine/src/builtins/intl/locale/utils.rs
  5. 25
      core/engine/src/builtins/proxy/mod.rs
  6. 20
      core/engine/src/builtins/typed_array/builtin.rs
  7. 41
      core/engine/src/builtins/typed_array/object.rs
  8. 6
      core/engine/src/environments/runtime/mod.rs
  9. 8
      core/engine/src/error.rs
  10. 88
      core/engine/src/module/namespace.rs
  11. 81
      core/engine/src/object/internal_methods/mod.rs
  12. 24
      core/engine/src/object/jsobject.rs
  13. 22
      core/engine/src/object/operations.rs

157
core/engine/src/builtins/array/mod.rs

@ -811,11 +811,9 @@ impl Array {
for k in 0..len {
// 1. Let P be ! ToString(𝔽(k)).
// 2. Let exists be ? HasProperty(E, P).
let exists = item.has_property(k, context)?;
// 3. If exists is true, then
if exists {
// a. Let subElement be ? Get(E, P).
let sub_element = item.get(k, context)?;
// 3.a. Let subElement be ? Get(E, P).
if let Some(sub_element) = item.try_get(k, context)? {
// b. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), subElement).
arr.create_data_property_or_throw(n, sub_element, context)?;
}
@ -960,11 +958,9 @@ impl Array {
// a. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// b. Let kPresent be ? HasProperty(O, Pk).
let present = o.has_property(pk, context)?;
// c. If kPresent is true, then
if present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(pk, context)?;
// c.i. Let kValue be ? Get(O, Pk).
if let Some(k_value) = o.try_get(pk, context)? {
// ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
let this_arg = args.get_or_undefined(1);
callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?;
@ -1093,47 +1089,37 @@ impl Array {
// Skipped: b. Let upperP be ! ToString(𝔽(upper)).
// Skipped: c. Let lowerP be ! ToString(𝔽(lower)).
// d. Let lowerExists be ? HasProperty(O, lowerP).
let lower_exists = o.has_property(lower, context)?;
// e. If lowerExists is true, then
let lower_value = if lower_exists {
// i. Let lowerValue be ? Get(O, lowerP).
o.get(lower, context)?
} else {
JsValue::undefined()
};
// e.i. Let lowerValue be ? Get(O, lowerP).
let lower_value = o.try_get(lower, context)?;
// f. Let upperExists be ? HasProperty(O, upperP).
let upper_exists = o.has_property(upper, context)?;
// g. If upperExists is true, then
let upper_value = if upper_exists {
// i. Let upperValue be ? Get(O, upperP).
o.get(upper, context)?
} else {
JsValue::undefined()
};
match (lower_exists, upper_exists) {
// g.i. Let upperValue be ? Get(O, upperP).
let upper_value = o.try_get(upper, context)?;
match (lower_value, upper_value) {
// h. If lowerExists is true and upperExists is true, then
(true, true) => {
(Some(lower_value), Some(upper_value)) => {
// i. Perform ? Set(O, lowerP, upperValue, true).
o.set(lower, upper_value, true, context)?;
// ii. Perform ? Set(O, upperP, lowerValue, true).
o.set(upper, lower_value, true, context)?;
}
// i. Else if lowerExists is false and upperExists is true, then
(false, true) => {
(None, Some(upper_value)) => {
// i. Perform ? Set(O, lowerP, upperValue, true).
o.set(lower, upper_value, true, context)?;
// ii. Perform ? DeletePropertyOrThrow(O, upperP).
o.delete_property_or_throw(upper, context)?;
}
// j. Else if lowerExists is true and upperExists is false, then
(true, false) => {
(Some(lower_value), None) => {
// i. Perform ? DeletePropertyOrThrow(O, lowerP).
o.delete_property_or_throw(lower, context)?;
// ii. Perform ? Set(O, upperP, lowerValue, true).
o.set(upper, lower_value, true, context)?;
}
// k. Else,
(false, false) => {
(None, None) => {
// i. Assert: lowerExists and upperExists are both false.
// ii. No action is required.
}
@ -1255,11 +1241,9 @@ impl Array {
// b. Let to be ! ToString(𝔽(k - 1)).
let to = k - 1;
// c. Let fromPresent be ? HasProperty(O, from).
let from_present = o.has_property(from, context)?;
// d. If fromPresent is true, then
if from_present {
// i. Let fromVal be ? Get(O, from).
let from_val = o.get(from, context)?;
// d.i. Let fromVal be ? Get(O, from).
if let Some(from_val) = o.try_get(from, context)? {
// ii. Perform ? Set(O, to, fromVal, true).
o.set(to, from_val, true, context)?;
// e. Else,
@ -1318,11 +1302,9 @@ impl Array {
// ii. Let to be ! ToString(𝔽(k + argCount - 1)).
let to = k + arg_count - 1;
// iii. Let fromPresent be ? HasProperty(O, from).
let from_present = o.has_property(from, context)?;
// iv. If fromPresent is true, then
if from_present {
// 1. Let fromValue be ? Get(O, from).
let from_value = o.get(from, context)?;
// iv.1. Let fromValue be ? Get(O, from).
if let Some(from_value) = o.try_get(from, context)? {
// 2. Perform ? Set(O, to, fromValue, true).
o.set(to, from_value, true, context)?;
// v. Else,
@ -1383,11 +1365,9 @@ impl Array {
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(k, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(k, context)?;
// c.i. Let kValue be ? Get(O, Pk).
if let Some(k_value) = o.try_get(k, context)? {
// ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let test_result = callback
.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?
@ -1438,11 +1418,9 @@ impl Array {
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let k_present be ? HasProperty(O, Pk).
let k_present = o.has_property(k, context)?;
// c. If k_present is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(k, context)?;
// c.i. Let kValue be ? Get(O, Pk).
if let Some(k_value) = o.try_get(k, context)? {
// ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
let mapped_value =
callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?;
@ -1514,11 +1492,9 @@ impl Array {
// 10. Repeat, while k < len,
while k < len {
// a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))).
let k_present = o.has_property(k, context)?;
// b. If kPresent is true, then
if k_present {
// i. Let elementK be ? Get(O, ! ToString(𝔽(k))).
let element_k = o.get(k, context)?;
// b.i. Let elementK be ? Get(O, ! ToString(𝔽(k))).
if let Some(element_k) = o.try_get(k, context)? {
// ii. Let same be IsStrictlyEqual(searchElement, elementK).
// iii. If same is true, return 𝔽(k).
if search_element.strict_equals(&element_k) {
@ -1590,11 +1566,9 @@ impl Array {
// 8. Repeat, while k ≥ 0,
while k >= 0 {
// a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))).
let k_present = o.has_property(k, context)?;
// b. If kPresent is true, then
if k_present {
// i. Let elementK be ? Get(O, ! ToString(𝔽(k))).
let element_k = o.get(k, context)?;
// b.i. Let elementK be ? Get(O, ! ToString(𝔽(k))).
if let Some(element_k) = o.try_get(k, context)? {
// ii. Let same be IsStrictlyEqual(searchElement, elementK).
// iii. If same is true, return 𝔽(k).
if JsValue::strict_equals(search_element, &element_k) {
@ -1907,12 +1881,9 @@ impl Array {
let p = source_index;
// b. Let exists be ? HasProperty(source, P).
let exists = source.has_property(p, context)?;
// c. If exists is true, then
if exists {
// i. Let element be Get(source, P)
let mut element = source.get(p, context)?;
// c.i. Let element be Get(source, P)
if let Some(mut element) = source.try_get(p, context)? {
// ii. If mapperFunction is present, then
if let Some(mapper_function) = mapper_function {
// 1. Set element to ? Call(mapperFunction, thisArg, <<element, sourceIndex, source>>)
@ -2156,11 +2127,9 @@ impl Array {
// a. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(pk, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(pk, context)?;
// c.i. Let kValue be ? Get(O, Pk).
if let Some(k_value) = o.try_get(pk, context)? {
// ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), kValue).
a.create_data_property_or_throw(n, k_value, context)?;
}
@ -2327,10 +2296,8 @@ impl Array {
for k in 0..actual_delete_count {
// a. Let from be ! ToString(𝔽(actualStart + k)).
// b. If ? HasProperty(O, from) is true, then
if o.has_property(actual_start + k, context)? {
// i. Let fromValue be ? Get(O, from).
let from_value = o.get(actual_start + k, context)?;
// b.i. Let fromValue be ? Get(O, from).
if let Some(from_value) = o.try_get(actual_start + k, context)? {
// ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(k)), fromValue).
arr.create_data_property_or_throw(k, from_value, context)?;
}
@ -2356,10 +2323,8 @@ impl Array {
let to = k + item_count;
// iii. If ? HasProperty(O, from) is true, then
if o.has_property(from, context)? {
// 1. Let fromValue be ? Get(O, from).
let from_value = o.get(from, context)?;
// iii.1. Let fromValue be ? Get(O, from).
if let Some(from_value) = o.try_get(from, context)? {
// 2. Perform ? Set(O, to, fromValue, true).
o.set(to, from_value, true, context)?;
} else {
@ -2391,10 +2356,8 @@ impl Array {
let to = k + item_count;
// iii. If ? HasProperty(O, from) is true, then
if o.has_property(from, context)? {
// 1. Let fromValue be ? Get(O, from).
let from_value = o.get(from, context)?;
// iii.1. Let fromValue be ? Get(O, from).
if let Some(from_value) = o.try_get(from, context)? {
// 2. Perform ? Set(O, to, fromValue, true).
o.set(to, from_value, true, context)?;
}
@ -2555,10 +2518,8 @@ impl Array {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kPresent be ? HasProperty(O, Pk).
// c. If kPresent is true, then
if o.has_property(idx, context)? {
// i. Let kValue be ? Get(O, Pk).
let element = o.get(idx, context)?;
// c.i. Let kValue be ? Get(O, Pk).
if let Some(element) = o.try_get(idx, context)? {
let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())];
// ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
@ -2612,11 +2573,9 @@ impl Array {
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(k, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(k, context)?;
// c.i. Let kValue be ? Get(O, Pk).
if let Some(k_value) = o.try_get(k, context)? {
// ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let this_arg = args.get_or_undefined(1);
let test_result = callback
@ -2868,11 +2827,13 @@ impl Array {
// i. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// ii. Set kPresent to ? HasProperty(O, Pk).
k_present = o.has_property(pk, context)?;
// iii. If kPresent is true, then
if k_present {
// 1. Set accumulator to ? Get(O, Pk).
accumulator = o.get(pk, context)?;
// iii.1. Set accumulator to ? Get(O, Pk).
if let Some(v) = o.try_get(pk, context)? {
accumulator = v;
k_present = true;
} else {
k_present = false;
}
// iv. Set k to k + 1.
k += 1;
@ -2890,11 +2851,9 @@ impl Array {
// a. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(pk, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(pk, context)?;
// c.i. Let kValue be ? Get(O, Pk).
if let Some(k_value) = o.try_get(pk, context)? {
// ii. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »).
accumulator = callback.call(
&JsValue::undefined(),
@ -2962,11 +2921,13 @@ impl Array {
// i. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// ii. Set kPresent to ? HasProperty(O, Pk).
k_present = o.has_property(pk, context)?;
// iii. If kPresent is true, then
if k_present {
// 1. Set accumulator to ? Get(O, Pk).
accumulator = o.get(pk, context)?;
// iii.1. Set accumulator to ? Get(O, Pk).
if let Some(v) = o.try_get(pk, context)? {
k_present = true;
accumulator = v;
} else {
k_present = false;
}
// iv. Set k to k - 1.
k -= 1;
@ -2984,11 +2945,9 @@ impl Array {
// a. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(pk, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(pk, context)?;
// c.i. Let kValue be ? Get(O, Pk).
if let Some(k_value) = o.try_get(pk, context)? {
// ii. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »).
accumulator = callback.call(
&JsValue::undefined(),
@ -3071,11 +3030,9 @@ impl Array {
let to_key = to;
// c. Let fromPresent be ? HasProperty(O, fromKey).
let from_present = o.has_property(from_key, context)?;
// d. If fromPresent is true, then
if from_present {
// i. Let fromVal be ? Get(O, fromKey).
let from_val = o.get(from_key, context)?;
// d.i. Let fromVal be ? Get(O, fromKey).
if let Some(from_val) = o.try_get(from_key, context)? {
// ii. Perform ? Set(O, toKey, fromVal, true).
o.set(to_key, from_val, true, context)?;
// e. Else,

6
core/engine/src/builtins/error/mod.rs

@ -210,11 +210,9 @@ impl Error {
context: &mut Context,
) -> JsResult<()> {
// 1. If Type(options) is Object and ? HasProperty(options, "cause") is true, then
// 1.a. Let cause be ? Get(options, "cause").
if let Some(options) = options.as_object() {
if options.has_property(js_str!("cause"), context)? {
// a. Let cause be ? Get(options, "cause").
let cause = options.get(js_str!("cause"), context)?;
if let Some(cause) = options.try_get(js_str!("cause"), context)? {
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause).
o.create_non_enumerable_data_property_or_throw(js_str!("cause"), cause, context);
}

39
core/engine/src/builtins/function/arguments.rs

@ -3,7 +3,8 @@ use crate::{
object::{
internal_methods::{
ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property,
ordinary_set, InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
ordinary_set, ordinary_try_get, InternalMethodContext, InternalObjectMethods,
ORDINARY_INTERNAL_METHODS,
},
JsObject,
},
@ -87,6 +88,7 @@ impl JsData for MappedArguments {
static METHODS: InternalObjectMethods = InternalObjectMethods {
__get_own_property__: arguments_exotic_get_own_property,
__define_own_property__: arguments_exotic_define_own_property,
__try_get__: arguments_exotic_try_get,
__get__: arguments_exotic_get,
__set__: arguments_exotic_set,
__delete__: arguments_exotic_delete,
@ -394,6 +396,41 @@ pub(crate) fn arguments_exotic_define_own_property(
Ok(true)
}
/// Internal optimization method for `Arguments` exotic objects.
///
/// This method combines the internal methods `OrdinaryHasProperty` and `[[Get]]`.
///
/// More information:
/// - [ECMAScript reference OrdinaryHasProperty][spec0]
/// - [ECMAScript reference Get][spec1]
///
/// [spec0]: https://tc39.es/ecma262/#sec-ordinaryhasproperty
/// [spec1]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-get-p-receiver
pub(crate) fn arguments_exotic_try_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<JsValue>> {
if let PropertyKey::Index(index) = key {
// 1. Let map be args.[[ParameterMap]].
// 2. Let isMapped be ! HasOwnProperty(map, P).
if let Some(value) = obj
.downcast_ref::<MappedArguments>()
.expect("arguments exotic method must only be callable from arguments objects")
.get(index.get())
{
// a. Assert: map contains a formal parameter mapping for P.
// b. Return Get(map, P).
return Ok(Some(value));
}
}
// 3. If isMapped is false, then
// a. Return ? OrdinaryGet(args, P, Receiver).
ordinary_try_get(obj, key, receiver, context)
}
/// `[[Get]]` for arguments exotic objects.
///
/// More information:

7
core/engine/src/builtins/intl/locale/utils.rs

@ -93,12 +93,9 @@ pub(crate) fn canonicalize_locale_list(
for k in 0..len {
// a. Let Pk be ToString(k).
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(k, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(k, context)?;
// c.i. Let kValue be ? Get(O, Pk).
if let Some(k_value) = o.try_get(k, context)? {
// ii. If Type(kValue) is not String or Object, throw a TypeError exception.
if !(k_value.is_object() || k_value.is_string()) {
return Err(JsNativeError::typ()

25
core/engine/src/builtins/proxy/mod.rs

@ -53,6 +53,7 @@ impl JsData for Proxy {
__get_own_property__: proxy_exotic_get_own_property,
__define_own_property__: proxy_exotic_define_own_property,
__has_property__: proxy_exotic_has_property,
__try_get__: proxy_exotic_try_get,
__get__: proxy_exotic_get,
__set__: proxy_exotic_set,
__delete__: proxy_exotic_delete,
@ -759,6 +760,30 @@ pub(crate) fn proxy_exotic_has_property(
Ok(boolean_trap_result)
}
/// Internal optimization method for `Proxy` exotic objects.
///
/// This method combines the internal methods `[[HasProperty]]` and `[[Get]]`.
///
/// More information:
/// - [ECMAScript reference HasProperty][spec0]
/// - [ECMAScript reference Get][spec1]
///
/// [spec0]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-hasproperty-p
/// [spec1]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver
pub(crate) fn proxy_exotic_try_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<JsValue>> {
// Note: For now, this just calls the normal methods. Could be optimized further.
if proxy_exotic_has_property(obj, key, context)? {
Ok(Some(proxy_exotic_get(obj, key, receiver, context)?))
} else {
Ok(None)
}
}
/// `10.5.8 [[Get]] ( P, Receiver )`
///
/// More information:

20
core/engine/src/builtins/typed_array/builtin.rs

@ -1155,15 +1155,9 @@ impl BuiltinTypedArray {
let ta = ta.upcast();
for k in k..len {
// a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))).
let k_present = ta
.has_property(k, context)
.expect("HasProperty cannot fail here");
// b. If kPresent is true, then
if k_present {
// i. Let elementK be ! Get(O, ! ToString(𝔽(k))).
let element_k = ta.get(k, context).expect("Get cannot fail here");
// b.i. Let elementK be ! Get(O, ! ToString(𝔽(k))).
if let Some(element_k) = ta.try_get(k, context).expect("Get cannot fail here") {
// ii. Let same be IsStrictlyEqual(searchElement, elementK).
// iii. If same is true, return 𝔽(k).
if args.get_or_undefined(0).strict_equals(&element_k) {
@ -1296,15 +1290,9 @@ impl BuiltinTypedArray {
let ta = ta.upcast();
for k in (0..k).rev() {
// a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))).
let k_present = ta
.has_property(k, context)
.expect("HasProperty cannot fail here");
// b. If kPresent is true, then
if k_present {
// i. Let elementK be ! Get(O, ! ToString(𝔽(k))).
let element_k = ta.get(k, context).expect("Get cannot fail here");
// b.i. Let elementK be ! Get(O, ! ToString(𝔽(k))).
if let Some(element_k) = ta.try_get(k, context).expect("Get cannot fail here") {
// ii. Let same be IsStrictlyEqual(searchElement, elementK).
// iii. If same is true, return 𝔽(k).
if args.get_or_undefined(0).strict_equals(&element_k) {

41
core/engine/src/builtins/typed_array/object.rs

@ -7,8 +7,8 @@ use crate::{
object::{
internal_methods::{
ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property,
ordinary_has_property, ordinary_set, InternalMethodContext, InternalObjectMethods,
ORDINARY_INTERNAL_METHODS,
ordinary_has_property, ordinary_set, ordinary_try_get, InternalMethodContext,
InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
},
JsData, JsObject,
},
@ -42,6 +42,7 @@ impl JsData for TypedArray {
__get_own_property__: typed_array_exotic_get_own_property,
__has_property__: typed_array_exotic_has_property,
__define_own_property__: typed_array_exotic_define_own_property,
__try_get__: typed_array_exotic_try_get,
__get__: typed_array_exotic_get,
__set__: typed_array_exotic_set,
__delete__: typed_array_exotic_delete,
@ -413,6 +414,42 @@ pub(crate) fn typed_array_exotic_define_own_property(
ordinary_define_own_property(obj, key, desc, context)
}
/// Internal optimization method for `TypedArray` exotic objects.
///
/// This method combines the internal methods `[[HasProperty]]` and `[[Get]]`.
///
/// More information:
/// - [ECMAScript reference HasProperty][spec0]
/// - [ECMAScript reference Get][spec1]
///
/// [spec0]: https://tc39.es/ecma262/#sec-typedarray-hasproperty
/// [spec1]: https://tc39.es/ecma262/#sec-typedarray-get
pub(crate) fn typed_array_exotic_try_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<JsValue>> {
let p = match key {
PropertyKey::String(key) => {
// 1.a. Let numericIndex be CanonicalNumericIndexString(P).
canonical_numeric_index_string(key)
}
PropertyKey::Index(index) => Some(index.get().into()),
PropertyKey::Symbol(_) => None,
};
// 1. If P is a String, then
// 1.b. If numericIndex is not undefined, then
if let Some(numeric_index) = p {
// i. Return IntegerIndexedElementGet(O, numericIndex).
return Ok(typed_array_get_element(obj, numeric_index));
}
// 2. Return ? OrdinaryGet(O, P, Receiver).
ordinary_try_get(obj, key, receiver, context)
}
/// Internal method `[[Get]]` for `TypedArray` exotic objects.
///
/// More information:

6
core/engine/src/environments/runtime/mod.rs

@ -621,11 +621,7 @@ impl Context {
if locator.global {
let global = self.global_object();
let key = locator.name().clone();
if global.has_property(key.clone(), self)? {
global.get(key, self).map(Some)
} else {
Ok(None)
}
global.try_get(key, self)
} else {
match self.environment_expect(locator.environment_index) {
Environment::Declarative(env) => Ok(env.get(locator.binding_index)),

8
core/engine/src/error.rs

@ -233,13 +233,7 @@ impl JsError {
.ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?;
let try_get_property = |key: JsString, name, context: &mut Context| {
obj.has_property(key.clone(), context)
.map_err(|e| TryNativeError::InaccessibleProperty {
property: name,
source: e,
})?
.then(|| obj.get(key, context))
.transpose()
obj.try_get(key, context)
.map_err(|e| TryNativeError::InaccessibleProperty {
property: name,
source: e,

88
core/engine/src/module/namespace.rs

@ -8,7 +8,7 @@ use boa_gc::{Finalize, Trace};
use crate::object::internal_methods::immutable_prototype::immutable_prototype_exotic_set_prototype_of;
use crate::object::internal_methods::{
ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property,
ordinary_has_property, ordinary_own_property_keys, InternalMethodContext,
ordinary_has_property, ordinary_own_property_keys, ordinary_try_get, InternalMethodContext,
InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
};
use crate::object::{JsData, JsPrototype};
@ -38,6 +38,7 @@ impl JsData for ModuleNamespace {
__get_own_property__: module_namespace_exotic_get_own_property,
__define_own_property__: module_namespace_exotic_define_own_property,
__has_property__: module_namespace_exotic_has_property,
__try_get__: module_namespace_exotic_try_get,
__get__: module_namespace_exotic_get,
__set__: module_namespace_exotic_set,
__delete__: module_namespace_exotic_delete,
@ -242,6 +243,91 @@ fn module_namespace_exotic_has_property(
Ok(exports.contains(&key))
}
/// Internal optimization method for `Module Namespace` exotic objects.
///
/// This method combines the internal methods `[[HasProperty]]` and `[[Get]]`.
///
/// More information:
/// - [ECMAScript reference HasProperty][spec0]
/// - [ECMAScript reference Get][spec1]
///
/// [spec0]: https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-hasproperty-p
/// [spec1]: https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver
fn module_namespace_exotic_try_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<JsValue>> {
// 1. If P is a Symbol, then
// a. Return ! OrdinaryGet(O, P, Receiver).
let key = match key {
PropertyKey::Symbol(_) => return ordinary_try_get(obj, key, receiver, context),
PropertyKey::Index(idx) => js_string!(format!("{}", idx.get())),
PropertyKey::String(s) => s.clone(),
};
let obj = obj
.downcast_ref::<ModuleNamespace>()
.expect("internal method can only be called on module namespace objects");
// 2. Let exports be O.[[Exports]].
let exports = obj.exports();
// 3. If exports does not contain P, return undefined.
let Some(export_name) = exports.get(&key).cloned() else {
return Ok(None);
};
// 4. Let m be O.[[Module]].
let m = obj.module();
// 5. Let binding be m.ResolveExport(P).
let binding = m
.resolve_export(
export_name.clone(),
&mut HashSet::default(),
context.interner(),
)
.expect("6. Assert: binding is a ResolvedBinding Record.");
// 7. Let targetModule be binding.[[Module]].
// 8. Assert: targetModule is not undefined.
let target_module = binding.module();
// TODO: cache binding resolution instead of doing the whole process on every access.
if let BindingName::Name(name) = binding.binding_name() {
// 10. Let targetEnv be targetModule.[[Environment]].
let Some(env) = target_module.environment() else {
// 11. If targetEnv is empty, throw a ReferenceError exception.
let import = export_name.to_std_string_escaped();
return Err(JsNativeError::reference()
.with_message(format!(
"cannot get import `{import}` from an uninitialized module"
))
.into());
};
let locator = env
.compile_env()
.get_binding(&name)
.expect("checked before that the name was reachable");
// 12. Return ? targetEnv.GetBindingValue(binding.[[BindingName]], true).
env.get(locator.binding_index()).map(Some).ok_or_else(|| {
let import = export_name.to_std_string_escaped();
JsNativeError::reference()
.with_message(format!("cannot get uninitialized import `{import}`"))
.into()
})
} else {
// 9. If binding.[[BindingName]] is namespace, then
// a. Return GetModuleNamespace(targetModule).
Ok(Some(target_module.namespace(context).into()))
}
}
/// [`[[Get]] ( P, Receiver )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver

81
core/engine/src/object/internal_methods/mod.rs

@ -179,6 +179,26 @@ impl JsObject {
(self.vtable().__has_property__)(self, key, context)
}
/// Internal optimization method.
///
/// This method combines the internal methods `[[hasProperty]]` and `[[Get]]`.
///
/// More information:
/// - [ECMAScript reference hasProperty][spec0]
/// - [ECMAScript reference get][spec1]
///
/// [spec0]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
/// [spec1]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver
pub(crate) fn __try_get__(
&self,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<JsValue>> {
let _timer = Profiler::global().start_event("Object::__try_get__", "object");
(self.vtable().__try_get__)(self, key, receiver, context)
}
/// Internal method `[[Get]]`
///
/// Get the specified property of this object or its prototype.
@ -308,6 +328,7 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj
__get_own_property__: ordinary_get_own_property,
__define_own_property__: ordinary_define_own_property,
__has_property__: ordinary_has_property,
__try_get__: ordinary_try_get,
__get__: ordinary_get,
__set__: ordinary_set,
__delete__: ordinary_delete,
@ -345,6 +366,12 @@ pub struct InternalObjectMethods {
fn(&JsObject, &PropertyKey, &mut InternalMethodContext<'_>) -> JsResult<bool>,
pub(crate) __get__:
fn(&JsObject, &PropertyKey, JsValue, &mut InternalMethodContext<'_>) -> JsResult<JsValue>,
pub(crate) __try_get__: fn(
&JsObject,
&PropertyKey,
JsValue,
&mut InternalMethodContext<'_>,
) -> JsResult<Option<JsValue>>,
pub(crate) __set__: fn(
&JsObject,
PropertyKey,
@ -643,6 +670,60 @@ pub(crate) fn ordinary_get(
}
}
/// Abstract optimization operation.
///
/// This operation combines the abstract operations `OrdinaryHasProperty` and `OrdinaryGet`.
///
/// More information:
/// - [ECMAScript reference OrdinaryHasProperty][spec0]
/// - [ECMAScript reference OrdinaryGet][spec1]
///
/// [spec0]: https://tc39.es/ecma262/#sec-ordinaryhasproperty
/// [spec1]: https://tc39.es/ecma262/#sec-ordinaryget
pub(crate) fn ordinary_try_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<JsValue>> {
let _timer = Profiler::global().start_event("Object::ordinary_try_get", "object");
// 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)? {
context.slot().set_not_cachable_if_already_prototype();
context.slot().attributes |= SlotAttributes::PROTOTYPE;
// c. Return ? parent.[[Get]](P, Receiver).
parent.__try_get__(key, receiver, context)
}
// b. If parent is null, return undefined.
else {
Ok(None)
}
}
Some(ref desc) => {
match desc.kind() {
// 4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
DescriptorKind::Data {
value: Some(value), ..
} => Ok(Some(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).
get.call(&receiver, &[], context).map(Some)
}
// 7. If getter is undefined, return undefined.
_ => Ok(Some(JsValue::undefined())),
}
}
}
}
/// Abstract operation `OrdinarySet`.
///
/// More information:

24
core/engine/src/object/jsobject.rs

@ -379,41 +379,40 @@ impl JsObject {
// 3. Let hasEnumerable be ? HasProperty(Obj, "enumerable").
// 4. If hasEnumerable is true, then ...
if self.has_property(js_str!("enumerable"), context)? {
if let Some(enumerable) = self.try_get(js_str!("enumerable"), context)? {
// a. Let enumerable be ! ToBoolean(? Get(Obj, "enumerable")).
// b. Set desc.[[Enumerable]] to enumerable.
desc = desc.enumerable(self.get(js_str!("enumerable"), context)?.to_boolean());
desc = desc.enumerable(enumerable.to_boolean());
}
// 5. Let hasConfigurable be ? HasProperty(Obj, "configurable").
// 6. If hasConfigurable is true, then ...
if self.has_property(js_str!("configurable"), context)? {
if let Some(configurable) = self.try_get(js_str!("configurable"), context)? {
// a. Let configurable be ! ToBoolean(? Get(Obj, "configurable")).
// b. Set desc.[[Configurable]] to configurable.
desc = desc.configurable(self.get(js_str!("configurable"), context)?.to_boolean());
desc = desc.configurable(configurable.to_boolean());
}
// 7. Let hasValue be ? HasProperty(Obj, "value").
// 8. If hasValue is true, then ...
if self.has_property(js_str!("value"), context)? {
if let Some(value) = self.try_get(js_str!("value"), context)? {
// a. Let value be ? Get(Obj, "value").
// b. Set desc.[[Value]] to value.
desc = desc.value(self.get(js_str!("value"), context)?);
desc = desc.value(value);
}
// 9. Let hasWritable be ? HasProperty(Obj, ).
// 10. If hasWritable is true, then ...
if self.has_property(js_str!("writable"), context)? {
if let Some(writable) = self.try_get(js_str!("writable"), context)? {
// a. Let writable be ! ToBoolean(? Get(Obj, "writable")).
// b. Set desc.[[Writable]] to writable.
desc = desc.writable(self.get(js_str!("writable"), context)?.to_boolean());
desc = desc.writable(writable.to_boolean());
}
// 11. Let hasGet be ? HasProperty(Obj, "get").
// 12. If hasGet is true, then
let get = if self.has_property(js_str!("get"), context)? {
// a. Let getter be ? Get(Obj, "get").
let getter = self.get(js_str!("get"), context)?;
// 12.a. Let getter be ? Get(Obj, "get").
let get = if let Some(getter) = self.try_get(js_str!("get"), context)? {
// b. If IsCallable(getter) is false and getter is not undefined, throw a TypeError exception.
// todo: extract IsCallable to be callable from Value
if !getter.is_undefined() && getter.as_object().map_or(true, |o| !o.is_callable()) {
@ -429,9 +428,8 @@ impl JsObject {
// 13. Let hasSet be ? HasProperty(Obj, "set").
// 14. If hasSet is true, then
let set = if self.has_property(js_str!("set"), context)? {
// 14.a. Let setter be ? Get(Obj, "set").
let setter = self.get(js_str!("set"), context)?;
let set = if let Some(setter) = self.try_get(js_str!("set"), context)? {
// 14.b. If IsCallable(setter) is false and setter is not undefined, throw a TypeError exception.
// todo: extract IsCallable to be callable from Value
if !setter.is_undefined() && setter.as_object().map_or(true, |o| !o.is_callable()) {

22
core/engine/src/object/operations.rs

@ -323,6 +323,28 @@ impl JsObject {
self.__has_property__(&key.into(), &mut InternalMethodContext::new(context))
}
/// Abstract optimization operation.
///
/// Check if an object has a property and get it if it exists.
/// This operation combines the abstract operations `HasProperty` and `Get`.
///
/// More information:
/// - [ECMAScript reference HasProperty][spec0]
/// - [ECMAScript reference Get][spec1]
///
/// [spec0]: https://tc39.es/ecma262/#sec-hasproperty
/// [spec1]: https://tc39.es/ecma262/#sec-get-o-p
pub(crate) fn try_get<K>(&self, key: K, context: &mut Context) -> JsResult<Option<JsValue>>
where
K: Into<PropertyKey>,
{
self.__try_get__(
&key.into(),
self.clone().into(),
&mut InternalMethodContext::new(context),
)
}
/// Check if object has an own property.
///
/// More information:

Loading…
Cancel
Save