Browse Source

Cleanup `Context` APIs (#2504)

Just a general cleanup of the APIs of our `Context`.

- Reordered the `pub` and `pub(crate)/fn` methods to have a clear separation between our public and private APIs.
- Removed the call method and added it to `JsValue` instead, which semantically makes a bit more sense.
- Removed the `construct_object` method, and added an utility method `new` to `JsObject` instead.
- Rewrote some patterns I found while rewriting the calls of the removed function.
pull/2510/head
José Julián Espina 2 years ago
parent
commit
cbf07824cb
  1. 142
      boa_engine/src/builtins/array/mod.rs
  2. 2
      boa_engine/src/builtins/array/tests.rs
  3. 15
      boa_engine/src/builtins/array_buffer/mod.rs
  4. 2
      boa_engine/src/builtins/date/mod.rs
  5. 7
      boa_engine/src/builtins/function/arguments.rs
  6. 5
      boa_engine/src/builtins/function/tests.rs
  7. 46
      boa_engine/src/builtins/iterable/mod.rs
  8. 4
      boa_engine/src/builtins/json/mod.rs
  9. 22
      boa_engine/src/builtins/object/mod.rs
  10. 113
      boa_engine/src/builtins/promise/mod.rs
  11. 16
      boa_engine/src/builtins/promise/promise_job.rs
  12. 2
      boa_engine/src/builtins/proxy/mod.rs
  13. 97
      boa_engine/src/builtins/regexp/mod.rs
  14. 2
      boa_engine/src/builtins/set/mod.rs
  15. 62
      boa_engine/src/builtins/string/mod.rs
  16. 37
      boa_engine/src/builtins/typed_array/integer_indexed_object.rs
  17. 4
      boa_engine/src/builtins/typed_array/mod.rs
  18. 6
      boa_engine/src/context/intrinsics.rs
  19. 413
      boa_engine/src/context/mod.rs
  20. 15
      boa_engine/src/object/builtins/jsarraybuffer.rs
  21. 2
      boa_engine/src/object/builtins/jsproxy.rs
  22. 4
      boa_engine/src/object/internal_methods/global.rs
  23. 4
      boa_engine/src/object/internal_methods/mod.rs
  24. 58
      boa_engine/src/object/jsobject.rs
  25. 6
      boa_engine/src/object/mod.rs
  26. 31
      boa_engine/src/object/operations.rs
  27. 3
      boa_engine/src/value/serde_json.rs
  28. 6
      boa_engine/src/value/tests.rs
  29. 3
      boa_engine/src/vm/code_block.rs
  30. 6
      boa_engine/src/vm/opcode/binary_ops/mod.rs
  31. 10
      boa_engine/src/vm/opcode/generator/mod.rs
  32. 4
      boa_engine/src/vm/opcode/get/name.rs
  33. 4
      boa_engine/src/vm/opcode/push/object.rs
  34. 2
      boa_examples/src/bin/closures.rs

142
boa_engine/src/builtins/array/mod.rs

@ -426,77 +426,9 @@ impl Array {
}; };
// 4. Let usingIterator be ? GetMethod(items, @@iterator). // 4. Let usingIterator be ? GetMethod(items, @@iterator).
let using_iterator = items let using_iterator = items.get_method(WellKnownSymbols::iterator(), context)?;
.get_method(WellKnownSymbols::iterator(), context)?
.map(JsValue::from);
if let Some(using_iterator) = using_iterator { let Some(using_iterator) = using_iterator else {
// 5. If usingIterator is not undefined, then
// a. If IsConstructor(C) is true, then
// i. Let A be ? Construct(C).
// b. Else,
// i. Let A be ? ArrayCreate(0en).
let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[], None, context)?,
_ => Self::array_create(0, None, context)?,
};
// c. Let iteratorRecord be ? GetIterator(items, sync, usingIterator).
let iterator_record =
items.get_iterator(context, Some(IteratorHint::Sync), Some(using_iterator))?;
// d. Let k be 0.
// e. Repeat,
// i. If k ≥ 2^53 - 1 (MAX_SAFE_INTEGER), then
// ...
// x. Set k to k + 1.
for k in 0..9_007_199_254_740_991_u64 {
// iii. Let next be ? IteratorStep(iteratorRecord).
let next = iterator_record.step(context)?;
// iv. If next is false, then
let Some(next) = next else {
// 1. Perform ? Set(A, "length", 𝔽(k), true).
a.set("length", k, true, context)?;
// 2. Return A.
return Ok(a.into());
};
// v. Let nextValue be ? IteratorValue(next).
let next_value = next.value(context)?;
// vi. If mapping is true, then
let mapped_value = if let Some(mapfn) = mapping {
// 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »).
let mapped_value = mapfn.call(this_arg, &[next_value, k.into()], context);
// 2. IfAbruptCloseIterator(mappedValue, iteratorRecord).
if_abrupt_close_iterator!(mapped_value, iterator_record, context)
} else {
// vii. Else, let mappedValue be nextValue.
next_value
};
// viii. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue).
let define_status = a.create_data_property_or_throw(k, mapped_value, context);
// ix. IfAbruptCloseIterator(defineStatus, iteratorRecord).
if_abrupt_close_iterator!(define_status, iterator_record, context);
}
// NOTE: The loop above has to return before it reaches iteration limit,
// which is why it's safe to have this as the fallback return
//
// 1. Let error be ThrowCompletion(a newly created TypeError object).
let error = Err(JsNativeError::typ()
.with_message("Invalid array length")
.into());
// 2. Return ? IteratorClose(iteratorRecord, error).
iterator_record.close(error, context)
} else {
// 6. NOTE: items is not an Iterable so assume it is an array-like object. // 6. NOTE: items is not an Iterable so assume it is an array-like object.
// 7. Let arrayLike be ! ToObject(items). // 7. Let arrayLike be ! ToObject(items).
let array_like = items let array_like = items
@ -541,8 +473,74 @@ impl Array {
a.set("length", len, true, context)?; a.set("length", len, true, context)?;
// 14. Return A. // 14. Return A.
Ok(a.into()) return Ok(a.into());
};
// 5. If usingIterator is not undefined, then
// a. If IsConstructor(C) is true, then
// i. Let A be ? Construct(C).
// b. Else,
// i. Let A be ? ArrayCreate(0en).
let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[], None, context)?,
_ => Self::array_create(0, None, context)?,
};
// c. Let iteratorRecord be ? GetIterator(items, sync, usingIterator).
let iterator_record =
items.get_iterator(context, Some(IteratorHint::Sync), Some(using_iterator))?;
// d. Let k be 0.
// e. Repeat,
// i. If k ≥ 2^53 - 1 (MAX_SAFE_INTEGER), then
// ...
// x. Set k to k + 1.
for k in 0..9_007_199_254_740_991_u64 {
// iii. Let next be ? IteratorStep(iteratorRecord).
let next = iterator_record.step(context)?;
// iv. If next is false, then
let Some(next) = next else {
// 1. Perform ? Set(A, "length", 𝔽(k), true).
a.set("length", k, true, context)?;
// 2. Return A.
return Ok(a.into());
};
// v. Let nextValue be ? IteratorValue(next).
let next_value = next.value(context)?;
// vi. If mapping is true, then
let mapped_value = if let Some(mapfn) = mapping {
// 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »).
let mapped_value = mapfn.call(this_arg, &[next_value, k.into()], context);
// 2. IfAbruptCloseIterator(mappedValue, iteratorRecord).
if_abrupt_close_iterator!(mapped_value, iterator_record, context)
} else {
// vii. Else, let mappedValue be nextValue.
next_value
};
// viii. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue).
let define_status = a.create_data_property_or_throw(k, mapped_value, context);
// ix. IfAbruptCloseIterator(defineStatus, iteratorRecord).
if_abrupt_close_iterator!(define_status, iterator_record, context);
} }
// NOTE: The loop above has to return before it reaches iteration limit,
// which is why it's safe to have this as the fallback return
//
// 1. Let error be ThrowCompletion(a newly created TypeError object).
let error = Err(JsNativeError::typ()
.with_message("Invalid array length")
.into());
// 2. Return ? IteratorClose(iteratorRecord, error).
iterator_record.close(error, context)
} }
/// `Array.isArray( arg )` /// `Array.isArray( arg )`
@ -2957,7 +2955,7 @@ impl Array {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@unscopables /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@unscopables
pub(crate) fn unscopables_intrinsic(context: &mut Context) -> JsObject { pub(crate) fn unscopables_intrinsic(context: &mut Context) -> JsObject {
// 1. Let unscopableList be OrdinaryObjectCreate(null). // 1. Let unscopableList be OrdinaryObjectCreate(null).
let unscopable_list = JsObject::empty(); let unscopable_list = JsObject::with_null_proto();
// 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true). // 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true).
unscopable_list unscopable_list
.create_data_property_or_throw("at", true, context) .create_data_property_or_throw("at", true, context)

2
boa_engine/src/builtins/array/tests.rs

@ -1411,7 +1411,7 @@ fn array_spread_non_iterable() {
try { try {
const array2 = [...5]; const array2 = [...5];
} catch (err) { } catch (err) {
err.name === "TypeError" && err.message === "Value is not callable" err.name === "TypeError" && err.message === "value with type `number` is not iterable"
} }
"#; "#;
assert_eq!(forward(&mut context, init), "true"); assert_eq!(forward(&mut context, init), "true");

15
boa_engine/src/builtins/array_buffer/mod.rs

@ -340,19 +340,20 @@ impl ArrayBuffer {
StandardConstructors::array_buffer, StandardConstructors::array_buffer,
context, context,
)?; )?;
let obj = context.construct_object();
obj.set_prototype(prototype.into());
// 2. Let block be ? CreateByteDataBlock(byteLength). // 2. Let block be ? CreateByteDataBlock(byteLength).
let block = create_byte_data_block(byte_length)?; let block = create_byte_data_block(byte_length)?;
// 3. Set obj.[[ArrayBufferData]] to block. // 3. Set obj.[[ArrayBufferData]] to block.
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
obj.borrow_mut().data = ObjectData::array_buffer(Self { let obj = JsObject::from_proto_and_data(
array_buffer_data: Some(block), prototype,
array_buffer_byte_length: byte_length, ObjectData::array_buffer(Self {
array_buffer_detach_key: JsValue::Undefined, array_buffer_data: Some(block),
}); array_buffer_byte_length: byte_length,
array_buffer_detach_key: JsValue::Undefined,
}),
);
// 5. Return obj. // 5. Return obj.
Ok(obj) Ok(obj)

2
boa_engine/src/builtins/date/mod.rs

@ -1234,7 +1234,7 @@ impl Date {
// 4. Return ? Invoke(O, "toISOString"). // 4. Return ? Invoke(O, "toISOString").
let func = o.get("toISOString", context)?; let func = o.get("toISOString", context)?;
context.call(&func, &o.into(), &[]) func.call(this, &[], context)
} }
/// [`Date.prototype.toLocaleDateString()`][spec]. /// [`Date.prototype.toLocaleDateString()`][spec].

7
boa_engine/src/builtins/function/arguments.rs

@ -77,11 +77,12 @@ impl Arguments {
let len = arguments_list.len(); let len = arguments_list.len();
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »). // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »).
let obj = context.construct_object();
// 3. Set obj.[[ParameterMap]] to undefined. // 3. Set obj.[[ParameterMap]] to undefined.
// skipped because the `Arguments` enum ensures ordinary argument objects don't have a `[[ParameterMap]]` // skipped because the `Arguments` enum ensures ordinary argument objects don't have a `[[ParameterMap]]`
obj.borrow_mut().data = ObjectData::arguments(Self::Unmapped); let obj = JsObject::from_proto_and_data(
context.intrinsics().constructors().object().prototype(),
ObjectData::arguments(Self::Unmapped),
);
// 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len),
// [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).

5
boa_engine/src/builtins/function/tests.rs

@ -1,7 +1,7 @@
use crate::{ use crate::{
error::JsNativeError, error::JsNativeError,
forward, forward_val, js_string, forward, forward_val, js_string,
object::FunctionBuilder, object::{FunctionBuilder, JsObject},
property::{Attribute, PropertyDescriptor}, property::{Attribute, PropertyDescriptor},
string::utf16, string::utf16,
Context, JsNativeErrorKind, Context, JsNativeErrorKind,
@ -230,7 +230,8 @@ fn closure_capture_clone() {
let mut context = Context::default(); let mut context = Context::default();
let string = js_string!("Hello"); let string = js_string!("Hello");
let object = context.construct_object(); let object = JsObject::with_object_proto(&mut context);
object object
.define_property_or_throw( .define_property_or_throw(
"key", "key",

46
boa_engine/src/builtins/iterable/mod.rs

@ -136,7 +136,7 @@ pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Conte
// 1. Assert: Type(done) is Boolean. // 1. Assert: Type(done) is Boolean.
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
let obj = context.construct_object(); let obj = JsObject::with_object_proto(context);
// 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value). // 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value).
obj.create_data_property_or_throw("value", value, context) obj.create_data_property_or_throw("value", value, context)
@ -169,13 +169,13 @@ impl JsValue {
&self, &self,
context: &mut Context, context: &mut Context,
hint: Option<IteratorHint>, hint: Option<IteratorHint>,
method: Option<Self>, method: Option<JsObject>,
) -> JsResult<IteratorRecord> { ) -> JsResult<IteratorRecord> {
// 1. If hint is not present, set hint to sync. // 1. If hint is not present, set hint to sync.
let hint = hint.unwrap_or(IteratorHint::Sync); let hint = hint.unwrap_or(IteratorHint::Sync);
// 2. If method is not present, then // 2. If method is not present, then
let method = if let Some(method) = method { let method = if method.is_some() {
method method
} else { } else {
// a. If hint is async, then // a. If hint is async, then
@ -184,17 +184,15 @@ impl JsValue {
if let Some(method) = if let Some(method) =
self.get_method(WellKnownSymbols::async_iterator(), context)? self.get_method(WellKnownSymbols::async_iterator(), context)?
{ {
method.into() Some(method)
} else { } else {
// ii. If method is undefined, then // ii. If method is undefined, then
// 1. Let syncMethod be ? GetMethod(obj, @@iterator). // 1. Let syncMethod be ? GetMethod(obj, @@iterator).
let sync_method = self let sync_method = self.get_method(WellKnownSymbols::iterator(), context)?;
.get_method(WellKnownSymbols::iterator(), context)?
.map_or(Self::Undefined, Self::from);
// 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod). // 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod).
let sync_iterator_record = let sync_iterator_record =
self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method))?; self.get_iterator(context, Some(IteratorHint::Sync), sync_method)?;
// 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord). // 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord).
return Ok(AsyncFromSyncIterator::create(sync_iterator_record, context)); return Ok(AsyncFromSyncIterator::create(sync_iterator_record, context));
@ -202,17 +200,22 @@ impl JsValue {
} else { } else {
// b. Otherwise, set method to ? GetMethod(obj, @@iterator). // b. Otherwise, set method to ? GetMethod(obj, @@iterator).
self.get_method(WellKnownSymbols::iterator(), context)? self.get_method(WellKnownSymbols::iterator(), context)?
.map_or(Self::Undefined, Self::from)
} }
}; }
.ok_or_else(|| {
JsNativeError::typ().with_message(format!(
"value with type `{}` is not iterable",
self.type_of()
))
})?;
// 3. Let iterator be ? Call(method, obj). // 3. Let iterator be ? Call(method, obj).
let iterator = context.call(&method, self, &[])?; let iterator = method.call(self, &[], context)?;
// 4. If Type(iterator) is not Object, throw a TypeError exception. // 4. If Type(iterator) is not Object, throw a TypeError exception.
let iterator_obj = iterator let iterator_obj = iterator.as_object().ok_or_else(|| {
.as_object() JsNativeError::typ().with_message("returned iterator is not an object")
.ok_or_else(|| JsNativeError::typ().with_message("the iterator is not an object"))?; })?;
// 5. Let nextMethod be ? GetV(iterator, "next"). // 5. Let nextMethod be ? GetV(iterator, "next").
let next_method = iterator.get_v("next", context)?; let next_method = iterator.get_v("next", context)?;
@ -500,20 +503,15 @@ impl IteratorRecord {
pub(crate) fn iterable_to_list( pub(crate) fn iterable_to_list(
context: &mut Context, context: &mut Context,
items: &JsValue, items: &JsValue,
method: Option<JsValue>, method: Option<JsObject>,
) -> JsResult<Vec<JsValue>> { ) -> JsResult<Vec<JsValue>> {
let _timer = Profiler::global().start_event("iterable_to_list", "iterator"); let _timer = Profiler::global().start_event("iterable_to_list", "iterator");
// 1. If method is present, then // 1. If method is present, then
let iterator_record = if let Some(method) = method { // a. Let iteratorRecord be ? GetIterator(items, sync, method).
// a. Let iteratorRecord be ? GetIterator(items, sync, method). // 2. Else,
items.get_iterator(context, Some(IteratorHint::Sync), Some(method))? // a. Let iteratorRecord be ? GetIterator(items, sync).
} else { let iterator_record = items.get_iterator(context, Some(IteratorHint::Sync), method)?;
// 2. Else,
// a. Let iteratorRecord be ? GetIterator(items, sync).
items.get_iterator(context, Some(IteratorHint::Sync), None)?
};
// 3. Let values be a new empty List. // 3. Let values be a new empty List.
let mut values = Vec::new(); let mut values = Vec::new();

4
boa_engine/src/builtins/json/mod.rs

@ -203,7 +203,7 @@ impl Json {
// 11. If IsCallable(reviver) is true, then // 11. If IsCallable(reviver) is true, then
if let Some(obj) = args.get_or_undefined(1).as_callable() { if let Some(obj) = args.get_or_undefined(1).as_callable() {
// a. Let root be ! OrdinaryObjectCreate(%Object.prototype%). // a. Let root be ! OrdinaryObjectCreate(%Object.prototype%).
let root = context.construct_object(); let root = JsObject::with_object_proto(context);
// b. Let rootName be the empty String. // b. Let rootName be the empty String.
// c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered). // c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered).
@ -435,7 +435,7 @@ impl Json {
}; };
// 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%). // 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%).
let wrapper = context.construct_object(); let wrapper = JsObject::with_object_proto(context);
// 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value). // 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value).
wrapper wrapper

22
boa_engine/src/builtins/object/mod.rs

@ -124,18 +124,24 @@ impl Object {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. If NewTarget is neither undefined nor the active function object, then
if !new_target.is_undefined() { if !new_target.is_undefined() {
// a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%").
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::object, context)?; get_prototype_from_constructor(new_target, StandardConstructors::object, context)?;
let object = JsObject::from_proto_and_data(prototype, ObjectData::ordinary()); let object = JsObject::from_proto_and_data(prototype, ObjectData::ordinary());
return Ok(object.into()); return Ok(object.into());
} }
if let Some(arg) = args.get(0) {
if !arg.is_null_or_undefined() { let value = args.get_or_undefined(0);
return Ok(arg.to_object(context)?.into());
} // 2. If value is undefined or null, return OrdinaryObjectCreate(%Object.prototype%).
if value.is_null_or_undefined() {
Ok(JsObject::with_object_proto(context).into())
} else {
// 3. Return ! ToObject(value).
value.to_object(context).map(JsValue::from)
} }
Ok(context.construct_object().into())
} }
/// `get Object.prototype.__proto__` /// `get Object.prototype.__proto__`
@ -471,7 +477,7 @@ impl Object {
let own_keys = obj.__own_property_keys__(context)?; let own_keys = obj.__own_property_keys__(context)?;
// 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%). // 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%).
let descriptors = context.construct_object(); let descriptors = JsObject::with_object_proto(context);
// 4. For each element key of ownKeys, do // 4. For each element key of ownKeys, do
for key in own_keys { for key in own_keys {
@ -512,7 +518,7 @@ impl Object {
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
// 3. Assert: obj is an extensible ordinary object with no own properties. // 3. Assert: obj is an extensible ordinary object with no own properties.
let obj = context.construct_object(); let obj = JsObject::with_object_proto(context);
// 4. If Desc has a [[Value]] field, then // 4. If Desc has a [[Value]] field, then
if let Some(value) = desc.value() { if let Some(value) = desc.value() {
@ -1242,7 +1248,7 @@ impl Object {
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
// 3. Assert: obj is an extensible ordinary object with no own properties. // 3. Assert: obj is an extensible ordinary object with no own properties.
let obj = context.construct_object(); let obj = JsObject::with_object_proto(context);
// 4. Let closure be a new Abstract Closure with parameters (key, value) that captures // 4. Let closure be a new Abstract Closure with parameters (key, value) that captures
// obj and performs the following steps when called: // obj and performs the following steps when called:

113
boa_engine/src/builtins/promise/mod.rs

@ -41,11 +41,9 @@ macro_rules! if_abrupt_reject_promise {
Err(err) => { Err(err) => {
let err = err.to_opaque($context); let err = err.to_opaque($context);
// a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
$context.call( $capability
&$capability.reject().clone().into(), .reject()
&JsValue::undefined(), .call(&JsValue::undefined(), &[err], $context)?;
&[err],
)?;
// b. Return capability.[[Promise]]. // b. Return capability.[[Promise]].
return Ok($capability.promise().clone().into()); return Ok($capability.promise().clone().into());
@ -297,8 +295,8 @@ impl BuiltIn for Promise {
#[derive(Debug)] #[derive(Debug)]
struct ResolvingFunctionsRecord { struct ResolvingFunctionsRecord {
resolve: JsValue, resolve: JsFunction,
reject: JsValue, reject: JsFunction,
} }
impl Promise { impl Promise {
@ -327,14 +325,11 @@ impl Promise {
.into()); .into());
} }
let executor = args.get_or_undefined(0);
// 2. If IsCallable(executor) is false, throw a TypeError exception. // 2. If IsCallable(executor) is false, throw a TypeError exception.
if !executor.is_callable() { let executor = args
return Err(JsNativeError::typ() .get_or_undefined(0)
.with_message("Promise executor is not callable") .as_callable()
.into()); .ok_or_else(|| JsNativeError::typ().with_message("Promise executor is not callable"))?;
}
// 3. Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »). // 3. Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »).
let promise = let promise =
@ -358,20 +353,22 @@ impl Promise {
let resolving_functions = Self::create_resolving_functions(&promise, context); let resolving_functions = Self::create_resolving_functions(&promise, context);
// 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ). // 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ).
let completion = context.call( let completion = executor.call(
executor,
&JsValue::Undefined, &JsValue::Undefined,
&[ &[
resolving_functions.resolve, resolving_functions.resolve.clone().into(),
resolving_functions.reject.clone(), resolving_functions.reject.clone().into(),
], ],
context,
); );
// 10. If completion is an abrupt completion, then // 10. If completion is an abrupt completion, then
if let Err(e) = completion { if let Err(e) = completion {
let e = e.to_opaque(context); let e = e.to_opaque(context);
// a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »). // a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »).
context.call(&resolving_functions.reject, &JsValue::Undefined, &[e])?; resolving_functions
.reject
.call(&JsValue::Undefined, &[e], context)?;
} }
// 11. Return promise. // 11. Return promise.
@ -809,7 +806,7 @@ impl Promise {
// 8. Let remainingElementsCount be F.[[RemainingElements]]. // 8. Let remainingElementsCount be F.[[RemainingElements]].
// 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%).
let obj = context.construct_object(); let obj = JsObject::with_object_proto(context);
// 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled"). // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled").
obj.create_data_property_or_throw("status", "fulfilled", context) obj.create_data_property_or_throw("status", "fulfilled", context)
@ -893,7 +890,7 @@ impl Promise {
// 8. Let remainingElementsCount be F.[[RemainingElements]]. // 8. Let remainingElementsCount be F.[[RemainingElements]].
// 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%).
let obj = context.construct_object(); let obj = JsObject::with_object_proto(context);
// 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected"). // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected").
obj.create_data_property_or_throw("status", "rejected", context) obj.create_data_property_or_throw("status", "rejected", context)
@ -1429,8 +1426,6 @@ impl Promise {
.build(); .build();
// 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }. // 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }.
let resolve = resolve.conv::<JsValue>();
let reject = reject.conv::<JsValue>();
ResolvingFunctionsRecord { resolve, reject } ResolvingFunctionsRecord { resolve, reject }
} }
@ -1589,7 +1584,7 @@ impl Promise {
&mut iterator_record, &mut iterator_record,
c, c,
&promise_capability, &promise_capability,
&promise_resolve.into(), &promise_resolve,
context, context,
); );
@ -1625,7 +1620,7 @@ impl Promise {
iterator_record: &mut IteratorRecord, iterator_record: &mut IteratorRecord,
constructor: &JsValue, constructor: &JsValue,
result_capability: &PromiseCapability, result_capability: &PromiseCapability,
promise_resolve: &JsValue, promise_resolve: &JsObject,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Repeat, // 1. Repeat,
@ -1654,7 +1649,7 @@ impl Promise {
let next_value = next_value?; let next_value = next_value?;
// h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). // h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »).
let next_promise = context.call(promise_resolve, constructor, &[next_value])?; let next_promise = promise_resolve.call(constructor, &[next_value], context)?;
// i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »). // i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »).
next_promise.invoke( next_promise.invoke(
@ -1694,11 +1689,10 @@ impl Promise {
let promise_capability = PromiseCapability::new(c, context)?; let promise_capability = PromiseCapability::new(c, context)?;
// 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »). // 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
context.call(
&promise_capability.reject.clone().into(), promise_capability
&JsValue::undefined(), .reject
&[r.clone()], .call(&JsValue::undefined(), &[r.clone()], context)?;
)?;
// 4. Return promiseCapability.[[Promise]]. // 4. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise.clone().into()) Ok(promise_capability.promise.clone().into())
@ -1779,26 +1773,34 @@ impl Promise {
let promise = this; let promise = this;
// 2. If Type(promise) is not Object, throw a TypeError exception. // 2. If Type(promise) is not Object, throw a TypeError exception.
let Some(promise_obj) = promise.as_object() else { let Some(promise) = promise.as_object() else {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("finally called with a non-object promise") .with_message("finally called with a non-object promise")
.into()); .into());
}; };
// 3. Let C be ? SpeciesConstructor(promise, %Promise%). // 3. Let C be ? SpeciesConstructor(promise, %Promise%).
let c = promise_obj.species_constructor(StandardConstructors::promise, context)?; let c = promise.species_constructor(StandardConstructors::promise, context)?;
// 4. Assert: IsConstructor(C) is true. // 4. Assert: IsConstructor(C) is true.
debug_assert!(c.is_constructor()); debug_assert!(c.is_constructor());
let on_finally = args.get_or_undefined(0); let on_finally = args.get_or_undefined(0);
// 5. If IsCallable(onFinally) is false, then let Some(on_finally) = on_finally.as_callable() else {
let (then_finally, catch_finally) = if on_finally.is_callable() { // 5. If IsCallable(onFinally) is false, then
// a. Let thenFinally be onFinally.
// b. Let catchFinally be onFinally.
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
let then = promise.get("then", context)?;
return then.call(this, &[on_finally.clone(), on_finally.clone()], context);
};
let (then_finally, catch_finally) = {
/// Capture object for the `thenFinallyClosure` abstract closure. /// Capture object for the `thenFinallyClosure` abstract closure.
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
struct FinallyCaptures { struct FinallyCaptures {
on_finally: JsValue, on_finally: JsObject,
c: JsObject, c: JsObject,
} }
@ -1815,7 +1817,9 @@ impl Promise {
let value = args.get_or_undefined(0); let value = args.get_or_undefined(0);
// i. Let result be ? Call(onFinally, undefined). // i. Let result be ? Call(onFinally, undefined).
let result = context.call(&captures.on_finally, &JsValue::undefined(), &[])?; let result = captures
.on_finally
.call(&JsValue::undefined(), &[], context)?;
// ii. Let promise be ? PromiseResolve(C, result). // ii. Let promise be ? PromiseResolve(C, result).
let promise = Self::promise_resolve(captures.c.clone(), result, context)?; let promise = Self::promise_resolve(captures.c.clone(), result, context)?;
@ -1860,7 +1864,9 @@ impl Promise {
let reason = args.get_or_undefined(0); let reason = args.get_or_undefined(0);
// i. Let result be ? Call(onFinally, undefined). // i. Let result be ? Call(onFinally, undefined).
let result = context.call(&captures.on_finally, &JsValue::undefined(), &[])?; let result = captures
.on_finally
.call(&JsValue::undefined(), &[], context)?;
// ii. Let promise be ? PromiseResolve(C, result). // ii. Let promise be ? PromiseResolve(C, result).
let promise = Self::promise_resolve(captures.c.clone(), result, context)?; let promise = Self::promise_resolve(captures.c.clone(), result, context)?;
@ -1892,16 +1898,12 @@ impl Promise {
// d. Let catchFinally be CreateBuiltinFunction(catchFinallyClosure, 1, "", « »). // d. Let catchFinally be CreateBuiltinFunction(catchFinallyClosure, 1, "", « »).
let catch_finally = catch_finally_closure.length(1).name("").build(); let catch_finally = catch_finally_closure.length(1).name("").build();
(then_finally.into(), catch_finally.into()) // TODO (then_finally.into(), catch_finally.into())
} else {
// 6. Else,
// a. Let thenFinally be onFinally.
// b. Let catchFinally be onFinally.
(on_finally.clone(), on_finally.clone())
}; };
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
promise.invoke("then", &[then_finally, catch_finally], context) let then = promise.get("then", context)?;
then.call(this, &[then_finally, catch_finally], context)
} }
/// `Promise.prototype.then ( onFulfilled, onRejected )` /// `Promise.prototype.then ( onFulfilled, onRejected )`
@ -2081,11 +2083,9 @@ impl Promise {
let promise_capability = PromiseCapability::new(&c.into(), context)?; let promise_capability = PromiseCapability::new(&c.into(), context)?;
// 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »). // 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
context.call( promise_capability
&promise_capability.resolve.clone().into(), .resolve
&JsValue::undefined(), .call(&JsValue::Undefined, &[x], context)?;
&[x],
)?;
// 4. Return promiseCapability.[[Promise]]. // 4. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise.clone().into()) Ok(promise_capability.promise.clone().into())
@ -2109,13 +2109,10 @@ impl Promise {
let promise_resolve = promise_constructor.get("resolve", context)?; let promise_resolve = promise_constructor.get("resolve", context)?;
// 2. If IsCallable(promiseResolve) is false, throw a TypeError exception. // 2. If IsCallable(promiseResolve) is false, throw a TypeError exception.
promise_resolve.as_callable().map_or_else( promise_resolve.as_callable().cloned().ok_or_else(|| {
|| { JsNativeError::typ()
Err(JsNativeError::typ() .with_message("retrieving a non-callable promise resolver")
.with_message("retrieving a non-callable promise resolver") .into()
.into()) })
},
|promise_resolve| Ok(promise_resolve.clone()),
)
} }
} }

16
boa_engine/src/builtins/promise/promise_job.rs

@ -83,13 +83,13 @@ impl PromiseJob {
// h. If handlerResult is an abrupt completion, then // h. If handlerResult is an abrupt completion, then
Err(value) => { Err(value) => {
// i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).
context.call(&reject.clone().into(), &JsValue::Undefined, &[value]) reject.call(&JsValue::Undefined, &[value], context)
} }
// i. Else, // i. Else,
Ok(value) => { Ok(value) => {
// i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »).
context.call(&resolve.clone().into(), &JsValue::Undefined, &[value]) resolve.call(&JsValue::Undefined, &[value], context)
} }
} }
} }
@ -138,8 +138,8 @@ impl PromiseJob {
let then_call_result = then.call_job_callback( let then_call_result = then.call_job_callback(
thenable, thenable,
&[ &[
resolving_functions.resolve, resolving_functions.resolve.clone().into(),
resolving_functions.reject.clone(), resolving_functions.reject.clone().into(),
], ],
context, context,
); );
@ -148,11 +148,9 @@ impl PromiseJob {
if let Err(value) = then_call_result { if let Err(value) = then_call_result {
let value = value.to_opaque(context); let value = value.to_opaque(context);
// i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »).
return context.call( return resolving_functions
&resolving_functions.reject, .reject
&JsValue::Undefined, .call(&JsValue::Undefined, &[value], context);
&[value],
);
} }
// d. Return ? thenCallResult. // d. Return ? thenCallResult.

2
boa_engine/src/builtins/proxy/mod.rs

@ -173,7 +173,7 @@ impl Proxy {
let revoker = Self::revoker(p.clone(), context); let revoker = Self::revoker(p.clone(), context);
// 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%). // 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%).
let result = context.construct_object(); let result = JsObject::with_object_proto(context);
// 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p). // 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p).
result result

97
boa_engine/src/builtins/regexp/mod.rs

@ -975,7 +975,7 @@ impl RegExp {
let named_groups = match_value.named_groups(); let named_groups = match_value.named_groups();
let groups = if named_groups.clone().count() > 0 { let groups = if named_groups.clone().count() > 0 {
// a. Let groups be ! OrdinaryObjectCreate(null). // a. Let groups be ! OrdinaryObjectCreate(null).
let groups = JsObject::empty(); let groups = JsObject::with_null_proto();
// Perform 27.f here // Perform 27.f here
// f. If the ith capture of R was defined with a GroupName, then // f. If the ith capture of R was defined with a GroupName, then
@ -1237,17 +1237,14 @@ impl RegExp {
let length_arg_str = arg_str.len(); let length_arg_str = arg_str.len();
// 5. Let functionalReplace be IsCallable(replaceValue). // 5. Let functionalReplace be IsCallable(replaceValue).
let mut replace_value = args.get_or_undefined(1).clone(); let replace_value = args.get_or_undefined(1).clone();
let functional_replace = replace_value let replace = if let Some(f) = replace_value.as_callable() {
.as_object() Ok(f)
.map(JsObject::is_callable) } else {
.unwrap_or_default(); // 6. If functionalReplace is false, then
// 6. If functionalReplace is false, then
if !functional_replace {
// a. Set replaceValue to ? ToString(replaceValue). // a. Set replaceValue to ? ToString(replaceValue).
replace_value = replace_value.to_string(context)?.into(); Err(replace_value.to_string(context)?)
} };
// 7. Let global be ! ToBoolean(? Get(rx, "global")). // 7. Let global be ! ToBoolean(? Get(rx, "global")).
let global = rx.get("global", context)?.to_boolean(); let global = rx.get("global", context)?.to_boolean();
@ -1356,47 +1353,49 @@ impl RegExp {
let mut named_captures = result.get("groups", context)?; let mut named_captures = result.get("groups", context)?;
// k. If functionalReplace is true, then // k. If functionalReplace is true, then
// l. Else, let replacement = match replace {
let replacement = if functional_replace { Ok(replace_fn) => {
// i. Let replacerArgs be « matched ». // i. Let replacerArgs be « matched ».
let mut replacer_args = vec![JsValue::new(matched)]; let mut replacer_args = vec![JsValue::new(matched)];
// ii. Append in List order the elements of captures to the end of the List replacerArgs. // ii. Append in List order the elements of captures to the end of the List replacerArgs.
replacer_args.extend(captures); replacer_args.extend(captures);
// iii. Append 𝔽(position) and S to replacerArgs. // iii. Append 𝔽(position) and S to replacerArgs.
replacer_args.push(position.into()); replacer_args.push(position.into());
replacer_args.push(arg_str.clone().into()); replacer_args.push(arg_str.clone().into());
// iv. If namedCaptures is not undefined, then // iv. If namedCaptures is not undefined, then
if !named_captures.is_undefined() { if !named_captures.is_undefined() {
// 1. Append namedCaptures as the last element of replacerArgs. // 1. Append namedCaptures as the last element of replacerArgs.
replacer_args.push(named_captures); replacer_args.push(named_captures);
} }
// v. Let replValue be ? Call(replaceValue, undefined, replacerArgs).
let repl_value =
context.call(&replace_value, &JsValue::undefined(), &replacer_args)?;
// vi. Let replacement be ? ToString(replValue). // v. Let replValue be ? Call(replaceValue, undefined, replacerArgs).
repl_value.to_string(context)? // vi. Let replacement be ? ToString(replValue).
} else { replace_fn
// i. If namedCaptures is not undefined, then .call(&JsValue::undefined(), &replacer_args, context)?
if !named_captures.is_undefined() { .to_string(context)?
// 1. Set namedCaptures to ? ToObject(namedCaptures).
named_captures = named_captures.to_object(context)?.into();
} }
// l. Else,
Err(ref replace_str) => {
// i. If namedCaptures is not undefined, then
if !named_captures.is_undefined() {
// 1. Set namedCaptures to ? ToObject(namedCaptures).
named_captures = named_captures.to_object(context)?.into();
}
// ii. Let replacement be ? GetSubstitution(matched, S, position, captures, namedCaptures, replaceValue). // ii. Let replacement be ? GetSubstitution(matched, S, position, captures, namedCaptures, replaceValue).
string::get_substitution( string::get_substitution(
&matched, &matched,
&arg_str, &arg_str,
position, position,
&captures, &captures,
&named_captures, &named_captures,
&replace_value.to_string(context)?, replace_str,
context, context,
)? )?
}
}; };
// m. If position ≥ nextSourcePosition, then // m. If position ≥ nextSourcePosition, then

2
boa_engine/src/builtins/set/mod.rs

@ -371,7 +371,7 @@ impl Set {
.ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?; .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?;
if let Some(arguments) = arguments { if let Some(arguments) = arguments {
context.call(callback_arg, &this_arg, &arguments)?; callback_arg.call(&this_arg, &arguments, context)?;
} }
index += 1; index += 1;

62
boa_engine/src/builtins/string/mod.rs

@ -938,10 +938,7 @@ impl String {
let search_str = search_value.to_string(context)?; let search_str = search_value.to_string(context)?;
// 5. Let functionalReplace be IsCallable(replaceValue). // 5. Let functionalReplace be IsCallable(replaceValue).
let functional_replace = replace_value let replace_obj = replace_value.as_callable();
.as_object()
.map(JsObject::is_callable)
.unwrap_or_default();
// 6. If functionalReplace is false, then // 6. If functionalReplace is false, then
// a. Set replaceValue to ? ToString(replaceValue). // a. Set replaceValue to ? ToString(replaceValue).
@ -960,13 +957,13 @@ impl String {
// 11. If functionalReplace is true, then // 11. If functionalReplace is true, then
// 12. Else, // 12. Else,
let replacement = if functional_replace { let replacement = if let Some(replace_fn) = replace_obj {
// a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)). // a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)).
context replace_fn
.call( .call(
replace_value,
&JsValue::undefined(), &JsValue::undefined(),
&[search_str.into(), position.into(), this_str.clone().into()], &[search_str.into(), position.into(), this_str.clone().into()],
context,
)? )?
.to_string(context)? .to_string(context)?
} else { } else {
@ -1060,17 +1057,12 @@ impl String {
let search_string = search_value.to_string(context)?; let search_string = search_value.to_string(context)?;
// 5. Let functionalReplace be IsCallable(replaceValue). // 5. Let functionalReplace be IsCallable(replaceValue).
let functional_replace = replace_value let replace = if let Some(f) = replace_value.as_callable() {
.as_object() Ok(f)
.map(JsObject::is_callable)
.unwrap_or_default();
let replace_value_string = if functional_replace {
None
} else { } else {
// a. Set replaceValue to ? ToString(replaceValue).
// 6. If functionalReplace is false, then // 6. If functionalReplace is false, then
Some(replace_value.to_string(context)?) // a. Set replaceValue to ? ToString(replaceValue).
Err(replace_value.to_string(context)?)
}; };
// 7. Let searchLength be the length of searchString. // 7. Let searchLength be the length of searchString.
@ -1106,35 +1098,35 @@ impl String {
let preserved = &string[end_of_last_match..p]; let preserved = &string[end_of_last_match..p];
// c. Else, // c. Else,
let replacement = if let Some(ref replace_value) = replace_value_string { let replacement = match replace {
// b. If functionalReplace is true, then
Ok(replace_fn) => {
// i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)).
replace_fn
.call(
&JsValue::undefined(),
&[
search_string.clone().into(),
p.into(),
string.clone().into(),
],
context,
)?
.to_string(context)?
}
// i. Assert: Type(replaceValue) is String. // i. Assert: Type(replaceValue) is String.
// ii. Let captures be a new empty List. // ii. Let captures be a new empty List.
// iii. Let replacement be ! GetSubstitution(searchString, string, p, captures, undefined, replaceValue). // iii. Let replacement be ! GetSubstitution(searchString, string, p, captures, undefined, replaceValue).
get_substitution( Err(ref replace_str) => get_substitution(
&search_string, &search_string,
&string, &string,
p, p,
&[], &[],
&JsValue::undefined(), &JsValue::undefined(),
replace_value, replace_str,
context, context,
) )
.expect("GetSubstitution should never fail here.") .expect("GetSubstitution should never fail here."),
}
// b. If functionalReplace is true, then
else {
// i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)).
context
.call(
replace_value,
&JsValue::undefined(),
&[
search_string.clone().into(),
p.into(),
string.clone().into(),
],
)?
.to_string(context)?
}; };
// d. Set result to the string-concatenation of result, preserved, and replacement. // d. Set result to the string-concatenation of result, preserved, and replacement.

37
boa_engine/src/builtins/typed_array/integer_indexed_object.rs

@ -8,11 +8,7 @@
//! //!
//! [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects //! [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects
use crate::{ use crate::{builtins::typed_array::TypedArrayKind, object::JsObject};
builtins::typed_array::TypedArrayKind,
object::{JsObject, ObjectData},
Context,
};
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
/// Type of the array content. /// Type of the array content.
@ -50,37 +46,6 @@ impl IntegerIndexed {
} }
} }
/// `IntegerIndexedObjectCreate ( prototype )`
///
/// Create a new `JsObject` from a prototype and a `IntegerIndexedObject`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integerindexedobjectcreate
pub(super) fn create(prototype: JsObject, data: Self, context: &Context) -> JsObject {
// 1. Let internalSlotsList be « [[Prototype]], [[Extensible]], [[ViewedArrayBuffer]],
// [[TypedArrayName]], [[ContentType]], [[ByteLength]], [[ByteOffset]],
// [[ArrayLength]] ».
// 2. Let A be ! MakeBasicObject(internalSlotsList).
let a = context.construct_object();
// 3. Set A.[[GetOwnProperty]] as specified in 10.4.5.1.
// 4. Set A.[[HasProperty]] as specified in 10.4.5.2.
// 5. Set A.[[DefineOwnProperty]] as specified in 10.4.5.3.
// 6. Set A.[[Get]] as specified in 10.4.5.4.
// 7. Set A.[[Set]] as specified in 10.4.5.5.
// 8. Set A.[[Delete]] as specified in 10.4.5.6.
// 9. Set A.[[OwnPropertyKeys]] as specified in 10.4.5.7.
a.borrow_mut().data = ObjectData::integer_indexed(data);
// 10. Set A.[[Prototype]] to prototype.
a.set_prototype(prototype.into());
// 11. Return A.
a
}
/// Abstract operation `IsDetachedBuffer ( arrayBuffer )`. /// Abstract operation `IsDetachedBuffer ( arrayBuffer )`.
/// ///
/// Check if `[[ArrayBufferData]]` is null. /// Check if `[[ArrayBufferData]]` is null.

4
boa_engine/src/builtins/typed_array/mod.rs

@ -433,7 +433,7 @@ impl TypedArray {
// 6. If usingIterator is not undefined, then // 6. If usingIterator is not undefined, then
if let Some(using_iterator) = using_iterator { if let Some(using_iterator) = using_iterator {
// a. Let values be ? IterableToList(source, usingIterator). // a. Let values be ? IterableToList(source, usingIterator).
let values = iterable_to_list(context, source, Some(using_iterator.into()))?; let values = iterable_to_list(context, source, Some(using_iterator))?;
// b. Let len be the number of elements in values. // b. Let len be the number of elements in values.
// c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »).
@ -3188,7 +3188,7 @@ impl TypedArray {
} }
// 2. Let obj be ! IntegerIndexedObjectCreate(proto). // 2. Let obj be ! IntegerIndexedObjectCreate(proto).
let obj = IntegerIndexed::create(proto, indexed, context); let obj = JsObject::from_proto_and_data(proto, ObjectData::integer_indexed(indexed));
// 9. Return obj. // 9. Return obj.
Ok(obj) Ok(obj)

6
boa_engine/src/context/intrinsics.rs

@ -42,8 +42,8 @@ pub struct StandardConstructor {
impl Default for StandardConstructor { impl Default for StandardConstructor {
fn default() -> Self { fn default() -> Self {
Self { Self {
constructor: JsObject::empty(), constructor: JsObject::with_null_proto(),
prototype: JsObject::empty(), prototype: JsObject::with_null_proto(),
} }
} }
} }
@ -52,7 +52,7 @@ impl StandardConstructor {
/// Build a constructor with a defined prototype. /// Build a constructor with a defined prototype.
fn with_prototype(prototype: JsObject) -> Self { fn with_prototype(prototype: JsObject) -> Self {
Self { Self {
constructor: JsObject::empty(), constructor: JsObject::with_null_proto(),
prototype, prototype,
} }
} }

413
boa_engine/src/context/mod.rs

@ -17,13 +17,12 @@ use crate::{
builtins::{self, function::NativeFunctionSignature}, builtins::{self, function::NativeFunctionSignature},
bytecompiler::ByteCompiler, bytecompiler::ByteCompiler,
class::{Class, ClassBuilder}, class::{Class, ClassBuilder},
error::JsNativeError,
job::JobCallback, job::JobCallback,
object::{FunctionBuilder, GlobalPropertyMap, JsObject, ObjectData}, object::{FunctionBuilder, GlobalPropertyMap, JsObject},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm, realm::Realm,
vm::{CallFrame, CodeBlock, FinallyReturn, GeneratorResumeKind, Vm}, vm::{CallFrame, CodeBlock, FinallyReturn, GeneratorResumeKind, Vm},
JsResult, JsString, JsValue, JsResult, JsValue,
}; };
use boa_ast::StatementList; use boa_ast::StatementList;
@ -131,6 +130,8 @@ impl Default for Context {
} }
} }
// ==== Public API ====
impl Context { impl Context {
/// Create a new [`ContextBuilder`] to specify the [`Interner`] and/or /// Create a new [`ContextBuilder`] to specify the [`Interner`] and/or
/// the icu data provider. /// the icu data provider.
@ -139,44 +140,34 @@ impl Context {
ContextBuilder::default() ContextBuilder::default()
} }
/// Gets the string interner. /// Evaluates the given code by compiling down to bytecode, then interpreting the bytecode into a value
#[inline] ///
pub const fn interner(&self) -> &Interner { /// # Examples
&self.interner /// ```
} /// # use boa_engine::Context;
/// let mut context = Context::default();
/// Gets a mutable reference to the string interner. ///
#[inline] /// let value = context.eval("1 + 3").unwrap();
pub fn interner_mut(&mut self) -> &mut Interner { ///
&mut self.interner /// assert!(value.is_number());
} /// assert_eq!(value.as_number().unwrap(), 4.0);
/// ```
/// A helper function for getting an immutable reference to the `console` object. #[allow(clippy::unit_arg, clippy::drop_copy)]
#[cfg(feature = "console")] pub fn eval<S>(&mut self, src: S) -> JsResult<JsValue>
pub(crate) const fn console(&self) -> &Console { where
&self.console S: AsRef<[u8]>,
} {
let main_timer = Profiler::global().start_event("Evaluation", "Main");
/// A helper function for getting a mutable reference to the `console` object. let statement_list = self.parse(src)?;
#[cfg(feature = "console")] let code_block = self.compile(&statement_list)?;
pub(crate) fn console_mut(&mut self) -> &mut Console { let result = self.execute(code_block);
&mut self.console
}
/// Sets up the default global objects within Global // The main_timer needs to be dropped before the Profiler is.
fn create_intrinsics(&mut self) { drop(main_timer);
let _timer = Profiler::global().start_event("create_intrinsics", "interpreter"); Profiler::global().drop();
// Create intrinsics, add global objects here
builtins::init(self);
}
/// Constructs an object with the `%Object.prototype%` prototype. result
#[inline]
pub fn construct_object(&self) -> JsObject {
JsObject::from_proto_and_data(
self.intrinsics().constructors().object().prototype(),
ObjectData::ordinary(),
)
} }
/// Parse the given source text. /// Parse the given source text.
@ -184,70 +175,91 @@ impl Context {
where where
S: AsRef<[u8]>, S: AsRef<[u8]>,
{ {
let _timer = Profiler::global().start_event("Parsing", "Main");
let mut parser = Parser::new(src.as_ref()); let mut parser = Parser::new(src.as_ref());
parser.parse_all(&mut self.interner) parser.parse_all(&mut self.interner)
} }
/// `Call ( F, V [ , argumentsList ] )` /// Compile the AST into a `CodeBlock` ready to be executed by the VM.
/// pub fn compile(&mut self, statement_list: &StatementList) -> JsResult<Gc<CodeBlock>> {
/// The abstract operation `Call` takes arguments `F` (an ECMAScript language value) and `V` let _timer = Profiler::global().start_event("Compilation", "Main");
/// (an ECMAScript language value) and optional argument `argumentsList` (a `List` of let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), false, self);
/// ECMAScript language values) and returns either a normal completion containing an ECMAScript compiler.create_decls(statement_list, false);
/// language value or a throw completion. It is used to call the `[[Call]]` internal method of compiler.compile_statement_list(statement_list, true, false)?;
/// a function object. `F` is the function object, `V` is an ECMAScript language value that is Ok(Gc::new(compiler.finish()))
/// the `this` value of the `[[Call]]`, and `argumentsList` is the value passed to the
/// corresponding argument of the internal method. If `argumentsList` is not present, a new
/// empty `List` is used as its value.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-call
pub(crate) fn call(
&mut self,
f: &JsValue,
v: &JsValue,
arguments_list: &[JsValue],
) -> JsResult<JsValue> {
// 1. If argumentsList is not present, set argumentsList to a new empty List.
// 2. If IsCallable(F) is false, throw a TypeError exception.
// 3. Return ? F.[[Call]](V, argumentsList).
f.as_callable()
.ok_or_else(|| {
JsNativeError::typ()
.with_message("Value is not callable")
.into()
})
.and_then(|f| f.call(v, arguments_list, self))
} }
/// Return the global object. /// Call the VM with a `CodeBlock` and return the result.
#[inline] ///
pub const fn global_object(&self) -> &JsObject { /// Since this function receives a `Gc<CodeBlock>`, cloning the code is very cheap, since it's
self.realm.global_object() /// just a pointer copy. Therefore, if you'd like to execute the same `CodeBlock` multiple
} /// times, there is no need to re-compile it, and you can just call `clone()` on the
/// `Gc<CodeBlock>` returned by the [`Self::compile()`] function.
pub fn execute(&mut self, code_block: Gc<CodeBlock>) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Execution", "Main");
/// Return a mutable reference to the global object string bindings. self.vm.push_frame(CallFrame {
pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap { code: code_block,
self.realm.global_bindings_mut() pc: 0,
catch: Vec::new(),
finally_return: FinallyReturn::None,
finally_jump: Vec::new(),
pop_on_return: 0,
loop_env_stack: Vec::from([0]),
try_env_stack: Vec::from([crate::vm::TryStackEntry {
num_env: 0,
num_loop_stack_entries: 0,
}]),
param_count: 0,
arg_count: 0,
generator_resume_kind: GeneratorResumeKind::Normal,
thrown: false,
async_generator: None,
});
self.realm.set_global_binding_number();
let result = self.run();
self.vm.pop_frame();
self.clear_kept_objects();
self.run_queued_jobs()?;
let (result, _) = result?;
Ok(result)
} }
/// Constructs a `Error` with the specified message. /// Register a global property.
pub fn construct_error<M>(&mut self, message: M) -> JsValue ///
/// # Example
/// ```
/// use boa_engine::{
/// object::ObjectInitializer,
/// property::{Attribute, PropertyDescriptor},
/// Context,
/// };
///
/// let mut context = Context::default();
///
/// context.register_global_property("myPrimitiveProperty", 10, Attribute::all());
///
/// let object = ObjectInitializer::new(&mut context)
/// .property("x", 0, Attribute::all())
/// .property("y", 1, Attribute::all())
/// .build();
/// context.register_global_property("myObjectProperty", object, Attribute::all());
/// ```
pub fn register_global_property<K, V>(&mut self, key: K, value: V, attribute: Attribute)
where where
M: Into<JsString>, K: Into<PropertyKey>,
V: Into<JsValue>,
{ {
crate::builtins::error::Error::constructor( self.realm.global_property_map.insert(
&self &key.into(),
.intrinsics() PropertyDescriptor::builder()
.constructors() .value(value)
.error() .writable(attribute.writable())
.constructor() .enumerable(attribute.enumerable())
.into(), .configurable(attribute.configurable())
&[message.into().into()], .build(),
self, );
)
.expect("Into<String> used as message")
} }
/// Register a global native function. /// Register a global native function.
@ -372,12 +384,6 @@ impl Context {
Ok(()) Ok(())
} }
/// <https://tc39.es/ecma262/#sec-hasproperty>
pub(crate) fn has_property(&mut self, obj: &JsValue, key: &PropertyKey) -> JsResult<bool> {
obj.as_object()
.map_or(Ok(false), |obj| obj.__has_property__(key, self))
}
/// Register a global class of type `T`, where `T` implements `Class`. /// Register a global class of type `T`, where `T` implements `Class`.
/// ///
/// # Example /// # Example
@ -410,84 +416,82 @@ impl Context {
Ok(()) Ok(())
} }
/// Register a global property. /// Gets the string interner.
/// #[inline]
/// # Example pub const fn interner(&self) -> &Interner {
/// ``` &self.interner
/// use boa_engine::{
/// object::ObjectInitializer,
/// property::{Attribute, PropertyDescriptor},
/// Context,
/// };
///
/// let mut context = Context::default();
///
/// context.register_global_property("myPrimitiveProperty", 10, Attribute::all());
///
/// let object = ObjectInitializer::new(&mut context)
/// .property("x", 0, Attribute::all())
/// .property("y", 1, Attribute::all())
/// .build();
/// context.register_global_property("myObjectProperty", object, Attribute::all());
/// ```
pub fn register_global_property<K, V>(&mut self, key: K, value: V, attribute: Attribute)
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
self.realm.global_property_map.insert(
&key.into(),
PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable())
.build(),
);
} }
/// Evaluates the given code by compiling down to bytecode, then interpreting the bytecode into a value /// Gets a mutable reference to the string interner.
#[inline]
pub fn interner_mut(&mut self) -> &mut Interner {
&mut self.interner
}
/// Return the global object.
#[inline]
pub const fn global_object(&self) -> &JsObject {
self.realm.global_object()
}
/// Return the intrinsic constructors and objects.
#[inline]
pub const fn intrinsics(&self) -> &Intrinsics {
&self.intrinsics
}
/// Set the value of trace on the context
pub fn set_trace(&mut self, trace: bool) {
self.vm.trace = trace;
}
/// More information:
/// - [ECMAScript reference][spec]
/// ///
/// # Examples /// [spec]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob
/// ``` pub fn host_enqueue_promise_job(&mut self, job: JobCallback /* , realm: Realm */) {
/// # use boa_engine::Context; // If realm is not null ...
/// let mut context = Context::default(); // TODO
// Let scriptOrModule be ...
// TODO
self.promise_job_queue.push_back(job);
}
/// Abstract operation [`ClearKeptObjects`][clear].
/// ///
/// let value = context.eval("1 + 3").unwrap(); /// Clears all objects maintained alive by calls to the [`AddToKeptObjects`][add] abstract
/// operation, used within the [`WeakRef`][weak] constructor.
/// ///
/// assert!(value.is_number()); /// [clear]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-clear-kept-objects
/// assert_eq!(value.as_number().unwrap(), 4.0); /// [add]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-addtokeptobjects
/// ``` /// [weak]: https://tc39.es/ecma262/multipage/managing-memory.html#sec-weak-ref-objects
#[allow(clippy::unit_arg, clippy::drop_copy)] pub fn clear_kept_objects(&mut self) {
pub fn eval<S>(&mut self, src: S) -> JsResult<JsValue> self.kept_alive.clear();
where }
S: AsRef<[u8]>, }
{
let main_timer = Profiler::global().start_event("Evaluation", "Main");
let statement_list = Parser::new(src.as_ref()).parse_all(&mut self.interner)?;
let code_block = self.compile(&statement_list)?; // ==== Private API ====
let result = self.execute(code_block);
// The main_timer needs to be dropped before the Profiler is. impl Context {
drop(main_timer); /// A helper function for getting an immutable reference to the `console` object.
Profiler::global().drop(); #[cfg(feature = "console")]
pub(crate) const fn console(&self) -> &Console {
&self.console
}
result /// A helper function for getting a mutable reference to the `console` object.
#[cfg(feature = "console")]
pub(crate) fn console_mut(&mut self) -> &mut Console {
&mut self.console
} }
/// Compile the AST into a `CodeBlock` ready to be executed by the VM. /// Return a mutable reference to the global object string bindings.
pub fn compile(&mut self, statement_list: &StatementList) -> JsResult<Gc<CodeBlock>> { pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap {
let _timer = Profiler::global().start_event("Compilation", "Main"); self.realm.global_bindings_mut()
let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), false, self);
compiler.create_decls(statement_list, false);
compiler.compile_statement_list(statement_list, true, false)?;
Ok(Gc::new(compiler.finish()))
} }
/// Compile the AST into a `CodeBlock` ready to be executed by the VM in a `JSON.parse` context. /// Compile the AST into a `CodeBlock` ready to be executed by the VM in a `JSON.parse` context.
pub fn compile_json_parse( pub(crate) fn compile_json_parse(
&mut self, &mut self,
statement_list: &StatementList, statement_list: &StatementList,
) -> JsResult<Gc<CodeBlock>> { ) -> JsResult<Gc<CodeBlock>> {
@ -510,41 +514,17 @@ impl Context {
Ok(Gc::new(compiler.finish())) Ok(Gc::new(compiler.finish()))
} }
/// Call the VM with a `CodeBlock` and return the result. /// Get the ICU related utilities
/// #[cfg(feature = "intl")]
/// Since this function receives a `Gc<CodeBlock>`, cloning the code is very cheap, since it's pub(crate) const fn icu(&self) -> &icu::Icu<BoaProvider> {
/// just a pointer copy. Therefore, if you'd like to execute the same `CodeBlock` multiple &self.icu
/// times, there is no need to re-compile it, and you can just call `clone()` on the }
/// `Gc<CodeBlock>` returned by the [`Self::compile()`] function.
pub fn execute(&mut self, code_block: Gc<CodeBlock>) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Execution", "Main");
self.vm.push_frame(CallFrame {
code: code_block,
pc: 0,
catch: Vec::new(),
finally_return: FinallyReturn::None,
finally_jump: Vec::new(),
pop_on_return: 0,
loop_env_stack: Vec::from([0]),
try_env_stack: Vec::from([crate::vm::TryStackEntry {
num_env: 0,
num_loop_stack_entries: 0,
}]),
param_count: 0,
arg_count: 0,
generator_resume_kind: GeneratorResumeKind::Normal,
thrown: false,
async_generator: None,
});
self.realm.set_global_binding_number(); /// Sets up the default global objects within Global
let result = self.run(); fn create_intrinsics(&mut self) {
self.vm.pop_frame(); let _timer = Profiler::global().start_event("create_intrinsics", "interpreter");
self.clear_kept_objects(); // Create intrinsics, add global objects here
self.run_queued_jobs()?; builtins::init(self);
let (result, _) = result?;
Ok(result)
} }
/// Runs all the jobs in the job queue. /// Runs all the jobs in the job queue.
@ -555,47 +535,6 @@ impl Context {
} }
Ok(()) Ok(())
} }
/// Return the intrinsic constructors and objects.
#[inline]
pub const fn intrinsics(&self) -> &Intrinsics {
&self.intrinsics
}
/// Set the value of trace on the context
pub fn set_trace(&mut self, trace: bool) {
self.vm.trace = trace;
}
#[cfg(feature = "intl")]
/// Get the ICU related utilities
pub(crate) const fn icu(&self) -> &icu::Icu<BoaProvider> {
&self.icu
}
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob
pub fn host_enqueue_promise_job(&mut self, job: JobCallback /* , realm: Realm */) {
// If realm is not null ...
// TODO
// Let scriptOrModule be ...
// TODO
self.promise_job_queue.push_back(job);
}
/// Abstract operation [`ClearKeptObjects`][clear].
///
/// Clears all objects maintained alive by calls to the [`AddToKeptObjects`][add] abstract
/// operation, used within the [`WeakRef`][weak] constructor.
///
/// [clear]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-clear-kept-objects
/// [add]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-addtokeptobjects
/// [weak]: https://tc39.es/ecma262/multipage/managing-memory.html#sec-weak-ref-objects
pub fn clear_kept_objects(&mut self) {
self.kept_alive.clear();
}
} }
/// Builder for the [`Context`] type. /// Builder for the [`Context`] type.
/// ///

15
boa_engine/src/object/builtins/jsarraybuffer.rs

@ -91,8 +91,6 @@ impl JsArrayBuffer {
StandardConstructors::array_buffer, StandardConstructors::array_buffer,
context, context,
)?; )?;
let obj = context.construct_object();
obj.set_prototype(prototype.into());
// 2. Let block be ? CreateByteDataBlock(byteLength). // 2. Let block be ? CreateByteDataBlock(byteLength).
// //
@ -102,11 +100,14 @@ impl JsArrayBuffer {
// 3. Set obj.[[ArrayBufferData]] to block. // 3. Set obj.[[ArrayBufferData]] to block.
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer { let obj = JsObject::from_proto_and_data(
array_buffer_data: Some(block), prototype,
array_buffer_byte_length: byte_length as u64, ObjectData::array_buffer(ArrayBuffer {
array_buffer_detach_key: JsValue::Undefined, array_buffer_data: Some(block),
}); array_buffer_byte_length: byte_length as u64,
array_buffer_detach_key: JsValue::Undefined,
}),
);
Ok(Self { inner: obj }) Ok(Self { inner: obj })
} }

2
boa_engine/src/object/builtins/jsproxy.rs

@ -371,7 +371,7 @@ impl JsProxyBuilder {
/// inside Rust code. /// inside Rust code.
#[must_use] #[must_use]
pub fn build(self, context: &mut Context) -> JsProxy { pub fn build(self, context: &mut Context) -> JsProxy {
let handler = context.construct_object(); let handler = JsObject::with_object_proto(context);
if let Some(apply) = self.apply { if let Some(apply) = self.apply {
let f = FunctionBuilder::native(context, apply).length(3).build(); let f = FunctionBuilder::native(context, apply).length(3).build();

4
boa_engine/src/object/internal_methods/global.rs

@ -254,7 +254,7 @@ pub(crate) fn global_get(
// 6. Let getter be desc.[[Get]]. // 6. Let getter be desc.[[Get]].
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
// 8. Return ? Call(getter, Receiver). // 8. Return ? Call(getter, Receiver).
context.call(get, &receiver, &[]) get.call(&receiver, &[], context)
} }
// 7. If getter is undefined, return undefined. // 7. If getter is undefined, return undefined.
_ => Ok(JsValue::undefined()), _ => Ok(JsValue::undefined()),
@ -360,7 +360,7 @@ pub(crate) fn global_set_no_receiver(
match own_desc.set() { match own_desc.set() {
Some(set) if !set.is_undefined() => { Some(set) if !set.is_undefined() => {
// 7. Perform ? Call(setter, Receiver, « V »). // 7. Perform ? Call(setter, Receiver, « V »).
context.call(set, &context.global_object().clone().into(), &[value])?; set.call(&context.global_object().clone().into(), &[value], context)?;
// 8. Return true. // 8. Return true.
Ok(true) Ok(true)

4
boa_engine/src/object/internal_methods/mod.rs

@ -544,7 +544,7 @@ pub(crate) fn ordinary_get(
// 6. Let getter be desc.[[Get]]. // 6. Let getter be desc.[[Get]].
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
// 8. Return ? Call(getter, Receiver). // 8. Return ? Call(getter, Receiver).
context.call(get, &receiver, &[]) get.call(&receiver, &[], context)
} }
// 7. If getter is undefined, return undefined. // 7. If getter is undefined, return undefined.
_ => Ok(JsValue::undefined()), _ => Ok(JsValue::undefined()),
@ -644,7 +644,7 @@ pub(crate) fn ordinary_set(
match own_desc.set() { match own_desc.set() {
Some(set) if !set.is_undefined() => { Some(set) if !set.is_undefined() => {
// 7. Perform ? Call(setter, Receiver, « V »). // 7. Perform ? Call(setter, Receiver, « V »).
context.call(set, &receiver, &[value])?; set.call(&receiver, &[value], context)?;
// 8. Return true. // 8. Return true.
Ok(true) Ok(true)

58
boa_engine/src/object/jsobject.rs

@ -33,32 +33,50 @@ pub struct JsObject {
} }
impl JsObject { impl JsObject {
/// Create a new `JsObject` from an internal `Object`. /// Creates a new ordinary object with its prototype set to the `Object` prototype.
fn from_object(object: Object) -> Self { ///
Self { /// This is equivalent to calling the specification's abstract operation
inner: Gc::new(GcCell::new(object)), /// [`OrdinaryObjectCreate(%Object.prototype%)`][call].
} ///
/// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
#[inline]
#[must_use]
pub fn with_object_proto(context: &mut Context) -> Self {
Self::from_proto_and_data(
context.intrinsics().constructors().object().prototype(),
ObjectData::ordinary(),
)
} }
/// Create a new empty `JsObject`, with `prototype` set to `JsValue::Null` /// Creates a new ordinary object, with its prototype set to null.
/// and `data` set to `ObjectData::ordinary` ///
/// This is equivalent to calling the specification's abstract operation
/// [`OrdinaryObjectCreate(null)`][call].
///
/// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
#[inline]
#[must_use] #[must_use]
pub fn empty() -> Self { pub fn with_null_proto() -> Self {
Self::from_object(Object::default()) Self::from_proto_and_data(None, ObjectData::ordinary())
} }
/// The more general form of `OrdinaryObjectCreate` and `MakeBasicObject`. /// Creates a new object with the provided prototype and object data.
///
/// This is equivalent to calling the specification's abstract operation [`OrdinaryObjectCreate`],
/// with the difference that the `additionalInternalSlotsList` parameter is automatically set by
/// the [`ObjectData`] provided.
/// ///
/// Create a `JsObject` and automatically set its internal methods and /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
/// internal slots from the `data` provided.
pub fn from_proto_and_data<O: Into<Option<Self>>>(prototype: O, data: ObjectData) -> Self { pub fn from_proto_and_data<O: Into<Option<Self>>>(prototype: O, data: ObjectData) -> Self {
Self::from_object(Object { Self {
data, inner: Gc::new(GcCell::new(Object {
prototype: prototype.into(), data,
extensible: true, prototype: prototype.into(),
properties: PropertyMap::default(), extensible: true,
private_elements: FxHashMap::default(), properties: PropertyMap::default(),
}) private_elements: FxHashMap::default(),
})),
}
} }
/// Immutably borrows the `Object`. /// Immutably borrows the `Object`.
@ -80,7 +98,7 @@ impl JsObject {
/// The borrow lasts until the returned `RefMut` exits scope. /// The borrow lasts until the returned `RefMut` exits scope.
/// The object cannot be borrowed while this borrow is active. /// The object cannot be borrowed while this borrow is active.
/// ///
///# Panics /// # Panics
/// Panics if the object is currently borrowed. /// Panics if the object is currently borrowed.
#[inline] #[inline]
#[track_caller] #[track_caller]

6
boa_engine/src/object/mod.rs

@ -2095,7 +2095,7 @@ impl<'context> ObjectInitializer<'context> {
/// Create a new `ObjectBuilder`. /// Create a new `ObjectBuilder`.
#[inline] #[inline]
pub fn new(context: &'context mut Context) -> Self { pub fn new(context: &'context mut Context) -> Self {
let object = context.construct_object(); let object = JsObject::with_object_proto(context);
Self { context, object } Self { context, object }
} }
@ -2186,8 +2186,8 @@ impl<'context> ConstructorBuilder<'context> {
Self { Self {
context, context,
function, function,
object: JsObject::empty(), object: JsObject::with_null_proto(),
prototype: JsObject::empty(), prototype: JsObject::with_null_proto(),
length: 0, length: 0,
name: js_string!(), name: js_string!(),
callable: true, callable: true,

31
boa_engine/src/object/operations.rs

@ -762,6 +762,35 @@ impl JsValue {
Ok(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: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
self.as_callable()
.ok_or_else(|| {
JsNativeError::typ().with_message(format!(
"value with type `{}` is not callable",
self.type_of()
))
})?
.__call__(this, args, context)
}
/// Abstract operation `( V, P [ , argumentsList ] )` /// Abstract operation `( V, P [ , argumentsList ] )`
/// ///
/// Calls a method property of an ECMAScript language value. /// Calls a method property of an ECMAScript language value.
@ -779,7 +808,7 @@ impl JsValue {
let func = self.get_v(key, context)?; let func = self.get_v(key, context)?;
// 3. Return ? Call(func, V, argumentsList) // 3. Return ? Call(func, V, argumentsList)
context.call(&func, self, args) func.call(self, args, context)
} }
/// Abstract operation `OrdinaryHasInstance ( C, O )` /// Abstract operation `OrdinaryHasInstance ( C, O )`

3
boa_engine/src/value/serde_json.rs

@ -4,6 +4,7 @@ use super::JsValue;
use crate::{ use crate::{
builtins::Array, builtins::Array,
error::JsNativeError, error::JsNativeError,
object::JsObject,
property::{PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
Context, JsResult, Context, JsResult,
}; };
@ -63,7 +64,7 @@ impl JsValue {
Ok(Array::create_array_from_list(arr, context).into()) Ok(Array::create_array_from_list(arr, context).into())
} }
Value::Object(obj) => { Value::Object(obj) => {
let js_obj = context.construct_object(); let js_obj = JsObject::with_object_proto(context);
for (key, value) in obj { for (key, value) in obj {
let property = PropertyDescriptor::builder() let property = PropertyDescriptor::builder()
.value(Self::from_json(value, context)?) .value(Self::from_json(value, context)?)

6
boa_engine/src/value/tests.rs

@ -24,7 +24,7 @@ fn undefined() {
#[test] #[test]
fn get_set_field() { fn get_set_field() {
let mut context = Context::default(); let mut context = Context::default();
let obj = &context.construct_object(); let obj = &JsObject::with_object_proto(&mut context);
// Create string and convert it to a Value // Create string and convert it to a Value
let s = JsValue::new("bar"); let s = JsValue::new("bar");
obj.set("foo", s, false, &mut context).unwrap(); obj.set("foo", s, false, &mut context).unwrap();
@ -132,11 +132,11 @@ fn hash_rational() {
#[test] #[test]
#[allow(clippy::eq_op)] #[allow(clippy::eq_op)]
fn hash_object() { fn hash_object() {
let object1 = JsValue::new(JsObject::empty()); let object1 = JsValue::new(JsObject::with_null_proto());
assert_eq!(object1, object1); assert_eq!(object1, object1);
assert_eq!(object1, object1.clone()); assert_eq!(object1, object1.clone());
let object2 = JsValue::new(JsObject::empty()); let object2 = JsValue::new(JsObject::with_null_proto());
assert_ne!(object1, object2); assert_ne!(object1, object2);
assert_eq!(hash_value(&object1), hash_value(&object1.clone())); assert_eq!(hash_value(&object1), hash_value(&object1.clone()));

3
boa_engine/src/vm/code_block.rs

@ -506,8 +506,6 @@ pub(crate) fn create_function_object(
context.intrinsics().constructors().function().prototype() context.intrinsics().constructors().function().prototype()
}; };
let prototype = context.construct_object();
let name_property = PropertyDescriptor::builder() let name_property = PropertyDescriptor::builder()
.value( .value(
context context
@ -566,6 +564,7 @@ pub(crate) fn create_function_object(
.configurable(true) .configurable(true)
.build(); .build();
let prototype = JsObject::with_object_proto(context);
prototype prototype
.define_property_or_throw(js_string!("constructor"), constructor_property, context) .define_property_or_throw(js_string!("constructor"), constructor_property, context)
.expect("failed to define the constructor property of the function"); .expect("failed to define the constructor property of the function");

6
boa_engine/src/vm/opcode/binary_ops/mod.rs

@ -83,16 +83,16 @@ impl Operation for In {
let rhs = context.vm.pop(); let rhs = context.vm.pop();
let lhs = context.vm.pop(); let lhs = context.vm.pop();
if !rhs.is_object() { let Some(rhs) = rhs.as_object() else {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message(format!( .with_message(format!(
"right-hand side of 'in' should be an object, got `{}`", "right-hand side of 'in' should be an object, got `{}`",
rhs.type_of() rhs.type_of()
)) ))
.into()); .into());
} };
let key = lhs.to_property_key(context)?; let key = lhs.to_property_key(context)?;
let value = context.has_property(&rhs, &key)?; let value = rhs.has_property(key, context)?;
context.vm.push(value); context.vm.push(value);
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }

10
boa_engine/src/vm/opcode/generator/mod.rs

@ -147,18 +147,18 @@ impl Operation for GeneratorNextDelegate {
match context.vm.frame().generator_resume_kind { match context.vm.frame().generator_resume_kind {
GeneratorResumeKind::Normal => { GeneratorResumeKind::Normal => {
let result = context.call(&next_method, &iterator.clone().into(), &[received])?; let result = next_method.call(&iterator.clone().into(), &[received], context)?;
let result_object = result.as_object().ok_or_else(|| { let result = result.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("generator next method returned non-object") JsNativeError::typ().with_message("generator next method returned non-object")
})?; })?;
let done = result_object.get("done", context)?.to_boolean(); let done = result.get("done", context)?.to_boolean();
if done { if done {
context.vm.frame_mut().pc = done_address as usize; context.vm.frame_mut().pc = done_address as usize;
let value = result_object.get("value", context)?; let value = result.get("value", context)?;
context.vm.push(value); context.vm.push(value);
return Ok(ShouldExit::False); return Ok(ShouldExit::False);
} }
let value = result_object.get("value", context)?; let value = result.get("value", context)?;
context.vm.push(iterator.clone()); context.vm.push(iterator.clone());
context.vm.push(next_method.clone()); context.vm.push(next_method.clone());
context.vm.push(done); context.vm.push(done);

4
boa_engine/src/vm/opcode/get/name.rs

@ -40,7 +40,7 @@ impl Operation for GetName {
} => value.clone(), } => value.clone(),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
let get = get.clone(); let get = get.clone();
context.call(&get, &context.global_object().clone().into(), &[])? get.call(&context.global_object().clone().into(), &[], context)?
} }
_ => { _ => {
return Err(JsNativeError::reference() return Err(JsNativeError::reference()
@ -113,7 +113,7 @@ impl Operation for GetNameOrUndefined {
} => value.clone(), } => value.clone(),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
let get = get.clone(); let get = get.clone();
context.call(&get, &context.global_object().clone().into(), &[])? get.call(&context.global_object().clone().into(), &[], context)?
} }
_ => JsValue::undefined(), _ => JsValue::undefined(),
}, },

4
boa_engine/src/vm/opcode/push/object.rs

@ -1,4 +1,5 @@
use crate::{ use crate::{
object::JsObject,
vm::{opcode::Operation, ShouldExit}, vm::{opcode::Operation, ShouldExit},
Context, JsResult, Context, JsResult,
}; };
@ -15,7 +16,8 @@ impl Operation for PushEmptyObject {
const INSTRUCTION: &'static str = "INST - PushEmptyObject"; const INSTRUCTION: &'static str = "INST - PushEmptyObject";
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
context.vm.push(context.construct_object()); let o = JsObject::with_object_proto(context);
context.vm.push(o);
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }

2
boa_examples/src/bin/closures.rs

@ -42,7 +42,7 @@ fn main() -> Result<(), JsError> {
} }
// We create a new `JsObject` with some data // We create a new `JsObject` with some data
let object = context.construct_object(); let object = JsObject::with_object_proto(&mut context);
object.define_property_or_throw( object.define_property_or_throw(
"name", "name",
PropertyDescriptor::builder() PropertyDescriptor::builder()

Loading…
Cancel
Save