Browse Source

Disallow changing type of already created objects (#3410)

* Disallow changing type of already created objects

* Fix regressions

* Fix spec steps

* Rollback restrictions and add new conversion
pull/3422/head
José Julián Espina 1 year ago committed by GitHub
parent
commit
06bb7dea80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      boa_ast/src/expression/regexp.rs
  2. 6
      boa_cli/src/main.rs
  3. 31
      boa_engine/src/builtins/error/type.rs
  4. 199
      boa_engine/src/builtins/mod.rs
  5. 150
      boa_engine/src/builtins/regexp/mod.rs
  6. 30
      boa_engine/src/context/intrinsics.rs
  7. 4
      boa_engine/src/context/mod.rs
  8. 26
      boa_engine/src/module/mod.rs
  9. 1
      boa_engine/src/module/synthetic.rs
  10. 34
      boa_engine/src/native_function.rs
  11. 12
      boa_engine/src/object/builtins/jsfunction.rs
  12. 305
      boa_engine/src/object/builtins/jspromise.rs
  13. 10
      boa_engine/src/object/builtins/jsregexp.rs
  14. 41
      boa_engine/src/object/internal_methods/function.rs
  15. 102
      boa_engine/src/object/mod.rs
  16. 4
      boa_engine/src/object/operations.rs
  17. 17
      boa_examples/src/bin/modules.rs
  18. 4
      boa_examples/src/bin/synthetic.rs
  19. 25
      boa_tester/src/exec/mod.rs

6
boa_ast/src/expression/regexp.rs

@ -35,21 +35,21 @@ pub struct RegExpLiteral {
} }
impl RegExpLiteral { impl RegExpLiteral {
/// Create a new [`RegExp`]. /// Create a new [`RegExpLiteral`].
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new(pattern: Sym, flags: Sym) -> Self { pub const fn new(pattern: Sym, flags: Sym) -> Self {
Self { pattern, flags } Self { pattern, flags }
} }
/// Get the pattern part of the [`RegExp`]. /// Get the pattern part of the [`RegExpLiteral`].
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn pattern(&self) -> Sym { pub const fn pattern(&self) -> Sym {
self.pattern self.pattern
} }
/// Get the flags part of the [`RegExp`]. /// Get the flags part of the [`RegExpLiteral`].
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn flags(&self) -> Sym { pub const fn flags(&self) -> Sym {

6
boa_cli/src/main.rs

@ -325,7 +325,7 @@ fn evaluate_files(
Err(v) => eprintln!("Uncaught {v}"), Err(v) => eprintln!("Uncaught {v}"),
} }
} else if args.module { } else if args.module {
let result = (|| { let result: JsResult<PromiseState> = (|| {
let module = Module::parse(Source::from_bytes(&buffer), None, context)?; let module = Module::parse(Source::from_bytes(&buffer), None, context)?;
loader.insert( loader.insert(
@ -334,10 +334,10 @@ fn evaluate_files(
module.clone(), module.clone(),
); );
let promise = module.load_link_evaluate(context)?; let promise = module.load_link_evaluate(context);
context.run_jobs(); context.run_jobs();
promise.state() Ok(promise.state())
})(); })();
match result { match result {

31
boa_engine/src/builtins/error/type.rs

@ -20,7 +20,8 @@ use crate::{
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError, error::JsNativeError,
js_string, js_string,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, ObjectKind}, native_function::NativeFunctionObject,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::Attribute, property::Attribute,
realm::Realm, realm::Realm,
string::{common::StaticJsStrings, utf16}, string::{common::StaticJsStrings, utf16},
@ -115,15 +116,6 @@ pub(crate) struct ThrowTypeError;
impl IntrinsicObject for ThrowTypeError { impl IntrinsicObject for ThrowTypeError {
fn init(realm: &Realm) { fn init(realm: &Realm) {
fn throw_type_error(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
Err(JsNativeError::typ()
.with_message(
"'caller', 'callee', and 'arguments' properties may not be accessed on strict mode \
functions or the arguments objects for calls to them",
)
.into())
}
let obj = BuiltInBuilder::with_intrinsic::<Self>(realm) let obj = BuiltInBuilder::with_intrinsic::<Self>(realm)
.prototype(realm.intrinsics().constructors().function().prototype()) .prototype(realm.intrinsics().constructors().function().prototype())
.static_property(utf16!("length"), 0, Attribute::empty()) .static_property(utf16!("length"), 0, Attribute::empty())
@ -132,12 +124,21 @@ impl IntrinsicObject for ThrowTypeError {
let mut obj = obj.borrow_mut(); let mut obj = obj.borrow_mut();
obj.extensible = false; *obj.as_native_function_mut()
*obj.kind_mut() = ObjectKind::NativeFunction { .expect("`%ThrowTypeError%` must be a function") = NativeFunctionObject {
function: NativeFunction::from_fn_ptr(throw_type_error), f: NativeFunction::from_fn_ptr(|_, _, _| {
Err(JsNativeError::typ()
.with_message(
"'caller', 'callee', and 'arguments' properties may not be accessed on strict mode \
functions or the arguments objects for calls to them",
)
.into())
}),
constructor: None, constructor: None,
realm: realm.clone(), realm: Some(realm.clone()),
} };
obj.extensible = false;
} }
fn get(intrinsics: &Intrinsics) -> JsObject { fn get(intrinsics: &Intrinsics) -> JsObject {

199
boa_engine/src/builtins/mod.rs

@ -46,6 +46,9 @@ pub(crate) mod options;
#[cfg(feature = "temporal")] #[cfg(feature = "temporal")]
pub mod temporal; pub mod temporal;
use boa_gc::GcRefMut;
use self::function::ConstructorKind;
pub(crate) use self::{ pub(crate) use self::{
array::Array, array::Array,
async_function::AsyncFunction, async_function::AsyncFunction,
@ -100,11 +103,11 @@ use crate::{
}, },
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string, js_string,
native_function::{NativeFunction, NativeFunctionPointer}, native_function::{NativeFunction, NativeFunctionObject, NativeFunctionPointer},
object::{ object::{
shape::{property_table::PropertyTableInner, slot::SlotAttributes}, shape::{property_table::PropertyTableInner, slot::SlotAttributes},
FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, ObjectKind, FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, CONSTRUCTOR,
CONSTRUCTOR, PROTOTYPE, PROTOTYPE,
}, },
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm, realm::Realm,
@ -405,85 +408,6 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult
// === Builder typestate === // === Builder typestate ===
#[derive(Debug)]
enum BuiltInObjectInitializer {
Shared(JsObject),
Unique { object: Object, data: ObjectData },
}
impl BuiltInObjectInitializer {
/// Inserts a new property descriptor into the builtin.
fn insert<K, P>(&mut self, key: K, property: P)
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
match self {
Self::Shared(obj) => obj.borrow_mut().insert(key, property),
Self::Unique { object, .. } => object.insert(key, property),
};
}
/// Sets the prototype of the builtin
fn set_prototype(&mut self, prototype: JsObject) {
match self {
Self::Shared(obj) => {
let mut obj = obj.borrow_mut();
obj.set_prototype(prototype);
}
Self::Unique { object, .. } => {
object.set_prototype(prototype);
}
}
}
/// Sets the `ObjectData` of the builtin.
///
/// # Panics
///
/// Panics if the builtin is a shared builtin and the data's vtable is not the same as the
/// builtin's vtable.
fn set_data(&mut self, new_data: ObjectData) {
match self {
Self::Shared(obj) => {
assert!(
std::ptr::eq(obj.vtable(), new_data.internal_methods),
"intrinsic object's vtable didn't match with new data"
);
*obj.borrow_mut().kind_mut() = new_data.kind;
}
Self::Unique { ref mut data, .. } => *data = new_data,
}
}
/// Gets a shared object from the builtin, transitioning its state if it's necessary.
fn as_shared(&mut self) -> JsObject {
match std::mem::replace(
self,
Self::Unique {
object: Object::default(),
data: ObjectData::ordinary(),
},
) {
Self::Shared(obj) => {
*self = Self::Shared(obj.clone());
obj
}
Self::Unique { mut object, data } => {
*object.kind_mut() = data.kind;
let obj = JsObject::from_object_and_vtable(object, data.internal_methods);
*self = Self::Shared(obj.clone());
obj
}
}
}
/// Converts the builtin into a shared object.
fn into_shared(mut self) -> JsObject {
self.as_shared()
}
}
/// Marker for a constructor function. /// Marker for a constructor function.
struct Constructor { struct Constructor {
prototype: JsObject, prototype: JsObject,
@ -528,11 +452,11 @@ struct OrdinaryObject;
/// Applies the pending builder data to the object. /// Applies the pending builder data to the object.
trait ApplyToObject { trait ApplyToObject {
fn apply_to(self, object: &mut BuiltInObjectInitializer); fn apply_to(self, object: &JsObject);
} }
impl ApplyToObject for Constructor { impl ApplyToObject for Constructor {
fn apply_to(self, object: &mut BuiltInObjectInitializer) { fn apply_to(self, object: &JsObject) {
object.insert( object.insert(
PROTOTYPE, PROTOTYPE,
PropertyDescriptor::builder() PropertyDescriptor::builder()
@ -542,15 +466,13 @@ impl ApplyToObject for Constructor {
.configurable(false), .configurable(false),
); );
let object = object.as_shared();
{ {
let mut prototype = self.prototype.borrow_mut(); let mut prototype = self.prototype.borrow_mut();
prototype.set_prototype(self.inherits); prototype.set_prototype(self.inherits);
prototype.insert( prototype.insert(
CONSTRUCTOR, CONSTRUCTOR,
PropertyDescriptor::builder() PropertyDescriptor::builder()
.value(object) .value(object.clone())
.writable(self.attributes.writable()) .writable(self.attributes.writable())
.enumerable(self.attributes.enumerable()) .enumerable(self.attributes.enumerable())
.configurable(self.attributes.configurable()), .configurable(self.attributes.configurable()),
@ -560,21 +482,23 @@ impl ApplyToObject for Constructor {
} }
impl ApplyToObject for ConstructorNoProto { impl ApplyToObject for ConstructorNoProto {
fn apply_to(self, _: &mut BuiltInObjectInitializer) {} fn apply_to(self, _: &JsObject) {}
} }
impl ApplyToObject for OrdinaryFunction { impl ApplyToObject for OrdinaryFunction {
fn apply_to(self, _: &mut BuiltInObjectInitializer) {} fn apply_to(self, _: &JsObject) {}
} }
impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> { impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
fn apply_to(self, object: &mut BuiltInObjectInitializer) { fn apply_to(self, object: &JsObject) {
let function = ObjectData::native_function( {
NativeFunction::from_fn_ptr(self.function), let mut function =
S::IS_CONSTRUCTOR, GcRefMut::try_map(object.borrow_mut(), Object::as_native_function_mut)
self.realm, .expect("Builtin must be a function object");
); function.f = NativeFunction::from_fn_ptr(self.function);
object.set_data(function); function.constructor = S::IS_CONSTRUCTOR.then_some(ConstructorKind::Base);
function.realm = Some(self.realm);
}
object.insert( object.insert(
utf16!("length"), utf16!("length"),
PropertyDescriptor::builder() PropertyDescriptor::builder()
@ -597,7 +521,7 @@ impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
} }
impl ApplyToObject for OrdinaryObject { impl ApplyToObject for OrdinaryObject {
fn apply_to(self, _: &mut BuiltInObjectInitializer) {} fn apply_to(self, _: &JsObject) {}
} }
/// Builder for creating built-in objects, like `Array`. /// Builder for creating built-in objects, like `Array`.
@ -608,30 +532,18 @@ impl ApplyToObject for OrdinaryObject {
#[must_use = "You need to call the `build` method in order for this to correctly assign the inner data"] #[must_use = "You need to call the `build` method in order for this to correctly assign the inner data"]
struct BuiltInBuilder<'ctx, Kind> { struct BuiltInBuilder<'ctx, Kind> {
realm: &'ctx Realm, realm: &'ctx Realm,
object: BuiltInObjectInitializer, object: JsObject,
kind: Kind, kind: Kind,
prototype: JsObject, prototype: JsObject,
} }
impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
// fn new(realm: &'ctx Realm) -> BuiltInBuilder<'ctx, OrdinaryObject> {
// BuiltInBuilder {
// realm,
// object: BuiltInObjectInitializer::Unique {
// object: Object::default(),
// data: ObjectData::ordinary(),
// },
// kind: OrdinaryObject,
// prototype: realm.intrinsics().constructors().object().prototype(),
// }
// }
fn with_intrinsic<I: IntrinsicObject>( fn with_intrinsic<I: IntrinsicObject>(
realm: &'ctx Realm, realm: &'ctx Realm,
) -> BuiltInBuilder<'ctx, OrdinaryObject> { ) -> BuiltInBuilder<'ctx, OrdinaryObject> {
BuiltInBuilder { BuiltInBuilder {
realm, realm,
object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())), object: I::get(realm.intrinsics()),
kind: OrdinaryObject, kind: OrdinaryObject,
prototype: realm.intrinsics().constructors().object().prototype(), prototype: realm.intrinsics().constructors().object().prototype(),
} }
@ -836,12 +748,6 @@ impl BuiltInConstructorWithPrototype<'_> {
} }
fn build(mut self) { fn build(mut self) {
let function = ObjectKind::NativeFunction {
function: NativeFunction::from_fn_ptr(self.function),
constructor: Some(function::ConstructorKind::Base),
realm: self.realm.clone(),
};
let length = self.length; let length = self.length;
let name = self.name.clone(); let name = self.name.clone();
let prototype = self.prototype.clone(); let prototype = self.prototype.clone();
@ -871,7 +777,12 @@ impl BuiltInConstructorWithPrototype<'_> {
} }
let mut object = self.object.borrow_mut(); let mut object = self.object.borrow_mut();
*object.kind_mut() = function; let function = object
.as_native_function_mut()
.expect("Builtin must be a function object");
function.f = NativeFunction::from_fn_ptr(self.function);
function.constructor = Some(ConstructorKind::Base);
function.realm = Some(self.realm.clone());
object object
.properties_mut() .properties_mut()
.shape .shape
@ -886,19 +797,18 @@ impl BuiltInConstructorWithPrototype<'_> {
} }
fn build_without_prototype(mut self) { fn build_without_prototype(mut self) {
let function = ObjectKind::NativeFunction {
function: NativeFunction::from_fn_ptr(self.function),
constructor: Some(function::ConstructorKind::Base),
realm: self.realm.clone(),
};
let length = self.length; let length = self.length;
let name = self.name.clone(); let name = self.name.clone();
self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE); self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE);
self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE);
let mut object = self.object.borrow_mut(); let mut object = self.object.borrow_mut();
*object.kind_mut() = function; let function = object
.as_native_function_mut()
.expect("Builtin must be a function object");
function.f = NativeFunction::from_fn_ptr(self.function);
function.constructor = Some(ConstructorKind::Base);
function.realm = Some(self.realm.clone());
object object
.properties_mut() .properties_mut()
.shape .shape
@ -940,11 +850,11 @@ impl BuiltInCallable<'_> {
fn build(self) -> JsFunction { fn build(self) -> JsFunction {
let object = self.realm.intrinsics().templates().function().create( let object = self.realm.intrinsics().templates().function().create(
ObjectData::native_function( ObjectData::native_function(NativeFunctionObject {
NativeFunction::from_fn_ptr(self.function), f: NativeFunction::from_fn_ptr(self.function),
false, constructor: None,
self.realm.clone(), realm: Some(self.realm.clone()),
), }),
vec![JsValue::new(self.length), JsValue::new(self.name)], vec![JsValue::new(self.length), JsValue::new(self.name)],
); );
@ -968,7 +878,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> { ) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> {
BuiltInBuilder { BuiltInBuilder {
realm, realm,
object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())), object: I::get(realm.intrinsics()),
kind: Callable { kind: Callable {
function, function,
name: js_string!(""), name: js_string!(""),
@ -987,7 +897,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> { ) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> {
BuiltInBuilder { BuiltInBuilder {
realm, realm,
object: BuiltInObjectInitializer::Shared(object), object,
kind: Callable { kind: Callable {
function, function,
name: js_string!(""), name: js_string!(""),
@ -1025,12 +935,7 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
impl<T> BuiltInBuilder<'_, T> { impl<T> BuiltInBuilder<'_, T> {
/// Adds a new static method to the builtin object. /// Adds a new static method to the builtin object.
fn static_method<B>( fn static_method<B>(self, function: NativeFunctionPointer, binding: B, length: usize) -> Self
mut self,
function: NativeFunctionPointer,
binding: B,
length: usize,
) -> Self
where where
B: Into<FunctionBinding>, B: Into<FunctionBinding>,
{ {
@ -1052,7 +957,7 @@ impl<T> BuiltInBuilder<'_, T> {
} }
/// Adds a new static data property to the builtin object. /// Adds a new static data property to the builtin object.
fn static_property<K, V>(mut self, key: K, value: V, attribute: Attribute) -> Self fn static_property<K, V>(self, key: K, value: V, attribute: Attribute) -> Self
where where
K: Into<PropertyKey>, K: Into<PropertyKey>,
V: Into<JsValue>, V: Into<JsValue>,
@ -1096,22 +1001,22 @@ impl<FnTyp> BuiltInBuilder<'_, Callable<FnTyp>> {
impl BuiltInBuilder<'_, OrdinaryObject> { impl BuiltInBuilder<'_, OrdinaryObject> {
/// Build the builtin object. /// Build the builtin object.
fn build(mut self) -> JsObject { fn build(self) -> JsObject {
self.kind.apply_to(&mut self.object); self.kind.apply_to(&self.object);
self.object.set_prototype(self.prototype); self.object.set_prototype(Some(self.prototype));
self.object.into_shared() self.object
} }
} }
impl<FnTyp: ApplyToObject + IsConstructor> BuiltInBuilder<'_, Callable<FnTyp>> { impl<FnTyp: ApplyToObject + IsConstructor> BuiltInBuilder<'_, Callable<FnTyp>> {
/// Build the builtin callable. /// Build the builtin callable.
fn build(mut self) -> JsFunction { fn build(self) -> JsFunction {
self.kind.apply_to(&mut self.object); self.kind.apply_to(&self.object);
self.object.set_prototype(self.prototype); self.object.set_prototype(Some(self.prototype));
JsFunction::from_object_unchecked(self.object.into_shared()) JsFunction::from_object_unchecked(self.object)
} }
} }

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

@ -15,10 +15,9 @@ use crate::{
error::JsNativeError, error::JsNativeError,
js_string, js_string,
object::{ object::{
internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, ObjectKind, internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, CONSTRUCTOR,
CONSTRUCTOR,
}, },
property::{Attribute, PropertyDescriptorBuilder}, property::Attribute,
realm::Realm, realm::Realm,
string::{common::StaticJsStrings, utf16, CodePoint}, string::{common::StaticJsStrings, utf16, CodePoint},
symbol::JsSymbol, symbol::JsSymbol,
@ -252,10 +251,11 @@ impl BuiltInConstructor for RegExp {
}; };
// 7. Let O be ? RegExpAlloc(newTarget). // 7. Let O be ? RegExpAlloc(newTarget).
let o = Self::alloc(new_target, context)?; let proto =
get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?;
// 8.Return ? RegExpInitialize(O, P, F). // 8.Return ? RegExpInitialize(O, P, F).
Self::initialize(o, &p, &f, context) Self::initialize(Some(proto), &p, &f, context)
} }
} }
@ -294,50 +294,16 @@ impl RegExp {
Ok(None) Ok(None)
} }
/// `22.2.3.2.1 RegExpAlloc ( newTarget )` /// Compiles a `RegExp` from the provided pattern and flags.
/// ///
/// More information: /// Equivalent to the beginning of [`RegExpInitialize ( obj, pattern, flags )`][spec]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexpalloc
pub(crate) fn alloc(new_target: &JsValue, context: &mut Context<'_>) -> JsResult<JsObject> {
// 1. Let obj be ? OrdinaryCreateFromConstructor(newTarget, "%RegExp.prototype%", « [[RegExpMatcher]], [[OriginalSource]], [[OriginalFlags]] »).
let proto =
get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?;
let obj = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
ObjectData::ordinary(),
);
// 2. Perform ! DefinePropertyOrThrow(obj, "lastIndex", PropertyDescriptor { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
obj.define_property_or_throw(
utf16!("lastIndex"),
PropertyDescriptorBuilder::new()
.writable(true)
.enumerable(false)
.configurable(false)
.build(),
context,
)
.expect("this cannot fail per spec");
// 3. Return obj.
Ok(obj)
}
/// `22.2.3.2.2 RegExpInitialize ( obj, pattern, flags )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize /// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize
pub(crate) fn initialize( fn compile_native_regexp(
obj: JsObject,
pattern: &JsValue, pattern: &JsValue,
flags: &JsValue, flags: &JsValue,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<JsValue> { ) -> JsResult<RegExp> {
// 1. If pattern is undefined, let P be the empty String. // 1. If pattern is undefined, let P be the empty String.
// 2. Else, let P be ? ToString(pattern). // 2. Else, let P be ? ToString(pattern).
let p = if pattern.is_undefined() { let p = if pattern.is_undefined() {
@ -362,43 +328,66 @@ impl RegExp {
Ok(result) => result, Ok(result) => result,
}; };
// 10. If u is true, then // 13. Let parseResult be ParsePattern(patternText, u, v).
// a. Let patternText be StringToCodePoints(P). // 14. If parseResult is a non-empty List of SyntaxError objects, throw a SyntaxError exception.
// 11. Else,
// a. Let patternText be the result of interpreting each of P's 16-bit elements as a Unicode BMP code point. UTF-16 decoding is not applied to the elements.
// 12. Let parseResult be ParsePattern(patternText, u).
// 13. If parseResult is a non-empty List of SyntaxError objects, throw a SyntaxError exception.
// 14. Assert: parseResult is a Pattern Parse Node.
// 15. Set obj.[[OriginalSource]] to P.
// 16. Set obj.[[OriginalFlags]] to F.
// 17. Let capturingGroupsCount be CountLeftCapturingParensWithin(parseResult).
// 18. Let rer be the RegExp Record { [[IgnoreCase]]: i, [[Multiline]]: m, [[DotAll]]: s, [[Unicode]]: u, [[CapturingGroupsCount]]: capturingGroupsCount }.
// 19. Set obj.[[RegExpRecord]] to rer.
// 20. Set obj.[[RegExpMatcher]] to CompilePattern of parseResult with argument rer.
let matcher = let matcher =
match Regex::from_unicode(p.code_points().map(CodePoint::as_u32), Flags::from(flags)) { Regex::from_unicode(p.code_points().map(CodePoint::as_u32), Flags::from(flags))
Err(error) => { .map_err(|error| {
return Err(JsNativeError::syntax() JsNativeError::syntax()
.with_message(format!("failed to create matcher: {}", error.text)) .with_message(format!("failed to create matcher: {}", error.text))
.into()); })?;
}
Ok(val) => val,
};
let regexp = Self { // 15. Assert: parseResult is a Pattern Parse Node.
// 16. Set obj.[[OriginalSource]] to P.
// 17. Set obj.[[OriginalFlags]] to F.
// 18. Let capturingGroupsCount be CountLeftCapturingParensWithin(parseResult).
// 19. Let rer be the RegExp Record { [[IgnoreCase]]: i, [[Multiline]]: m, [[DotAll]]: s, [[Unicode]]: u, [[UnicodeSets]]: v, [[CapturingGroupsCount]]: capturingGroupsCount }.
// 20. Set obj.[[RegExpRecord]] to rer.
// 21. Set obj.[[RegExpMatcher]] to CompilePattern of parseResult with argument rer.
Ok(RegExp {
matcher, matcher,
flags, flags,
original_source: p, original_source: p,
original_flags: f, original_flags: f,
}; })
}
// Safe to directly initialize since previous assertions ensure `obj` is a `Regexp` object.
*obj.borrow_mut().kind_mut() = ObjectKind::RegExp(Box::new(regexp));
// 16. Perform ? Set(obj, "lastIndex", +0𝔽, true). /// `RegExpInitialize ( obj, pattern, flags )`
obj.set(utf16!("lastIndex"), 0, true, context)?; ///
/// If prototype is `None`, initializes the prototype to `%RegExp%.prototype`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize
pub(crate) fn initialize(
prototype: Option<JsObject>,
pattern: &JsValue,
flags: &JsValue,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// Has the steps of `RegExpInitialize`.
let regexp = Self::compile_native_regexp(pattern, flags, context)?;
let data = ObjectData::regexp(regexp);
// 22. Perform ? Set(obj, "lastIndex", +0𝔽, true).
let obj = if let Some(prototype) = prototype {
let mut template = context
.intrinsics()
.templates()
.regexp_without_proto()
.clone();
template.set_prototype(prototype);
template.create(data, vec![0.into()])
} else {
context
.intrinsics()
.templates()
.regexp()
.create(data, vec![0.into()])
};
// 16. Return obj. // 23. Return obj.
Ok(obj.into()) Ok(obj.into())
} }
@ -410,10 +399,8 @@ impl RegExp {
/// [spec]: https://tc39.es/ecma262/#sec-regexpcreate /// [spec]: https://tc39.es/ecma262/#sec-regexpcreate
pub(crate) fn create(p: &JsValue, f: &JsValue, context: &mut Context<'_>) -> JsResult<JsValue> { pub(crate) fn create(p: &JsValue, f: &JsValue, context: &mut Context<'_>) -> JsResult<JsValue> {
// 1. Let obj be ? RegExpAlloc(%RegExp%). // 1. Let obj be ? RegExpAlloc(%RegExp%).
let obj = Self::alloc(&context.global_object().get(Self::NAME, context)?, context)?;
// 2. Return ? RegExpInitialize(obj, P, F). // 2. Return ? RegExpInitialize(obj, P, F).
Self::initialize(obj, p, f, context) Self::initialize(None, p, f, context)
} }
/// `get RegExp [ @@species ]` /// `get RegExp [ @@species ]`
@ -1859,6 +1846,7 @@ impl RegExp {
fn compile(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> { fn compile(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
// 1. Let O be the this value. // 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[RegExpMatcher]]). // 2. Perform ? RequireInternalSlot(O, [[RegExpMatcher]]).
let this = this let this = this
.as_object() .as_object()
.filter(|o| o.borrow().is_regexp()) .filter(|o| o.borrow().is_regexp())
@ -1893,8 +1881,20 @@ impl RegExp {
// b. Let F be flags. // b. Let F be flags.
(pattern.clone(), flags.clone()) (pattern.clone(), flags.clone())
}; };
let regexp = Self::compile_native_regexp(&pattern, &flags, context)?;
// 5. Return ? RegExpInitialize(O, P, F). // 5. Return ? RegExpInitialize(O, P, F).
Self::initialize(this, &pattern, &flags, context) {
let mut obj = this.borrow_mut();
*obj.as_regexp_mut()
.expect("already checked that the object was a RegExp") = regexp;
}
this.set(utf16!("lastIndex"), 0, true, context)?;
Ok(this.into())
} }
} }

30
boa_engine/src/context/intrinsics.rs

@ -1324,6 +1324,9 @@ pub(crate) struct ObjectTemplates {
bigint: ObjectTemplate, bigint: ObjectTemplate,
boolean: ObjectTemplate, boolean: ObjectTemplate,
regexp: ObjectTemplate,
regexp_without_proto: ObjectTemplate,
unmapped_arguments: ObjectTemplate, unmapped_arguments: ObjectTemplate,
mapped_arguments: ObjectTemplate, mapped_arguments: ObjectTemplate,
@ -1369,6 +1372,12 @@ impl ObjectTemplates {
); );
string.set_prototype(constructors.string().prototype()); string.set_prototype(constructors.string().prototype());
let mut regexp_without_prototype = ObjectTemplate::new(root_shape);
regexp_without_prototype.property(js_string!("lastIndex").into(), Attribute::WRITABLE);
let mut regexp = regexp_without_prototype.clone();
regexp.set_prototype(constructors.regexp().prototype());
let name_property_key: PropertyKey = js_string!("name").into(); let name_property_key: PropertyKey = js_string!("name").into();
let mut function = ObjectTemplate::new(root_shape); let mut function = ObjectTemplate::new(root_shape);
function.property( function.property(
@ -1474,6 +1483,8 @@ impl ObjectTemplates {
symbol, symbol,
bigint, bigint,
boolean, boolean,
regexp,
regexp_without_proto: regexp_without_prototype,
unmapped_arguments, unmapped_arguments,
mapped_arguments, mapped_arguments,
function_with_prototype, function_with_prototype,
@ -1564,6 +1575,25 @@ impl ObjectTemplates {
&self.boolean &self.boolean
} }
/// Cached regexp object template.
///
/// Transitions:
///
/// 1. `"lastIndex"`: (`WRITABLE` , `PERMANENT`,`NON_ENUMERABLE`)
pub(crate) const fn regexp(&self) -> &ObjectTemplate {
&self.regexp
}
/// Cached regexp object template without `__proto__` template.
///
/// Transitions:
///
/// 1. `"lastIndex"`: (`WRITABLE` , `PERMANENT`,`NON_ENUMERABLE`)
/// 2. `__proto__`: `RegExp.prototype`
pub(crate) const fn regexp_without_proto(&self) -> &ObjectTemplate {
&self.regexp_without_proto
}
/// Cached unmapped arguments object template. /// Cached unmapped arguments object template.
/// ///
/// Transitions: /// Transitions:

4
boa_engine/src/context/mod.rs

@ -75,7 +75,9 @@ use self::intrinsics::StandardConstructor;
/// let arg = ObjectInitializer::new(&mut context) /// let arg = ObjectInitializer::new(&mut context)
/// .property(js_string!("x"), 12, Attribute::READONLY) /// .property(js_string!("x"), 12, Attribute::READONLY)
/// .build(); /// .build();
/// context.register_global_property(js_string!("arg"), arg, Attribute::all()); /// context
/// .register_global_property(js_string!("arg"), arg, Attribute::all())
/// .expect("property shouldn't exist");
/// ///
/// let value = context.eval(Source::from_bytes("test(arg)")).unwrap(); /// let value = context.eval(Source::from_bytes("test(arg)")).unwrap();
/// ///

26
boa_engine/src/module/mod.rs

@ -47,7 +47,7 @@ use crate::{
builtins::promise::{PromiseCapability, PromiseState}, builtins::promise::{PromiseCapability, PromiseState},
environments::DeclarativeEnvironment, environments::DeclarativeEnvironment,
js_string, js_string,
object::{FunctionObjectBuilder, JsObject, JsPromise, ObjectData}, object::{JsObject, JsPromise, ObjectData},
realm::Realm, realm::Realm,
Context, HostDefined, JsError, JsResult, JsString, JsValue, NativeFunction, Context, HostDefined, JsError, JsResult, JsString, JsValue, NativeFunction,
}; };
@ -430,7 +430,7 @@ impl Module {
ModuleKind::Synthetic(synth) => { ModuleKind::Synthetic(synth) => {
// a. Let promise be ! module.Evaluate(). // a. Let promise be ! module.Evaluate().
let promise: JsPromise = synth.evaluate(context); let promise: JsPromise = synth.evaluate(context);
let state = promise.state()?; let state = promise.state();
match state { match state {
PromiseState::Pending => { PromiseState::Pending => {
unreachable!("b. Assert: promise.[[PromiseState]] is not pending.") unreachable!("b. Assert: promise.[[PromiseState]] is not pending.")
@ -467,59 +467,53 @@ impl Module {
/// ///
/// loader.insert(Path::new("main.mjs").to_path_buf(), module.clone()); /// loader.insert(Path::new("main.mjs").to_path_buf(), module.clone());
/// ///
/// let promise = module.load_link_evaluate(context).unwrap(); /// let promise = module.load_link_evaluate(context);
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
/// assert_eq!( /// assert_eq!(
/// promise.state().unwrap(), /// promise.state(),
/// PromiseState::Fulfilled(JsValue::undefined()) /// PromiseState::Fulfilled(JsValue::undefined())
/// ); /// );
/// ``` /// ```
#[allow(dropping_copy_types)] #[allow(dropping_copy_types)]
#[inline] #[inline]
pub fn load_link_evaluate(&self, context: &mut Context<'_>) -> JsResult<JsPromise> { pub fn load_link_evaluate(&self, context: &mut Context<'_>) -> JsPromise {
let main_timer = Profiler::global().start_event("Module evaluation", "Main"); let main_timer = Profiler::global().start_event("Module evaluation", "Main");
let promise = self let promise = self
.load(context) .load(context)
.then( .then(
Some( Some(
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures( NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| { |_, _, module, context| {
module.link(context)?; module.link(context)?;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
self.clone(), self.clone(),
),
) )
.build(), .to_js_function(context.realm()),
), ),
None, None,
context, context,
)? )
.then( .then(
Some( Some(
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures( NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| Ok(module.evaluate(context).into()), |_, _, module, context| Ok(module.evaluate(context).into()),
self.clone(), self.clone(),
),
) )
.build(), .to_js_function(context.realm()),
), ),
None, None,
context, context,
)?; );
// The main_timer needs to be dropped before the Profiler is. // The main_timer needs to be dropped before the Profiler is.
drop(main_timer); drop(main_timer);
Profiler::global().drop(); Profiler::global().drop();
Ok(promise) promise
} }
/// Abstract operation [`GetModuleNamespace ( module )`][spec]. /// Abstract operation [`GetModuleNamespace ( module )`][spec].

1
boa_engine/src/module/synthetic.rs

@ -198,7 +198,6 @@ impl SyntheticModule {
pub(super) fn load(context: &mut Context<'_>) -> JsPromise { pub(super) fn load(context: &mut Context<'_>) -> JsPromise {
// 1. Return ! PromiseResolve(%Promise%, undefined). // 1. Return ! PromiseResolve(%Promise%, undefined).
JsPromise::resolve(JsValue::undefined(), context) JsPromise::resolve(JsValue::undefined(), context)
.expect("creating a promise from the %Promise% constructor must not fail")
} }
/// Concrete method [`GetExportedNames ( [ exportStarSet ] )`][spec]. /// Concrete method [`GetExportedNames ( [ exportStarSet ] )`][spec].

34
boa_engine/src/native_function.rs

@ -5,7 +5,12 @@
use boa_gc::{custom_trace, Finalize, Gc, Trace}; use boa_gc::{custom_trace, Finalize, Gc, Trace};
use crate::{object::JsPromise, Context, JsResult, JsValue}; use crate::{
builtins::function::ConstructorKind,
object::{FunctionObjectBuilder, JsFunction, JsPromise},
realm::Realm,
Context, JsResult, JsValue,
};
/// The required signature for all native built-in function pointers. /// The required signature for all native built-in function pointers.
/// ///
@ -56,6 +61,25 @@ where
} }
} }
#[derive(Clone, Debug, Finalize)]
/// The data of an object containing a `NativeFunction`.
pub struct NativeFunctionObject {
/// The rust function.
pub(crate) f: NativeFunction,
/// The kind of the function constructor if it is a constructor.
pub(crate) constructor: Option<ConstructorKind>,
/// The [`Realm`] in which the function is defined, or `None` if the realm is uninitialized.
pub(crate) realm: Option<Realm>,
}
// SAFETY: this traces all fields that need to be traced by the GC.
unsafe impl Trace for NativeFunctionObject {
custom_trace!(this, {
mark(&this.f);
mark(&this.realm);
});
}
/// A callable Rust function that can be invoked by the engine. /// A callable Rust function that can be invoked by the engine.
/// ///
/// `NativeFunction` functions are divided in two: /// `NativeFunction` functions are divided in two:
@ -280,4 +304,12 @@ impl NativeFunction {
Inner::Closure(ref c) => c.call(this, args, context), Inner::Closure(ref c) => c.call(this, args, context),
} }
} }
/// Converts this `NativeFunction` into a `JsFunction` without setting its name or length.
///
/// Useful to create functions that will only be used once, such as callbacks.
#[must_use]
pub fn to_js_function(self, realm: &Realm) -> JsFunction {
FunctionObjectBuilder::new(realm, self).build()
}
} }

12
boa_engine/src/object/builtins/jsfunction.rs

@ -1,13 +1,15 @@
//! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object //! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object
use crate::{ use crate::{
builtins::function::ConstructorKind,
native_function::NativeFunctionObject,
object::{ object::{
internal_methods::function::{ internal_methods::function::{
NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS, NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS,
}, },
JsObject, JsObjectType, Object, JsObject, JsObjectType, Object, ObjectKind,
}, },
value::TryFromJs, value::TryFromJs,
Context, JsNativeError, JsResult, JsValue, Context, JsNativeError, JsResult, JsValue, NativeFunction,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
use std::ops::Deref; use std::ops::Deref;
@ -32,7 +34,11 @@ impl JsFunction {
pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self { pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self {
Self { Self {
inner: JsObject::from_object_and_vtable( inner: JsObject::from_object_and_vtable(
Object::default(), Object::with_kind(ObjectKind::NativeFunction(NativeFunctionObject {
f: NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())),
constructor: constructor.then_some(ConstructorKind::Base),
realm: None,
})),
if constructor { if constructor {
&NATIVE_CONSTRUCTOR_INTERNAL_METHODS &NATIVE_CONSTRUCTOR_INTERNAL_METHODS
} else { } else {

305
boa_engine/src/object/builtins/jspromise.rs

@ -8,9 +8,8 @@ use crate::{
promise::{PromiseState, ResolvingFunctions}, promise::{PromiseState, ResolvingFunctions},
Promise, Promise,
}, },
context::intrinsics::StandardConstructors,
job::NativeJob, job::NativeJob,
object::{FunctionObjectBuilder, JsObject, JsObjectType, ObjectData}, object::{JsObject, JsObjectType, ObjectData},
value::TryFromJs, value::TryFromJs,
Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction, Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction,
}; };
@ -52,38 +51,28 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
/// Ok(JsValue::undefined()) /// Ok(JsValue::undefined())
/// }, /// },
/// context, /// context,
/// )?; /// );
/// ///
/// let promise = promise /// let promise = promise
/// .then( /// .then(
/// Some( /// Some(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, _| { /// NativeFunction::from_fn_ptr(|_, args, _| {
/// Err(JsError::from_opaque( /// Err(JsError::from_opaque(args.get_or_undefined(0).clone())
/// args.get_or_undefined(0).clone(),
/// )
/// .into()) /// .into())
/// }), /// })
/// ) /// .to_js_function(context.realm()),
/// .build(),
/// ), /// ),
/// None, /// None,
/// context, /// context,
/// )? /// )
/// .catch( /// .catch(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, _| { /// NativeFunction::from_fn_ptr(|_, args, _| {
/// Ok(args.get_or_undefined(0).clone()) /// Ok(args.get_or_undefined(0).clone())
/// }), /// })
/// ) /// .to_js_function(context.realm()),
/// .build(),
/// context, /// context,
/// )? /// )
/// .finally( /// .finally(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, _, context| { /// NativeFunction::from_fn_ptr(|_, _, context| {
/// context.global_object().clone().set( /// context.global_object().clone().set(
/// js_string!("finally"), /// js_string!("finally"),
@ -92,16 +81,15 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
/// context, /// context,
/// )?; /// )?;
/// Ok(JsValue::undefined()) /// Ok(JsValue::undefined())
/// }), /// })
/// ) /// .to_js_function(context.realm()),
/// .build(),
/// context, /// context,
/// )?; /// );
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
/// assert_eq!( /// assert_eq!(
/// promise.state()?, /// promise.state(),
/// PromiseState::Fulfilled(js_string!("hello world!").into()) /// PromiseState::Fulfilled(js_string!("hello world!").into())
/// ); /// );
/// ///
@ -160,12 +148,12 @@ impl JsPromise {
/// Ok(JsValue::undefined()) /// Ok(JsValue::undefined())
/// }, /// },
/// context, /// context,
/// )?; /// );
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
/// assert_eq!( /// assert_eq!(
/// promise.state()?, /// promise.state(),
/// PromiseState::Fulfilled(js_string!("hello world").into()) /// PromiseState::Fulfilled(js_string!("hello world").into())
/// ); /// );
/// # Ok(()) /// # Ok(())
@ -173,7 +161,7 @@ impl JsPromise {
/// ``` /// ```
/// ///
/// [`Promise()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise /// [`Promise()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
pub fn new<F>(executor: F, context: &mut Context<'_>) -> JsResult<Self> pub fn new<F>(executor: F, context: &mut Context<'_>) -> Self
where where
F: FnOnce(&ResolvingFunctions, &mut Context<'_>) -> JsResult<JsValue>, F: FnOnce(&ResolvingFunctions, &mut Context<'_>) -> JsResult<JsValue>,
{ {
@ -188,10 +176,11 @@ impl JsPromise {
let e = e.to_opaque(context); let e = e.to_opaque(context);
resolvers resolvers
.reject .reject
.call(&JsValue::undefined(), &[e], context)?; .call(&JsValue::undefined(), &[e], context)
.expect("default `reject` function cannot throw");
} }
Ok(Self { inner: promise }) Self { inner: promise }
} }
/// Creates a new pending promise and returns it and its associated `ResolvingFunctions`. /// Creates a new pending promise and returns it and its associated `ResolvingFunctions`.
@ -214,13 +203,13 @@ impl JsPromise {
/// ///
/// let (promise, resolvers) = JsPromise::new_pending(context); /// let (promise, resolvers) = JsPromise::new_pending(context);
/// ///
/// assert_eq!(promise.state()?, PromiseState::Pending); /// assert_eq!(promise.state(), PromiseState::Pending);
/// ///
/// resolvers /// resolvers
/// .reject /// .reject
/// .call(&JsValue::undefined(), &[5.into()], context)?; /// .call(&JsValue::undefined(), &[5.into()], context)?;
/// ///
/// assert_eq!(promise.state()?, PromiseState::Rejected(5.into())); /// assert_eq!(promise.state(), PromiseState::Rejected(5.into()));
/// ///
/// # Ok(()) /// # Ok(())
/// # } /// # }
@ -262,7 +251,7 @@ impl JsPromise {
/// let promise = JsPromise::from_object(promise)?; /// let promise = JsPromise::from_object(promise)?;
/// ///
/// assert_eq!( /// assert_eq!(
/// promise.state()?, /// promise.state(),
/// PromiseState::Fulfilled(JsValue::undefined()) /// PromiseState::Fulfilled(JsValue::undefined())
/// ); /// );
/// ///
@ -295,7 +284,6 @@ impl JsPromise {
/// # builtins::promise::PromiseState, /// # builtins::promise::PromiseState,
/// # Context, JsResult, JsValue /// # Context, JsResult, JsValue
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// async fn f() -> JsResult<JsValue> { /// async fn f() -> JsResult<JsValue> {
/// Ok(JsValue::null()) /// Ok(JsValue::null())
/// } /// }
@ -305,9 +293,7 @@ impl JsPromise {
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
/// assert_eq!(promise.state()?, PromiseState::Fulfilled(JsValue::null())); /// assert_eq!(promise.state(), PromiseState::Fulfilled(JsValue::null()));
/// # Ok(())
/// # }
/// ``` /// ```
/// ///
/// [async_fn]: crate::native_function::NativeFunction::from_async_fn /// [async_fn]: crate::native_function::NativeFunction::from_async_fn
@ -353,29 +339,26 @@ impl JsPromise {
/// # builtins::promise::PromiseState, /// # builtins::promise::PromiseState,
/// # Context, js_string /// # Context, js_string
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default(); /// let context = &mut Context::default();
/// ///
/// let promise = JsPromise::resolve(js_string!("resolved!"), context)?; /// let promise = JsPromise::resolve(js_string!("resolved!"), context);
/// ///
/// assert_eq!( /// assert_eq!(
/// promise.state()?, /// promise.state(),
/// PromiseState::Fulfilled(js_string!("resolved!").into()) /// PromiseState::Fulfilled(js_string!("resolved!").into())
/// ); /// );
///
/// # Ok(())
/// # }
/// ``` /// ```
/// ///
/// [`Promise.resolve()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve /// [`Promise.resolve()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
/// [thenables]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables /// [thenables]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables
pub fn resolve<V: Into<JsValue>>(value: V, context: &mut Context<'_>) -> JsResult<Self> { pub fn resolve<V: Into<JsValue>>(value: V, context: &mut Context<'_>) -> Self {
Promise::promise_resolve( Promise::promise_resolve(
&context.intrinsics().constructors().promise().constructor(), &context.intrinsics().constructors().promise().constructor(),
value.into(), value.into(),
context, context,
) )
.and_then(Self::from_object) .and_then(Self::from_object)
.expect("default resolving functions cannot throw and must return a promise")
} }
/// Creates a `JsPromise` that is rejected with the reason `error`. /// Creates a `JsPromise` that is rejected with the reason `error`.
@ -394,32 +377,29 @@ impl JsPromise {
/// # builtins::promise::PromiseState, /// # builtins::promise::PromiseState,
/// # Context, js_string, JsError /// # Context, js_string, JsError
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default(); /// let context = &mut Context::default();
/// ///
/// let promise = JsPromise::reject( /// let promise = JsPromise::reject(
/// JsError::from_opaque(js_string!("oops!").into()), /// JsError::from_opaque(js_string!("oops!").into()),
/// context, /// context,
/// )?; /// );
/// ///
/// assert_eq!( /// assert_eq!(
/// promise.state()?, /// promise.state(),
/// PromiseState::Rejected(js_string!("oops!").into()) /// PromiseState::Rejected(js_string!("oops!").into())
/// ); /// );
///
/// # Ok(())
/// # }
/// ``` /// ```
/// ///
/// [`Promise.reject`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject /// [`Promise.reject`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
/// [thenable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables /// [thenable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables
pub fn reject<E: Into<JsError>>(error: E, context: &mut Context<'_>) -> JsResult<Self> { pub fn reject<E: Into<JsError>>(error: E, context: &mut Context<'_>) -> Self {
Promise::promise_reject( Promise::promise_reject(
&context.intrinsics().constructors().promise().constructor(), &context.intrinsics().constructors().promise().constructor(),
&error.into(), &error.into(),
context, context,
) )
.and_then(Self::from_object) .and_then(Self::from_object)
.expect("default resolving functions cannot throw and must return a promise")
} }
/// Gets the current state of the promise. /// Gets the current state of the promise.
@ -433,26 +413,21 @@ impl JsPromise {
/// # builtins::promise::PromiseState, /// # builtins::promise::PromiseState,
/// # Context /// # Context
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default(); /// let context = &mut Context::default();
/// ///
/// let promise = JsPromise::new_pending(context).0; /// let promise = JsPromise::new_pending(context).0;
/// ///
/// assert_eq!(promise.state()?, PromiseState::Pending); /// assert_eq!(promise.state(), PromiseState::Pending);
///
/// # Ok(())
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn state(&self) -> JsResult<PromiseState> { #[must_use]
// TODO: if we can guarantee that objects cannot change type after creation, pub fn state(&self) -> PromiseState {
// we can remove this throw. self.inner
let promise = self.inner.borrow(); .borrow()
let promise = promise
.as_promise() .as_promise()
.ok_or_else(|| JsNativeError::typ().with_message("object is not a Promise"))?; .expect("objects cannot change type after creation")
.state()
Ok(promise.state().clone()) .clone()
} }
/// Schedules callback functions to run when the promise settles. /// Schedules callback functions to run when the promise settles.
@ -489,7 +464,6 @@ impl JsPromise {
/// # object::{builtins::JsPromise, FunctionObjectBuilder}, /// # object::{builtins::JsPromise, FunctionObjectBuilder},
/// # Context, JsArgs, JsError, JsValue, NativeFunction, /// # Context, JsArgs, JsError, JsValue, NativeFunction,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default(); /// let context = &mut Context::default();
/// ///
/// let promise = JsPromise::new( /// let promise = JsPromise::new(
@ -502,44 +476,40 @@ impl JsPromise {
/// Ok(JsValue::undefined()) /// Ok(JsValue::undefined())
/// }, /// },
/// context, /// context,
/// )? /// )
/// .then( /// .then(
/// Some( /// Some(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, context| { /// NativeFunction::from_fn_ptr(|_, args, context| {
/// args.get_or_undefined(0) /// args.get_or_undefined(0)
/// .to_string(context) /// .to_string(context)
/// .map(JsValue::from) /// .map(JsValue::from)
/// }), /// })
/// ) /// .to_js_function(context.realm()),
/// .build(),
/// ), /// ),
/// None, /// None,
/// context, /// context,
/// )?; /// );
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
/// assert_eq!( /// assert_eq!(
/// promise.state()?, /// promise.state(),
/// PromiseState::Fulfilled(js_string!("255.255").into()) /// PromiseState::Fulfilled(js_string!("255.255").into())
/// ); /// );
///
/// # Ok(())
/// # }
/// ``` /// ```
/// ///
/// [`Promise.prototype.then`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then /// [`Promise.prototype.then`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
#[inline] #[inline]
#[allow(clippy::return_self_not_must_use)] // Could just be used to add handlers on an existing promise
pub fn then( pub fn then(
&self, &self,
on_fulfilled: Option<JsFunction>, on_fulfilled: Option<JsFunction>,
on_rejected: Option<JsFunction>, on_rejected: Option<JsFunction>,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<Self> { ) -> Self {
let result_promise = Promise::inner_then(self, on_fulfilled, on_rejected, context)?; Promise::inner_then(self, on_fulfilled, on_rejected, context)
Self::from_object(result_promise) .and_then(Self::from_object)
.expect("`inner_then` cannot fail for native `JsPromise`")
} }
/// Schedules a callback to run when the promise is rejected. /// Schedules a callback to run when the promise is rejected.
@ -559,7 +529,6 @@ impl JsPromise {
/// # object::{builtins::JsPromise, FunctionObjectBuilder}, /// # object::{builtins::JsPromise, FunctionObjectBuilder},
/// # Context, JsArgs, JsNativeError, JsValue, NativeFunction, /// # Context, JsArgs, JsNativeError, JsValue, NativeFunction,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default(); /// let context = &mut Context::default();
/// ///
/// let promise = JsPromise::new( /// let promise = JsPromise::new(
@ -574,35 +543,30 @@ impl JsPromise {
/// Ok(JsValue::undefined()) /// Ok(JsValue::undefined())
/// }, /// },
/// context, /// context,
/// )? /// )
/// .catch( /// .catch(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, context| { /// NativeFunction::from_fn_ptr(|_, args, context| {
/// args.get_or_undefined(0) /// args.get_or_undefined(0)
/// .to_string(context) /// .to_string(context)
/// .map(JsValue::from) /// .map(JsValue::from)
/// }), /// })
/// ) /// .to_js_function(context.realm()),
/// .build(),
/// context, /// context,
/// )?; /// );
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
/// assert_eq!( /// assert_eq!(
/// promise.state()?, /// promise.state(),
/// PromiseState::Fulfilled(js_string!("TypeError: thrown").into()) /// PromiseState::Fulfilled(js_string!("TypeError: thrown").into())
/// ); /// );
///
/// # Ok(())
/// # }
/// ``` /// ```
/// ///
/// [`Promise.prototype.catch`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch /// [`Promise.prototype.catch`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
/// [then]: JsPromise::then /// [then]: JsPromise::then
#[inline] #[inline]
pub fn catch(&self, on_rejected: JsFunction, context: &mut Context<'_>) -> JsResult<Self> { #[allow(clippy::return_self_not_must_use)] // Could just be used to add a handler on an existing promise
pub fn catch(&self, on_rejected: JsFunction, context: &mut Context<'_>) -> Self {
self.then(None, Some(on_rejected), context) self.then(None, Some(on_rejected), context)
} }
@ -633,7 +597,7 @@ impl JsPromise {
/// js_string!("finally"), /// js_string!("finally"),
/// false, /// false,
/// Attribute::all(), /// Attribute::all(),
/// ); /// )?;
/// ///
/// let promise = JsPromise::new( /// let promise = JsPromise::new(
/// |resolvers, context| { /// |resolvers, context| {
@ -647,10 +611,8 @@ impl JsPromise {
/// Ok(JsValue::undefined()) /// Ok(JsValue::undefined())
/// }, /// },
/// context, /// context,
/// )? /// )
/// .finally( /// .finally(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, _, context| { /// NativeFunction::from_fn_ptr(|_, _, context| {
/// context.global_object().clone().set( /// context.global_object().clone().set(
/// js_string!("finally"), /// js_string!("finally"),
@ -659,11 +621,10 @@ impl JsPromise {
/// context, /// context,
/// )?; /// )?;
/// Ok(JsValue::undefined()) /// Ok(JsValue::undefined())
/// }), /// })
/// ) /// .to_js_function(context.realm()),
/// .build(),
/// context, /// context,
/// )?; /// );
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
@ -682,10 +643,16 @@ impl JsPromise {
/// [`Promise.prototype.finally()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally /// [`Promise.prototype.finally()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
/// [then]: JsPromise::then /// [then]: JsPromise::then
#[inline] #[inline]
pub fn finally(&self, on_finally: JsFunction, context: &mut Context<'_>) -> JsResult<Self> { #[allow(clippy::return_self_not_must_use)] // Could just be used to add a handler on an existing promise
let c = self.species_constructor(StandardConstructors::promise, context)?; pub fn finally(&self, on_finally: JsFunction, context: &mut Context<'_>) -> Self {
let (then, catch) = Promise::then_catch_finally_closures(c, on_finally, context); let (then, catch) = Promise::then_catch_finally_closures(
self.then(Some(then), Some(catch), context) context.intrinsics().constructors().promise().constructor(),
on_finally,
context,
);
Promise::inner_then(self, Some(then), Some(catch), context)
.and_then(Self::from_object)
.expect("`inner_then` cannot fail for native `JsPromise`")
} }
/// Waits for a list of promises to settle with fulfilled values, rejecting the aggregate promise /// Waits for a list of promises to settle with fulfilled values, rejecting the aggregate promise
@ -707,26 +674,26 @@ impl JsPromise {
/// ///
/// let promise1 = JsPromise::all( /// let promise1 = JsPromise::all(
/// [ /// [
/// JsPromise::resolve(0, context)?, /// JsPromise::resolve(0, context),
/// JsPromise::resolve(2, context)?, /// JsPromise::resolve(2, context),
/// JsPromise::resolve(4, context)?, /// JsPromise::resolve(4, context),
/// ], /// ],
/// context, /// context,
/// )?; /// );
/// ///
/// let promise2 = JsPromise::all( /// let promise2 = JsPromise::all(
/// [ /// [
/// JsPromise::resolve(1, context)?, /// JsPromise::resolve(1, context),
/// JsPromise::reject(JsNativeError::typ(), context)?, /// JsPromise::reject(JsNativeError::typ(), context),
/// JsPromise::resolve(3, context)?, /// JsPromise::resolve(3, context),
/// ], /// ],
/// context, /// context,
/// )?; /// );
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
/// let array = promise1 /// let array = promise1
/// .state()? /// .state()
/// .as_fulfilled() /// .as_fulfilled()
/// .and_then(JsValue::as_object) /// .and_then(JsValue::as_object)
/// .unwrap() /// .unwrap()
@ -736,7 +703,7 @@ impl JsPromise {
/// assert_eq!(array.at(1, context)?, 2.into()); /// assert_eq!(array.at(1, context)?, 2.into());
/// assert_eq!(array.at(2, context)?, 4.into()); /// assert_eq!(array.at(2, context)?, 4.into());
/// ///
/// let error = promise2.state()?.as_rejected().unwrap().clone(); /// let error = promise2.state().as_rejected().unwrap().clone();
/// assert_eq!(error.to_string(context)?, js_string!("TypeError")); /// assert_eq!(error.to_string(context)?, js_string!("TypeError"));
/// ///
/// # Ok(()) /// # Ok(())
@ -744,7 +711,7 @@ impl JsPromise {
/// ``` /// ```
/// ///
/// [`Promise.all`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all /// [`Promise.all`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
pub fn all<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self> pub fn all<I>(promises: I, context: &mut Context<'_>) -> Self
where where
I: IntoIterator<Item = Self>, I: IntoIterator<Item = Self>,
{ {
@ -757,12 +724,15 @@ impl JsPromise {
.constructor() .constructor()
.into(); .into();
let value = Promise::all(c, &[promises.into()], context)?; let value = Promise::all(c, &[promises.into()], context)
let value = value .expect("Promise.all cannot fail with the default `%Promise%` constructor");
let object = value
.as_object() .as_object()
.expect("Promise.all always returns an object on success"); .expect("`Promise.all` always returns an object on success");
Self::from_object(value.clone()) Self::from_object(object.clone())
.expect("`Promise::all` with the default `%Promise%` constructor always returns a native `JsPromise`")
} }
/// Waits for a list of promises to settle, fulfilling with an array of the outcomes of every /// Waits for a list of promises to settle, fulfilling with an array of the outcomes of every
@ -784,17 +754,17 @@ impl JsPromise {
/// ///
/// let promise = JsPromise::all_settled( /// let promise = JsPromise::all_settled(
/// [ /// [
/// JsPromise::resolve(1, context)?, /// JsPromise::resolve(1, context),
/// JsPromise::reject(JsNativeError::typ(), context)?, /// JsPromise::reject(JsNativeError::typ(), context),
/// JsPromise::resolve(3, context)?, /// JsPromise::resolve(3, context),
/// ], /// ],
/// context, /// context,
/// )?; /// );
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
/// let array = promise /// let array = promise
/// .state()? /// .state()
/// .as_fulfilled() /// .as_fulfilled()
/// .and_then(JsValue::as_object) /// .and_then(JsValue::as_object)
/// .unwrap() /// .unwrap()
@ -830,7 +800,7 @@ impl JsPromise {
/// ``` /// ```
/// ///
/// [`Promise.allSettled`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled /// [`Promise.allSettled`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
pub fn all_settled<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self> pub fn all_settled<I>(promises: I, context: &mut Context<'_>) -> Self
where where
I: IntoIterator<Item = Self>, I: IntoIterator<Item = Self>,
{ {
@ -843,12 +813,15 @@ impl JsPromise {
.constructor() .constructor()
.into(); .into();
let value = Promise::all_settled(c, &[promises.into()], context)?; let value = Promise::all_settled(c, &[promises.into()], context)
let value = value .expect("`Promise.all_settled` cannot fail with the default `%Promise%` constructor");
let object = value
.as_object() .as_object()
.expect("Promise.allSettled always returns an object on success"); .expect("`Promise.all_settled` always returns an object on success");
Self::from_object(value.clone()) Self::from_object(object.clone())
.expect("`Promise::all_settled` with the default `%Promise%` constructor always returns a native `JsPromise`")
} }
/// Returns the first promise that fulfills from a list of promises. /// Returns the first promise that fulfills from a list of promises.
@ -869,32 +842,28 @@ impl JsPromise {
/// # object::builtins::JsPromise, /// # object::builtins::JsPromise,
/// # Context, JsNativeError, /// # Context, JsNativeError,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default(); /// let context = &mut Context::default();
/// ///
/// let promise = JsPromise::any( /// let promise = JsPromise::any(
/// [ /// [
/// JsPromise::reject(JsNativeError::syntax(), context)?, /// JsPromise::reject(JsNativeError::syntax(), context),
/// JsPromise::reject(JsNativeError::typ(), context)?, /// JsPromise::reject(JsNativeError::typ(), context),
/// JsPromise::resolve(js_string!("fulfilled"), context)?, /// JsPromise::resolve(js_string!("fulfilled"), context),
/// JsPromise::reject(JsNativeError::range(), context)?, /// JsPromise::reject(JsNativeError::range(), context),
/// ], /// ],
/// context, /// context,
/// )?; /// );
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
/// assert_eq!( /// assert_eq!(
/// promise.state()?, /// promise.state(),
/// PromiseState::Fulfilled(js_string!("fulfilled").into()) /// PromiseState::Fulfilled(js_string!("fulfilled").into())
/// ); /// );
///
/// # Ok(())
/// # }
/// ``` /// ```
/// ///
/// [`Promise.any`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any /// [`Promise.any`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
pub fn any<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self> pub fn any<I>(promises: I, context: &mut Context<'_>) -> Self
where where
I: IntoIterator<Item = Self>, I: IntoIterator<Item = Self>,
{ {
@ -907,12 +876,15 @@ impl JsPromise {
.constructor() .constructor()
.into(); .into();
let value = Promise::any(c, &[promises.into()], context)?; let value = Promise::any(c, &[promises.into()], context)
let value = value .expect("`Promise.any` cannot fail with the default `%Promise%` constructor");
let object = value
.as_object() .as_object()
.expect("Promise.any always returns an object on success"); .expect("`Promise.any` always returns an object on success");
Self::from_object(value.clone()) Self::from_object(object.clone())
.expect("`Promise::any` with the default `%Promise%` constructor always returns a native `JsPromise`")
} }
/// Returns the first promise that settles from a list of promises. /// Returns the first promise that settles from a list of promises.
@ -939,22 +911,24 @@ impl JsPromise {
/// let (b, resolvers_b) = JsPromise::new_pending(context); /// let (b, resolvers_b) = JsPromise::new_pending(context);
/// let (c, resolvers_c) = JsPromise::new_pending(context); /// let (c, resolvers_c) = JsPromise::new_pending(context);
/// ///
/// let promise = JsPromise::race([a, b, c], context)?; /// let promise = JsPromise::race([a, b, c], context);
/// ///
/// resolvers_b.reject.call(&JsValue::undefined(), &[], context); /// resolvers_b
/// .reject
/// .call(&JsValue::undefined(), &[], context)?;
/// resolvers_a /// resolvers_a
/// .resolve /// .resolve
/// .call(&JsValue::undefined(), &[5.into()], context); /// .call(&JsValue::undefined(), &[5.into()], context)?;
/// resolvers_c.reject.call( /// resolvers_c.reject.call(
/// &JsValue::undefined(), /// &JsValue::undefined(),
/// &[js_string!("c error").into()], /// &[js_string!("c error").into()],
/// context, /// context,
/// ); /// )?;
/// ///
/// context.run_jobs(); /// context.run_jobs();
/// ///
/// assert_eq!( /// assert_eq!(
/// promise.state()?, /// promise.state(),
/// PromiseState::Rejected(JsValue::undefined()) /// PromiseState::Rejected(JsValue::undefined())
/// ); /// );
/// ///
@ -963,7 +937,7 @@ impl JsPromise {
/// ``` /// ```
/// ///
/// [`Promise.race`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race /// [`Promise.race`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
pub fn race<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self> pub fn race<I>(promises: I, context: &mut Context<'_>) -> Self
where where
I: IntoIterator<Item = Self>, I: IntoIterator<Item = Self>,
{ {
@ -976,12 +950,15 @@ impl JsPromise {
.constructor() .constructor()
.into(); .into();
let value = Promise::race(c, &[promises.into()], context)?; let value = Promise::race(c, &[promises.into()], context)
let value = value .expect("`Promise.race` cannot fail with the default `%Promise%` constructor");
let object = value
.as_object() .as_object()
.expect("Promise.race always returns an object on success"); .expect("`Promise.race` always returns an object on success");
Self::from_object(value.clone()) Self::from_object(object.clone())
.expect("`Promise::race` with the default `%Promise%` constructor always returns a native `JsPromise`")
} }
/// Creates a `JsFuture` from this `JsPromise`. /// Creates a `JsFuture` from this `JsPromise`.
@ -1003,7 +980,7 @@ impl JsPromise {
/// let context = &mut Context::default(); /// let context = &mut Context::default();
/// ///
/// let (promise, resolvers) = JsPromise::new_pending(context); /// let (promise, resolvers) = JsPromise::new_pending(context);
/// let promise_future = promise.into_js_future(context)?; /// let promise_future = promise.into_js_future(context);
/// ///
/// let future1 = async move { promise_future.await }; /// let future1 = async move { promise_future.await };
/// ///
@ -1023,7 +1000,7 @@ impl JsPromise {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn into_js_future(self, context: &mut Context<'_>) -> JsResult<JsFuture> { pub fn into_js_future(self, context: &mut Context<'_>) -> JsFuture {
// Mostly based from: // Mostly based from:
// https://docs.rs/wasm-bindgen-futures/0.4.37/src/wasm_bindgen_futures/lib.rs.html#109-168 // https://docs.rs/wasm-bindgen-futures/0.4.37/src/wasm_bindgen_futures/lib.rs.html#109-168
@ -1057,24 +1034,18 @@ impl JsPromise {
let resolve = { let resolve = {
let state = state.clone(); let state = state.clone();
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures( NativeFunction::from_copy_closure_with_captures(
move |_, args, state, _| { move |_, args, state, _| {
finish(state, Ok(args.get_or_undefined(0).clone())); finish(state, Ok(args.get_or_undefined(0).clone()));
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
state, state,
),
) )
.build()
}; };
let reject = { let reject = {
let state = state.clone(); let state = state.clone();
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures( NativeFunction::from_copy_closure_with_captures(
move |_, args, state, _| { move |_, args, state, _| {
let err = JsError::from_opaque(args.get_or_undefined(0).clone()); let err = JsError::from_opaque(args.get_or_undefined(0).clone());
@ -1082,14 +1053,16 @@ impl JsPromise {
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
state, state,
),
) )
.build()
}; };
drop(self.then(Some(resolve), Some(reject), context)?); drop(self.then(
Some(resolve.to_js_function(context.realm())),
Some(reject.to_js_function(context.realm())),
context,
));
Ok(JsFuture { inner: state }) JsFuture { inner: state }
} }
} }

10
boa_engine/src/object/builtins/jsregexp.rs

@ -60,15 +60,7 @@ impl JsRegExp {
where where
S: Into<JsValue>, S: Into<JsValue>,
{ {
let constructor = &context let regexp = RegExp::initialize(None, &pattern.into(), &flags.into(), context)?
.intrinsics()
.constructors()
.regexp()
.constructor()
.into();
let obj = RegExp::alloc(constructor, context)?;
let regexp = RegExp::initialize(obj, &pattern.into(), &flags.into(), context)?
.as_object() .as_object()
.expect("RegExp::initialize must return a RegExp object") .expect("RegExp::initialize must return a RegExp object")
.clone(); .clone();

41
boa_engine/src/object/internal_methods/function.rs

@ -2,9 +2,10 @@ use crate::{
builtins::function::{arguments::Arguments, FunctionKind, ThisMode}, builtins::function::{arguments::Arguments, FunctionKind, ThisMode},
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
environments::{FunctionSlots, ThisBindingStatus}, environments::{FunctionSlots, ThisBindingStatus},
native_function::NativeFunctionObject,
object::{ object::{
internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
JsObject, ObjectData, ObjectKind, JsObject, ObjectData,
}, },
vm::{CallFrame, CallFrameFlags}, vm::{CallFrame, CallFrameFlags},
Context, JsNativeError, JsResult, JsValue, Context, JsNativeError, JsResult, JsValue,
@ -336,21 +337,18 @@ pub(crate) fn native_function_call(
// vm, but we'll eventually have to combine the native stack with the vm stack. // vm, but we'll eventually have to combine the native stack with the vm stack.
context.check_runtime_limits()?; context.check_runtime_limits()?;
let this_function_object = obj.clone(); let this_function_object = obj.clone();
let object = obj.borrow();
let ObjectKind::NativeFunction { let NativeFunctionObject {
function, f: function,
constructor, constructor,
realm, realm,
} = object.kind() } = obj
else { .borrow()
unreachable!("the object should be a native function object"); .as_native_function()
}; .cloned()
.expect("the object should be a native function object");
let mut realm = realm.clone(); let mut realm = realm.unwrap_or_else(|| context.realm().clone());
let function = function.clone();
let constructor = *constructor;
drop(object);
context.swap_realm(&mut realm); context.swap_realm(&mut realm);
context.vm.native_active_function = Some(this_function_object); context.vm.native_active_function = Some(this_function_object);
@ -385,21 +383,18 @@ fn native_function_construct(
// vm, but we'll eventually have to combine the native stack with the vm stack. // vm, but we'll eventually have to combine the native stack with the vm stack.
context.check_runtime_limits()?; context.check_runtime_limits()?;
let this_function_object = obj.clone(); let this_function_object = obj.clone();
let object = obj.borrow();
let ObjectKind::NativeFunction { let NativeFunctionObject {
function, f: function,
constructor, constructor,
realm, realm,
} = object.kind() } = obj
else { .borrow()
unreachable!("the object should be a native function object"); .as_native_function()
}; .cloned()
.expect("the object should be a native function object");
let mut realm = realm.clone(); let mut realm = realm.unwrap_or_else(|| context.realm().clone());
let function = function.clone();
let constructor = *constructor;
drop(object);
context.swap_realm(&mut realm); context.swap_realm(&mut realm);
context.vm.native_active_function = Some(this_function_object); context.vm.native_active_function = Some(this_function_object);

102
boa_engine/src/object/mod.rs

@ -67,7 +67,7 @@ use crate::{
context::intrinsics::StandardConstructor, context::intrinsics::StandardConstructor,
js_string, js_string,
module::ModuleNamespace, module::ModuleNamespace,
native_function::NativeFunction, native_function::{NativeFunction, NativeFunctionObject},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm, realm::Realm,
string::utf16, string::utf16,
@ -356,16 +356,7 @@ pub enum ObjectKind {
GeneratorFunction(OrdinaryFunction), GeneratorFunction(OrdinaryFunction),
/// A native rust function. /// A native rust function.
NativeFunction { NativeFunction(NativeFunctionObject),
/// The rust function.
function: NativeFunction,
/// The kind of the function constructor if it is a constructor.
constructor: Option<ConstructorKind>,
/// The [`Realm`] in which the function is defined.
realm: Realm,
},
/// The `Set` object kind. /// The `Set` object kind.
Set(OrderedSet), Set(OrderedSet),
@ -510,10 +501,7 @@ unsafe impl Trace for ObjectKind {
Self::OrdinaryFunction(f) | Self::GeneratorFunction(f) | Self::AsyncGeneratorFunction(f) => mark(f), Self::OrdinaryFunction(f) | Self::GeneratorFunction(f) | Self::AsyncGeneratorFunction(f) => mark(f),
Self::BoundFunction(f) => mark(f), Self::BoundFunction(f) => mark(f),
Self::Generator(g) => mark(g), Self::Generator(g) => mark(g),
Self::NativeFunction { function, constructor: _, realm } => { Self::NativeFunction(f) => mark(f),
mark(function);
mark(realm);
}
Self::Set(s) => mark(s), Self::Set(s) => mark(s),
Self::SetIterator(i) => mark(i), Self::SetIterator(i) => mark(i),
Self::StringIterator(i) => mark(i), Self::StringIterator(i) => mark(i),
@ -659,9 +647,9 @@ impl ObjectData {
/// Create the `RegExp` object data /// Create the `RegExp` object data
#[must_use] #[must_use]
pub fn reg_exp(reg_exp: Box<RegExp>) -> Self { pub fn regexp(reg_exp: RegExp) -> Self {
Self { Self {
kind: ObjectKind::RegExp(reg_exp), kind: ObjectKind::RegExp(Box::new(reg_exp)),
internal_methods: &ORDINARY_INTERNAL_METHODS, internal_methods: &ORDINARY_INTERNAL_METHODS,
} }
} }
@ -737,8 +725,8 @@ impl ObjectData {
/// Create the native function object data /// Create the native function object data
#[must_use] #[must_use]
pub fn native_function(function: NativeFunction, constructor: bool, realm: Realm) -> Self { pub fn native_function(function: NativeFunctionObject) -> Self {
let internal_methods = if constructor { let internal_methods = if function.constructor.is_some() {
&NATIVE_CONSTRUCTOR_INTERNAL_METHODS &NATIVE_CONSTRUCTOR_INTERNAL_METHODS
} else { } else {
&NATIVE_FUNCTION_INTERNAL_METHODS &NATIVE_FUNCTION_INTERNAL_METHODS
@ -746,11 +734,7 @@ impl ObjectData {
Self { Self {
internal_methods, internal_methods,
kind: ObjectKind::NativeFunction { kind: ObjectKind::NativeFunction(function),
function,
constructor: constructor.then_some(ConstructorKind::Base),
realm,
},
} }
} }
@ -1210,9 +1194,12 @@ impl Debug for ObjectKind {
} }
impl Object { impl Object {
/// Returns a mutable reference to the kind of an object. /// Creates a new `Object` with the specified `ObjectKind`.
pub(crate) fn kind_mut(&mut self) -> &mut ObjectKind { pub(crate) fn with_kind(kind: ObjectKind) -> Self {
&mut self.kind Self {
kind,
..Self::default()
}
} }
/// Returns the shape of the object. /// Returns the shape of the object.
@ -1726,6 +1713,16 @@ impl Object {
} }
} }
/// Gets a mutable reference to the regexp data if the object is a regexp.
#[inline]
#[must_use]
pub fn as_regexp_mut(&mut self) -> Option<&mut RegExp> {
match &mut self.kind {
ObjectKind::RegExp(regexp) => Some(regexp),
_ => None,
}
}
/// Checks if it a `TypedArray` object. /// Checks if it a `TypedArray` object.
#[inline] #[inline]
#[must_use] #[must_use]
@ -1958,6 +1955,30 @@ impl Object {
} }
} }
/// Returns `true` if it holds a native Rust function.
#[inline]
#[must_use]
pub const fn is_native_function(&self) -> bool {
matches!(self.kind, ObjectKind::NativeFunction { .. })
}
/// Returns this `NativeFunctionObject` if this object contains a `NativeFunctionObject`.
pub(crate) fn as_native_function(&self) -> Option<&NativeFunctionObject> {
match &self.kind {
ObjectKind::NativeFunction(f) => Some(f),
_ => None,
}
}
/// Returns a mutable reference to this `NativeFunctionObject` if this object contains a
/// `NativeFunctionObject`.
pub(crate) fn as_native_function_mut(&mut self) -> Option<&mut NativeFunctionObject> {
match &mut self.kind {
ObjectKind::NativeFunction(f) => Some(f),
_ => None,
}
}
/// Gets the prototype instance of this object. /// Gets the prototype instance of this object.
#[inline] #[inline]
#[must_use] #[must_use]
@ -1990,13 +2011,6 @@ impl Object {
matches!(self.kind, ObjectKind::NativeObject(_)) matches!(self.kind, ObjectKind::NativeObject(_))
} }
/// Returns `true` if it holds a native Rust function.
#[inline]
#[must_use]
pub const fn is_native_function(&self) -> bool {
matches!(self.kind, ObjectKind::NativeFunction { .. })
}
/// Gets the native object data if the object is a `NativeObject`. /// Gets the native object data if the object is a `NativeObject`.
#[inline] #[inline]
#[must_use] #[must_use]
@ -2602,11 +2616,11 @@ impl<'realm> FunctionObjectBuilder<'realm> {
#[must_use] #[must_use]
pub fn build(self) -> JsFunction { pub fn build(self) -> JsFunction {
let object = self.realm.intrinsics().templates().function().create( let object = self.realm.intrinsics().templates().function().create(
ObjectData::native_function( ObjectData::native_function(NativeFunctionObject {
self.function, f: self.function,
self.constructor.is_some(), constructor: self.constructor,
self.realm.clone(), realm: Some(self.realm.clone()),
), }),
vec![self.length.into(), self.name.into()], vec![self.length.into(), self.name.into()],
); );
@ -3044,11 +3058,11 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
let mut constructor = self.constructor_object; let mut constructor = self.constructor_object;
constructor.insert(utf16!("length"), length); constructor.insert(utf16!("length"), length);
constructor.insert(utf16!("name"), name); constructor.insert(utf16!("name"), name);
let data = ObjectData::native_function( let data = ObjectData::native_function(NativeFunctionObject {
self.function, f: self.function,
self.kind.is_some(), constructor: self.kind,
self.context.realm().clone(), realm: Some(self.context.realm().clone()),
); });
constructor.kind = data.kind; constructor.kind = data.kind;

4
boa_engine/src/object/operations.rs

@ -720,8 +720,8 @@ impl JsObject {
return Ok(fun.realm().clone()); return Ok(fun.realm().clone());
} }
if let ObjectKind::NativeFunction { realm, .. } = constructor.kind() { if let ObjectKind::NativeFunction(f) = constructor.kind() {
return Ok(realm.clone()); return Ok(f.realm.clone().unwrap_or_else(|| context.realm().clone()));
} }
if let Some(bound) = constructor.as_bound_function() { if let Some(bound) = constructor.as_bound_function() {

17
boa_examples/src/bin/modules.rs

@ -4,7 +4,6 @@ use boa_engine::{
builtins::promise::PromiseState, builtins::promise::PromiseState,
js_string, js_string,
module::{ModuleLoader, SimpleModuleLoader}, module::{ModuleLoader, SimpleModuleLoader},
object::FunctionObjectBuilder,
Context, JsError, JsNativeError, JsValue, Module, NativeFunction, Context, JsError, JsNativeError, JsValue, Module, NativeFunction,
}; };
use boa_parser::Source; use boa_parser::Source;
@ -58,8 +57,6 @@ fn main() -> Result<(), Box<dyn Error>> {
.load(context) .load(context)
.then( .then(
Some( Some(
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures( NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| { |_, _, module, context| {
// After loading, link all modules by resolving the imports // After loading, link all modules by resolving the imports
@ -70,17 +67,14 @@ fn main() -> Result<(), Box<dyn Error>> {
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
module.clone(), module.clone(),
),
) )
.build(), .to_js_function(context.realm()),
), ),
None, None,
context, context,
)? )
.then( .then(
Some( Some(
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures( NativeFunction::from_copy_closure_with_captures(
// Finally, evaluate the root module. // Finally, evaluate the root module.
// This returns a `JsPromise` since a module could have // This returns a `JsPromise` since a module could have
@ -88,19 +82,18 @@ fn main() -> Result<(), Box<dyn Error>> {
// job queue. // job queue.
|_, _, module, context| Ok(module.evaluate(context).into()), |_, _, module, context| Ok(module.evaluate(context).into()),
module.clone(), module.clone(),
),
) )
.build(), .to_js_function(context.realm()),
), ),
None, None,
context, context,
)?; );
// Very important to push forward the job queue after queueing promises. // Very important to push forward the job queue after queueing promises.
context.run_jobs(); context.run_jobs();
// Checking if the final promise didn't return an error. // Checking if the final promise didn't return an error.
match promise_result.state()? { match promise_result.state() {
PromiseState::Pending => return Err("module didn't execute!".into()), PromiseState::Pending => return Err("module didn't execute!".into()),
PromiseState::Fulfilled(v) => { PromiseState::Fulfilled(v) => {
assert_eq!(v, JsValue::undefined()) assert_eq!(v, JsValue::undefined())

4
boa_examples/src/bin/synthetic.rs

@ -59,13 +59,13 @@ fn main() -> Result<(), Box<dyn Error>> {
// This uses the utility function to load, link and evaluate a module without having to deal // This uses the utility function to load, link and evaluate a module without having to deal
// with callbacks. For an example demonstrating the whole lifecycle of a module, see // with callbacks. For an example demonstrating the whole lifecycle of a module, see
// `modules.rs` // `modules.rs`
let promise_result = module.load_link_evaluate(context)?; let promise_result = module.load_link_evaluate(context);
// Very important to push forward the job queue after queueing promises. // Very important to push forward the job queue after queueing promises.
context.run_jobs(); context.run_jobs();
// Checking if the final promise didn't return an error. // Checking if the final promise didn't return an error.
match promise_result.state()? { match promise_result.state() {
PromiseState::Pending => return Err("module didn't execute!".into()), PromiseState::Pending => return Err("module didn't execute!".into()),
PromiseState::Fulfilled(v) => { PromiseState::Fulfilled(v) => {
assert_eq!(v, JsValue::undefined()) assert_eq!(v, JsValue::undefined())

25
boa_tester/src/exec/mod.rs

@ -250,17 +250,11 @@ impl Test {
module.clone(), module.clone(),
); );
let promise = match module.load_link_evaluate(context) { let promise = module.load_link_evaluate(context);
Ok(promise) => promise,
Err(err) => return (false, format!("Uncaught {err}")),
};
context.run_jobs(); context.run_jobs();
match promise match promise.state() {
.state()
.expect("tester can only use builtin promises")
{
PromiseState::Pending => { PromiseState::Pending => {
return (false, "module should have been executed".to_string()) return (false, "module should have been executed".to_string())
} }
@ -364,10 +358,7 @@ impl Test {
context.run_jobs(); context.run_jobs();
match promise match promise.state() {
.state()
.expect("tester can only use builtin promises")
{
PromiseState::Pending => { PromiseState::Pending => {
return (false, "module didn't try to load".to_string()) return (false, "module didn't try to load".to_string())
} }
@ -428,10 +419,7 @@ impl Test {
context.run_jobs(); context.run_jobs();
match promise match promise.state() {
.state()
.expect("tester can only use builtin promises")
{
PromiseState::Pending => { PromiseState::Pending => {
return (false, "module didn't try to load".to_string()) return (false, "module didn't try to load".to_string())
} }
@ -449,10 +437,7 @@ impl Test {
context.run_jobs(); context.run_jobs();
match promise match promise.state() {
.state()
.expect("tester can only use builtin promises")
{
PromiseState::Pending => { PromiseState::Pending => {
return (false, "module didn't try to evaluate".to_string()) return (false, "module didn't try to evaluate".to_string())
} }

Loading…
Cancel
Save