diff --git a/boa_ast/src/expression/regexp.rs b/boa_ast/src/expression/regexp.rs index ccb3848950..031a9f9334 100644 --- a/boa_ast/src/expression/regexp.rs +++ b/boa_ast/src/expression/regexp.rs @@ -35,21 +35,21 @@ pub struct RegExpLiteral { } impl RegExpLiteral { - /// Create a new [`RegExp`]. + /// Create a new [`RegExpLiteral`]. #[inline] #[must_use] pub const fn new(pattern: Sym, flags: Sym) -> Self { Self { pattern, flags } } - /// Get the pattern part of the [`RegExp`]. + /// Get the pattern part of the [`RegExpLiteral`]. #[inline] #[must_use] pub const fn pattern(&self) -> Sym { self.pattern } - /// Get the flags part of the [`RegExp`]. + /// Get the flags part of the [`RegExpLiteral`]. #[inline] #[must_use] pub const fn flags(&self) -> Sym { diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index 31f9a72b54..073613eafe 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -325,7 +325,7 @@ fn evaluate_files( Err(v) => eprintln!("Uncaught {v}"), } } else if args.module { - let result = (|| { + let result: JsResult = (|| { let module = Module::parse(Source::from_bytes(&buffer), None, context)?; loader.insert( @@ -334,10 +334,10 @@ fn evaluate_files( module.clone(), ); - let promise = module.load_link_evaluate(context)?; + let promise = module.load_link_evaluate(context); context.run_jobs(); - promise.state() + Ok(promise.state()) })(); match result { diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 9ebbed89cc..aab6c9c40a 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -20,7 +20,8 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, 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, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -115,15 +116,6 @@ pub(crate) struct ThrowTypeError; impl IntrinsicObject for ThrowTypeError { fn init(realm: &Realm) { - fn throw_type_error(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - 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::(realm) .prototype(realm.intrinsics().constructors().function().prototype()) .static_property(utf16!("length"), 0, Attribute::empty()) @@ -132,12 +124,21 @@ impl IntrinsicObject for ThrowTypeError { let mut obj = obj.borrow_mut(); - obj.extensible = false; - *obj.kind_mut() = ObjectKind::NativeFunction { - function: NativeFunction::from_fn_ptr(throw_type_error), + *obj.as_native_function_mut() + .expect("`%ThrowTypeError%` must be a function") = NativeFunctionObject { + 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, - realm: realm.clone(), - } + realm: Some(realm.clone()), + }; + + obj.extensible = false; } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index e150d22000..097c5fbd17 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -46,6 +46,9 @@ pub(crate) mod options; #[cfg(feature = "temporal")] pub mod temporal; +use boa_gc::GcRefMut; + +use self::function::ConstructorKind; pub(crate) use self::{ array::Array, async_function::AsyncFunction, @@ -100,11 +103,11 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - native_function::{NativeFunction, NativeFunctionPointer}, + native_function::{NativeFunction, NativeFunctionObject, NativeFunctionPointer}, object::{ shape::{property_table::PropertyTableInner, slot::SlotAttributes}, - FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, ObjectKind, - CONSTRUCTOR, PROTOTYPE, + FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, CONSTRUCTOR, + PROTOTYPE, }, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, @@ -405,85 +408,6 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult // === 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(&mut self, key: K, property: P) - where - K: Into, - P: Into, - { - 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. struct Constructor { prototype: JsObject, @@ -528,11 +452,11 @@ struct OrdinaryObject; /// Applies the pending builder data to the object. trait ApplyToObject { - fn apply_to(self, object: &mut BuiltInObjectInitializer); + fn apply_to(self, object: &JsObject); } impl ApplyToObject for Constructor { - fn apply_to(self, object: &mut BuiltInObjectInitializer) { + fn apply_to(self, object: &JsObject) { object.insert( PROTOTYPE, PropertyDescriptor::builder() @@ -542,15 +466,13 @@ impl ApplyToObject for Constructor { .configurable(false), ); - let object = object.as_shared(); - { let mut prototype = self.prototype.borrow_mut(); prototype.set_prototype(self.inherits); prototype.insert( CONSTRUCTOR, PropertyDescriptor::builder() - .value(object) + .value(object.clone()) .writable(self.attributes.writable()) .enumerable(self.attributes.enumerable()) .configurable(self.attributes.configurable()), @@ -560,21 +482,23 @@ impl ApplyToObject for Constructor { } impl ApplyToObject for ConstructorNoProto { - fn apply_to(self, _: &mut BuiltInObjectInitializer) {} + fn apply_to(self, _: &JsObject) {} } impl ApplyToObject for OrdinaryFunction { - fn apply_to(self, _: &mut BuiltInObjectInitializer) {} + fn apply_to(self, _: &JsObject) {} } impl ApplyToObject for Callable { - fn apply_to(self, object: &mut BuiltInObjectInitializer) { - let function = ObjectData::native_function( - NativeFunction::from_fn_ptr(self.function), - S::IS_CONSTRUCTOR, - self.realm, - ); - object.set_data(function); + fn apply_to(self, object: &JsObject) { + { + let mut function = + GcRefMut::try_map(object.borrow_mut(), Object::as_native_function_mut) + .expect("Builtin must be a function object"); + function.f = NativeFunction::from_fn_ptr(self.function); + function.constructor = S::IS_CONSTRUCTOR.then_some(ConstructorKind::Base); + function.realm = Some(self.realm); + } object.insert( utf16!("length"), PropertyDescriptor::builder() @@ -597,7 +521,7 @@ impl ApplyToObject for Callable { } impl ApplyToObject for OrdinaryObject { - fn apply_to(self, _: &mut BuiltInObjectInitializer) {} + fn apply_to(self, _: &JsObject) {} } /// 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"] struct BuiltInBuilder<'ctx, Kind> { realm: &'ctx Realm, - object: BuiltInObjectInitializer, + object: JsObject, kind: Kind, prototype: JsObject, } 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( realm: &'ctx Realm, ) -> BuiltInBuilder<'ctx, OrdinaryObject> { BuiltInBuilder { realm, - object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())), + object: I::get(realm.intrinsics()), kind: OrdinaryObject, prototype: realm.intrinsics().constructors().object().prototype(), } @@ -836,12 +748,6 @@ impl BuiltInConstructorWithPrototype<'_> { } 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 name = self.name.clone(); let prototype = self.prototype.clone(); @@ -871,7 +777,12 @@ impl BuiltInConstructorWithPrototype<'_> { } 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 .properties_mut() .shape @@ -886,19 +797,18 @@ impl BuiltInConstructorWithPrototype<'_> { } 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 name = self.name.clone(); self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE); self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); 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 .properties_mut() .shape @@ -940,11 +850,11 @@ impl BuiltInCallable<'_> { fn build(self) -> JsFunction { let object = self.realm.intrinsics().templates().function().create( - ObjectData::native_function( - NativeFunction::from_fn_ptr(self.function), - false, - self.realm.clone(), - ), + ObjectData::native_function(NativeFunctionObject { + f: NativeFunction::from_fn_ptr(self.function), + constructor: None, + realm: Some(self.realm.clone()), + }), vec![JsValue::new(self.length), JsValue::new(self.name)], ); @@ -968,7 +878,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { ) -> BuiltInBuilder<'ctx, Callable> { BuiltInBuilder { realm, - object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())), + object: I::get(realm.intrinsics()), kind: Callable { function, name: js_string!(""), @@ -987,7 +897,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { ) -> BuiltInBuilder<'ctx, Callable> { BuiltInBuilder { realm, - object: BuiltInObjectInitializer::Shared(object), + object, kind: Callable { function, name: js_string!(""), @@ -1025,12 +935,7 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable> { impl BuiltInBuilder<'_, T> { /// Adds a new static method to the builtin object. - fn static_method( - mut self, - function: NativeFunctionPointer, - binding: B, - length: usize, - ) -> Self + fn static_method(self, function: NativeFunctionPointer, binding: B, length: usize) -> Self where B: Into, { @@ -1052,7 +957,7 @@ impl BuiltInBuilder<'_, T> { } /// Adds a new static data property to the builtin object. - fn static_property(mut self, key: K, value: V, attribute: Attribute) -> Self + fn static_property(self, key: K, value: V, attribute: Attribute) -> Self where K: Into, V: Into, @@ -1096,22 +1001,22 @@ impl BuiltInBuilder<'_, Callable> { impl BuiltInBuilder<'_, OrdinaryObject> { /// Build the builtin object. - fn build(mut self) -> JsObject { - self.kind.apply_to(&mut self.object); + fn build(self) -> JsObject { + 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 BuiltInBuilder<'_, Callable> { /// Build the builtin callable. - fn build(mut self) -> JsFunction { - self.kind.apply_to(&mut self.object); + fn build(self) -> JsFunction { + 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) } } diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 9ca6a727e3..966acc672a 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -15,10 +15,9 @@ use crate::{ error::JsNativeError, js_string, object::{ - internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, ObjectKind, - CONSTRUCTOR, + internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, CONSTRUCTOR, }, - property::{Attribute, PropertyDescriptorBuilder}, + property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16, CodePoint}, symbol::JsSymbol, @@ -252,10 +251,11 @@ impl BuiltInConstructor for RegExp { }; // 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). - Self::initialize(o, &p, &f, context) + Self::initialize(Some(proto), &p, &f, context) } } @@ -294,50 +294,16 @@ impl RegExp { Ok(None) } - /// `22.2.3.2.1 RegExpAlloc ( newTarget )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-regexpalloc - pub(crate) fn alloc(new_target: &JsValue, context: &mut Context<'_>) -> JsResult { - // 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 )` + /// Compiles a `RegExp` from the provided pattern and flags. /// - /// More information: - /// - [ECMAScript reference][spec] + /// Equivalent to the beginning of [`RegExpInitialize ( obj, pattern, flags )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize - pub(crate) fn initialize( - obj: JsObject, + fn compile_native_regexp( pattern: &JsValue, flags: &JsValue, context: &mut Context<'_>, - ) -> JsResult { + ) -> JsResult { // 1. If pattern is undefined, let P be the empty String. // 2. Else, let P be ? ToString(pattern). let p = if pattern.is_undefined() { @@ -362,43 +328,66 @@ impl RegExp { Ok(result) => result, }; - // 10. If u is true, then - // a. Let patternText be StringToCodePoints(P). - // 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. + // 13. Let parseResult be ParsePattern(patternText, u, v). + // 14. If parseResult is a non-empty List of SyntaxError objects, throw a SyntaxError exception. let matcher = - match Regex::from_unicode(p.code_points().map(CodePoint::as_u32), Flags::from(flags)) { - Err(error) => { - return Err(JsNativeError::syntax() + Regex::from_unicode(p.code_points().map(CodePoint::as_u32), Flags::from(flags)) + .map_err(|error| { + JsNativeError::syntax() .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, flags, original_source: p, 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). - obj.set(utf16!("lastIndex"), 0, true, context)?; + /// `RegExpInitialize ( obj, pattern, flags )` + /// + /// 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, + pattern: &JsValue, + flags: &JsValue, + context: &mut Context<'_>, + ) -> JsResult { + // 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()) } @@ -410,10 +399,8 @@ impl RegExp { /// [spec]: https://tc39.es/ecma262/#sec-regexpcreate pub(crate) fn create(p: &JsValue, f: &JsValue, context: &mut Context<'_>) -> JsResult { // 1. Let obj be ? RegExpAlloc(%RegExp%). - let obj = Self::alloc(&context.global_object().get(Self::NAME, context)?, context)?; - // 2. Return ? RegExpInitialize(obj, P, F). - Self::initialize(obj, p, f, context) + Self::initialize(None, p, f, context) } /// `get RegExp [ @@species ]` @@ -1859,6 +1846,7 @@ impl RegExp { fn compile(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[RegExpMatcher]]). + let this = this .as_object() .filter(|o| o.borrow().is_regexp()) @@ -1893,8 +1881,20 @@ impl RegExp { // b. Let F be flags. (pattern.clone(), flags.clone()) }; + + let regexp = Self::compile_native_regexp(&pattern, &flags, context)?; + // 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()) } } diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 18cb4bb259..14c7ad31a6 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -1324,6 +1324,9 @@ pub(crate) struct ObjectTemplates { bigint: ObjectTemplate, boolean: ObjectTemplate, + regexp: ObjectTemplate, + regexp_without_proto: ObjectTemplate, + unmapped_arguments: ObjectTemplate, mapped_arguments: ObjectTemplate, @@ -1369,6 +1372,12 @@ impl ObjectTemplates { ); 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 mut function = ObjectTemplate::new(root_shape); function.property( @@ -1474,6 +1483,8 @@ impl ObjectTemplates { symbol, bigint, boolean, + regexp, + regexp_without_proto: regexp_without_prototype, unmapped_arguments, mapped_arguments, function_with_prototype, @@ -1564,6 +1575,25 @@ impl ObjectTemplates { &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. /// /// Transitions: diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 9fc0c2ba81..638058629c 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -75,7 +75,9 @@ use self::intrinsics::StandardConstructor; /// let arg = ObjectInitializer::new(&mut context) /// .property(js_string!("x"), 12, Attribute::READONLY) /// .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(); /// diff --git a/boa_engine/src/module/mod.rs b/boa_engine/src/module/mod.rs index a2941118e4..6522378849 100644 --- a/boa_engine/src/module/mod.rs +++ b/boa_engine/src/module/mod.rs @@ -47,7 +47,7 @@ use crate::{ builtins::promise::{PromiseCapability, PromiseState}, environments::DeclarativeEnvironment, js_string, - object::{FunctionObjectBuilder, JsObject, JsPromise, ObjectData}, + object::{JsObject, JsPromise, ObjectData}, realm::Realm, Context, HostDefined, JsError, JsResult, JsString, JsValue, NativeFunction, }; @@ -430,7 +430,7 @@ impl Module { ModuleKind::Synthetic(synth) => { // a. Let promise be ! module.Evaluate(). let promise: JsPromise = synth.evaluate(context); - let state = promise.state()?; + let state = promise.state(); match state { PromiseState::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()); /// - /// let promise = module.load_link_evaluate(context).unwrap(); + /// let promise = module.load_link_evaluate(context); /// /// context.run_jobs(); /// /// assert_eq!( - /// promise.state().unwrap(), + /// promise.state(), /// PromiseState::Fulfilled(JsValue::undefined()) /// ); /// ``` #[allow(dropping_copy_types)] #[inline] - pub fn load_link_evaluate(&self, context: &mut Context<'_>) -> JsResult { + pub fn load_link_evaluate(&self, context: &mut Context<'_>) -> JsPromise { let main_timer = Profiler::global().start_event("Module evaluation", "Main"); let promise = self .load(context) .then( Some( - FunctionObjectBuilder::new( - context.realm(), - NativeFunction::from_copy_closure_with_captures( - |_, _, module, context| { - module.link(context)?; - Ok(JsValue::undefined()) - }, - self.clone(), - ), + NativeFunction::from_copy_closure_with_captures( + |_, _, module, context| { + module.link(context)?; + Ok(JsValue::undefined()) + }, + self.clone(), ) - .build(), + .to_js_function(context.realm()), ), None, context, - )? + ) .then( Some( - FunctionObjectBuilder::new( - context.realm(), - NativeFunction::from_copy_closure_with_captures( - |_, _, module, context| Ok(module.evaluate(context).into()), - self.clone(), - ), + NativeFunction::from_copy_closure_with_captures( + |_, _, module, context| Ok(module.evaluate(context).into()), + self.clone(), ) - .build(), + .to_js_function(context.realm()), ), None, context, - )?; + ); // The main_timer needs to be dropped before the Profiler is. drop(main_timer); Profiler::global().drop(); - Ok(promise) + promise } /// Abstract operation [`GetModuleNamespace ( module )`][spec]. diff --git a/boa_engine/src/module/synthetic.rs b/boa_engine/src/module/synthetic.rs index 01a3051458..9587e90ada 100644 --- a/boa_engine/src/module/synthetic.rs +++ b/boa_engine/src/module/synthetic.rs @@ -198,7 +198,6 @@ impl SyntheticModule { pub(super) fn load(context: &mut Context<'_>) -> JsPromise { // 1. Return ! PromiseResolve(%Promise%, undefined). JsPromise::resolve(JsValue::undefined(), context) - .expect("creating a promise from the %Promise% constructor must not fail") } /// Concrete method [`GetExportedNames ( [ exportStarSet ] )`][spec]. diff --git a/boa_engine/src/native_function.rs b/boa_engine/src/native_function.rs index 20d65c3ed5..2f707716c7 100644 --- a/boa_engine/src/native_function.rs +++ b/boa_engine/src/native_function.rs @@ -5,7 +5,12 @@ 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. /// @@ -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, + /// The [`Realm`] in which the function is defined, or `None` if the realm is uninitialized. + pub(crate) realm: Option, +} + +// 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. /// /// `NativeFunction` functions are divided in two: @@ -280,4 +304,12 @@ impl NativeFunction { 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() + } } diff --git a/boa_engine/src/object/builtins/jsfunction.rs b/boa_engine/src/object/builtins/jsfunction.rs index d252b84bcd..046f68a2f0 100644 --- a/boa_engine/src/object/builtins/jsfunction.rs +++ b/boa_engine/src/object/builtins/jsfunction.rs @@ -1,13 +1,15 @@ //! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object use crate::{ + builtins::function::ConstructorKind, + native_function::NativeFunctionObject, object::{ internal_methods::function::{ NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS, }, - JsObject, JsObjectType, Object, + JsObject, JsObjectType, Object, ObjectKind, }, value::TryFromJs, - Context, JsNativeError, JsResult, JsValue, + Context, JsNativeError, JsResult, JsValue, NativeFunction, }; use boa_gc::{Finalize, Trace}; use std::ops::Deref; @@ -32,7 +34,11 @@ impl JsFunction { pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self { Self { 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 { &NATIVE_CONSTRUCTOR_INTERNAL_METHODS } else { diff --git a/boa_engine/src/object/builtins/jspromise.rs b/boa_engine/src/object/builtins/jspromise.rs index 2c00694464..9d69402080 100644 --- a/boa_engine/src/object/builtins/jspromise.rs +++ b/boa_engine/src/object/builtins/jspromise.rs @@ -8,9 +8,8 @@ use crate::{ promise::{PromiseState, ResolvingFunctions}, Promise, }, - context::intrinsics::StandardConstructors, job::NativeJob, - object::{FunctionObjectBuilder, JsObject, JsObjectType, ObjectData}, + object::{JsObject, JsObjectType, ObjectData}, value::TryFromJs, Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction, }; @@ -52,56 +51,45 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace}; /// Ok(JsValue::undefined()) /// }, /// context, -/// )?; +/// ); /// /// let promise = promise /// .then( /// Some( -/// FunctionObjectBuilder::new( -/// context.realm(), -/// NativeFunction::from_fn_ptr(|_, args, _| { -/// Err(JsError::from_opaque( -/// args.get_or_undefined(0).clone(), -/// ) +/// NativeFunction::from_fn_ptr(|_, args, _| { +/// Err(JsError::from_opaque(args.get_or_undefined(0).clone()) /// .into()) -/// }), -/// ) -/// .build(), +/// }) +/// .to_js_function(context.realm()), /// ), /// None, /// context, -/// )? +/// ) /// .catch( -/// FunctionObjectBuilder::new( -/// context.realm(), -/// NativeFunction::from_fn_ptr(|_, args, _| { -/// Ok(args.get_or_undefined(0).clone()) -/// }), -/// ) -/// .build(), +/// NativeFunction::from_fn_ptr(|_, args, _| { +/// Ok(args.get_or_undefined(0).clone()) +/// }) +/// .to_js_function(context.realm()), /// context, -/// )? +/// ) /// .finally( -/// FunctionObjectBuilder::new( -/// context.realm(), -/// NativeFunction::from_fn_ptr(|_, _, context| { -/// context.global_object().clone().set( -/// js_string!("finally"), -/// JsValue::from(true), -/// true, -/// context, -/// )?; -/// Ok(JsValue::undefined()) -/// }), -/// ) -/// .build(), +/// NativeFunction::from_fn_ptr(|_, _, context| { +/// context.global_object().clone().set( +/// js_string!("finally"), +/// JsValue::from(true), +/// true, +/// context, +/// )?; +/// Ok(JsValue::undefined()) +/// }) +/// .to_js_function(context.realm()), /// context, -/// )?; +/// ); /// /// context.run_jobs(); /// /// assert_eq!( -/// promise.state()?, +/// promise.state(), /// PromiseState::Fulfilled(js_string!("hello world!").into()) /// ); /// @@ -160,12 +148,12 @@ impl JsPromise { /// Ok(JsValue::undefined()) /// }, /// context, - /// )?; + /// ); /// /// context.run_jobs(); /// /// assert_eq!( - /// promise.state()?, + /// promise.state(), /// PromiseState::Fulfilled(js_string!("hello world").into()) /// ); /// # Ok(()) @@ -173,7 +161,7 @@ impl JsPromise { /// ``` /// /// [`Promise()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise - pub fn new(executor: F, context: &mut Context<'_>) -> JsResult + pub fn new(executor: F, context: &mut Context<'_>) -> Self where F: FnOnce(&ResolvingFunctions, &mut Context<'_>) -> JsResult, { @@ -188,10 +176,11 @@ impl JsPromise { let e = e.to_opaque(context); resolvers .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`. @@ -214,13 +203,13 @@ impl JsPromise { /// /// let (promise, resolvers) = JsPromise::new_pending(context); /// - /// assert_eq!(promise.state()?, PromiseState::Pending); + /// assert_eq!(promise.state(), PromiseState::Pending); /// /// resolvers /// .reject /// .call(&JsValue::undefined(), &[5.into()], context)?; /// - /// assert_eq!(promise.state()?, PromiseState::Rejected(5.into())); + /// assert_eq!(promise.state(), PromiseState::Rejected(5.into())); /// /// # Ok(()) /// # } @@ -262,7 +251,7 @@ impl JsPromise { /// let promise = JsPromise::from_object(promise)?; /// /// assert_eq!( - /// promise.state()?, + /// promise.state(), /// PromiseState::Fulfilled(JsValue::undefined()) /// ); /// @@ -295,7 +284,6 @@ impl JsPromise { /// # builtins::promise::PromiseState, /// # Context, JsResult, JsValue /// # }; - /// # fn main() -> Result<(), Box> { /// async fn f() -> JsResult { /// Ok(JsValue::null()) /// } @@ -305,9 +293,7 @@ impl JsPromise { /// /// context.run_jobs(); /// - /// assert_eq!(promise.state()?, PromiseState::Fulfilled(JsValue::null())); - /// # Ok(()) - /// # } + /// assert_eq!(promise.state(), PromiseState::Fulfilled(JsValue::null())); /// ``` /// /// [async_fn]: crate::native_function::NativeFunction::from_async_fn @@ -353,29 +339,26 @@ impl JsPromise { /// # builtins::promise::PromiseState, /// # Context, js_string /// # }; - /// # fn main() -> Result<(), Box> { /// let context = &mut Context::default(); /// - /// let promise = JsPromise::resolve(js_string!("resolved!"), context)?; + /// let promise = JsPromise::resolve(js_string!("resolved!"), context); /// /// assert_eq!( - /// promise.state()?, + /// promise.state(), /// PromiseState::Fulfilled(js_string!("resolved!").into()) /// ); - /// - /// # Ok(()) - /// # } /// ``` /// /// [`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 - pub fn resolve>(value: V, context: &mut Context<'_>) -> JsResult { + pub fn resolve>(value: V, context: &mut Context<'_>) -> Self { Promise::promise_resolve( &context.intrinsics().constructors().promise().constructor(), value.into(), context, ) .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`. @@ -394,32 +377,29 @@ impl JsPromise { /// # builtins::promise::PromiseState, /// # Context, js_string, JsError /// # }; - /// # fn main() -> Result<(), Box> { /// let context = &mut Context::default(); /// /// let promise = JsPromise::reject( /// JsError::from_opaque(js_string!("oops!").into()), /// context, - /// )?; + /// ); /// /// assert_eq!( - /// promise.state()?, + /// promise.state(), /// PromiseState::Rejected(js_string!("oops!").into()) /// ); - /// - /// # Ok(()) - /// # } /// ``` /// /// [`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 - pub fn reject>(error: E, context: &mut Context<'_>) -> JsResult { + pub fn reject>(error: E, context: &mut Context<'_>) -> Self { Promise::promise_reject( &context.intrinsics().constructors().promise().constructor(), &error.into(), context, ) .and_then(Self::from_object) + .expect("default resolving functions cannot throw and must return a promise") } /// Gets the current state of the promise. @@ -433,26 +413,21 @@ impl JsPromise { /// # builtins::promise::PromiseState, /// # Context /// # }; - /// # fn main() -> Result<(), Box> { /// let context = &mut Context::default(); /// /// let promise = JsPromise::new_pending(context).0; /// - /// assert_eq!(promise.state()?, PromiseState::Pending); - /// - /// # Ok(()) - /// # } + /// assert_eq!(promise.state(), PromiseState::Pending); /// ``` #[inline] - pub fn state(&self) -> JsResult { - // TODO: if we can guarantee that objects cannot change type after creation, - // we can remove this throw. - let promise = self.inner.borrow(); - let promise = promise + #[must_use] + pub fn state(&self) -> PromiseState { + self.inner + .borrow() .as_promise() - .ok_or_else(|| JsNativeError::typ().with_message("object is not a Promise"))?; - - Ok(promise.state().clone()) + .expect("objects cannot change type after creation") + .state() + .clone() } /// Schedules callback functions to run when the promise settles. @@ -489,7 +464,6 @@ impl JsPromise { /// # object::{builtins::JsPromise, FunctionObjectBuilder}, /// # Context, JsArgs, JsError, JsValue, NativeFunction, /// # }; - /// # fn main() -> Result<(), Box> { /// let context = &mut Context::default(); /// /// let promise = JsPromise::new( @@ -502,44 +476,40 @@ impl JsPromise { /// Ok(JsValue::undefined()) /// }, /// context, - /// )? + /// ) /// .then( /// Some( - /// FunctionObjectBuilder::new( - /// context.realm(), - /// NativeFunction::from_fn_ptr(|_, args, context| { - /// args.get_or_undefined(0) - /// .to_string(context) - /// .map(JsValue::from) - /// }), - /// ) - /// .build(), + /// NativeFunction::from_fn_ptr(|_, args, context| { + /// args.get_or_undefined(0) + /// .to_string(context) + /// .map(JsValue::from) + /// }) + /// .to_js_function(context.realm()), /// ), /// None, /// context, - /// )?; + /// ); /// /// context.run_jobs(); /// /// assert_eq!( - /// promise.state()?, + /// promise.state(), /// 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 #[inline] + #[allow(clippy::return_self_not_must_use)] // Could just be used to add handlers on an existing promise pub fn then( &self, on_fulfilled: Option, on_rejected: Option, context: &mut Context<'_>, - ) -> JsResult { - let result_promise = Promise::inner_then(self, on_fulfilled, on_rejected, context)?; - Self::from_object(result_promise) + ) -> Self { + Promise::inner_then(self, on_fulfilled, on_rejected, context) + .and_then(Self::from_object) + .expect("`inner_then` cannot fail for native `JsPromise`") } /// Schedules a callback to run when the promise is rejected. @@ -559,7 +529,6 @@ impl JsPromise { /// # object::{builtins::JsPromise, FunctionObjectBuilder}, /// # Context, JsArgs, JsNativeError, JsValue, NativeFunction, /// # }; - /// # fn main() -> Result<(), Box> { /// let context = &mut Context::default(); /// /// let promise = JsPromise::new( @@ -574,35 +543,30 @@ impl JsPromise { /// Ok(JsValue::undefined()) /// }, /// context, - /// )? + /// ) /// .catch( - /// FunctionObjectBuilder::new( - /// context.realm(), - /// NativeFunction::from_fn_ptr(|_, args, context| { - /// args.get_or_undefined(0) - /// .to_string(context) - /// .map(JsValue::from) - /// }), - /// ) - /// .build(), + /// NativeFunction::from_fn_ptr(|_, args, context| { + /// args.get_or_undefined(0) + /// .to_string(context) + /// .map(JsValue::from) + /// }) + /// .to_js_function(context.realm()), /// context, - /// )?; + /// ); /// /// context.run_jobs(); /// /// assert_eq!( - /// promise.state()?, + /// promise.state(), /// 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 /// [then]: JsPromise::then #[inline] - pub fn catch(&self, on_rejected: JsFunction, context: &mut Context<'_>) -> JsResult { + #[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) } @@ -633,7 +597,7 @@ impl JsPromise { /// js_string!("finally"), /// false, /// Attribute::all(), - /// ); + /// )?; /// /// let promise = JsPromise::new( /// |resolvers, context| { @@ -647,23 +611,20 @@ impl JsPromise { /// Ok(JsValue::undefined()) /// }, /// context, - /// )? + /// ) /// .finally( - /// FunctionObjectBuilder::new( - /// context.realm(), - /// NativeFunction::from_fn_ptr(|_, _, context| { - /// context.global_object().clone().set( - /// js_string!("finally"), - /// JsValue::from(true), - /// true, - /// context, - /// )?; - /// Ok(JsValue::undefined()) - /// }), - /// ) - /// .build(), + /// NativeFunction::from_fn_ptr(|_, _, context| { + /// context.global_object().clone().set( + /// js_string!("finally"), + /// JsValue::from(true), + /// true, + /// context, + /// )?; + /// Ok(JsValue::undefined()) + /// }) + /// .to_js_function(context.realm()), /// context, - /// )?; + /// ); /// /// 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 /// [then]: JsPromise::then #[inline] - pub fn finally(&self, on_finally: JsFunction, context: &mut Context<'_>) -> JsResult { - let c = self.species_constructor(StandardConstructors::promise, context)?; - let (then, catch) = Promise::then_catch_finally_closures(c, on_finally, context); - self.then(Some(then), Some(catch), context) + #[allow(clippy::return_self_not_must_use)] // Could just be used to add a handler on an existing promise + pub fn finally(&self, on_finally: JsFunction, context: &mut Context<'_>) -> Self { + let (then, catch) = Promise::then_catch_finally_closures( + 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 @@ -707,26 +674,26 @@ impl JsPromise { /// /// let promise1 = JsPromise::all( /// [ - /// JsPromise::resolve(0, context)?, - /// JsPromise::resolve(2, context)?, - /// JsPromise::resolve(4, context)?, + /// JsPromise::resolve(0, context), + /// JsPromise::resolve(2, context), + /// JsPromise::resolve(4, context), /// ], /// context, - /// )?; + /// ); /// /// let promise2 = JsPromise::all( /// [ - /// JsPromise::resolve(1, context)?, - /// JsPromise::reject(JsNativeError::typ(), context)?, - /// JsPromise::resolve(3, context)?, + /// JsPromise::resolve(1, context), + /// JsPromise::reject(JsNativeError::typ(), context), + /// JsPromise::resolve(3, context), /// ], /// context, - /// )?; + /// ); /// /// context.run_jobs(); /// /// let array = promise1 - /// .state()? + /// .state() /// .as_fulfilled() /// .and_then(JsValue::as_object) /// .unwrap() @@ -736,7 +703,7 @@ impl JsPromise { /// assert_eq!(array.at(1, context)?, 2.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")); /// /// # Ok(()) @@ -744,7 +711,7 @@ impl JsPromise { /// ``` /// /// [`Promise.all`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all - pub fn all(promises: I, context: &mut Context<'_>) -> JsResult + pub fn all(promises: I, context: &mut Context<'_>) -> Self where I: IntoIterator, { @@ -757,12 +724,15 @@ impl JsPromise { .constructor() .into(); - let value = Promise::all(c, &[promises.into()], context)?; - let value = value + let value = Promise::all(c, &[promises.into()], context) + .expect("Promise.all cannot fail with the default `%Promise%` constructor"); + + let object = value .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 @@ -784,17 +754,17 @@ impl JsPromise { /// /// let promise = JsPromise::all_settled( /// [ - /// JsPromise::resolve(1, context)?, - /// JsPromise::reject(JsNativeError::typ(), context)?, - /// JsPromise::resolve(3, context)?, + /// JsPromise::resolve(1, context), + /// JsPromise::reject(JsNativeError::typ(), context), + /// JsPromise::resolve(3, context), /// ], /// context, - /// )?; + /// ); /// /// context.run_jobs(); /// /// let array = promise - /// .state()? + /// .state() /// .as_fulfilled() /// .and_then(JsValue::as_object) /// .unwrap() @@ -830,7 +800,7 @@ impl JsPromise { /// ``` /// /// [`Promise.allSettled`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled - pub fn all_settled(promises: I, context: &mut Context<'_>) -> JsResult + pub fn all_settled(promises: I, context: &mut Context<'_>) -> Self where I: IntoIterator, { @@ -843,12 +813,15 @@ impl JsPromise { .constructor() .into(); - let value = Promise::all_settled(c, &[promises.into()], context)?; - let value = value + let value = Promise::all_settled(c, &[promises.into()], context) + .expect("`Promise.all_settled` cannot fail with the default `%Promise%` constructor"); + + let object = value .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. @@ -869,32 +842,28 @@ impl JsPromise { /// # object::builtins::JsPromise, /// # Context, JsNativeError, /// # }; - /// # fn main() -> Result<(), Box> { /// let context = &mut Context::default(); /// /// let promise = JsPromise::any( /// [ - /// JsPromise::reject(JsNativeError::syntax(), context)?, - /// JsPromise::reject(JsNativeError::typ(), context)?, - /// JsPromise::resolve(js_string!("fulfilled"), context)?, - /// JsPromise::reject(JsNativeError::range(), context)?, + /// JsPromise::reject(JsNativeError::syntax(), context), + /// JsPromise::reject(JsNativeError::typ(), context), + /// JsPromise::resolve(js_string!("fulfilled"), context), + /// JsPromise::reject(JsNativeError::range(), context), /// ], /// context, - /// )?; + /// ); /// /// context.run_jobs(); /// /// assert_eq!( - /// promise.state()?, + /// promise.state(), /// PromiseState::Fulfilled(js_string!("fulfilled").into()) /// ); - /// - /// # Ok(()) - /// # } /// ``` /// /// [`Promise.any`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any - pub fn any(promises: I, context: &mut Context<'_>) -> JsResult + pub fn any(promises: I, context: &mut Context<'_>) -> Self where I: IntoIterator, { @@ -907,12 +876,15 @@ impl JsPromise { .constructor() .into(); - let value = Promise::any(c, &[promises.into()], context)?; - let value = value + let value = Promise::any(c, &[promises.into()], context) + .expect("`Promise.any` cannot fail with the default `%Promise%` constructor"); + + let object = value .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. @@ -939,22 +911,24 @@ impl JsPromise { /// let (b, resolvers_b) = 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 /// .resolve - /// .call(&JsValue::undefined(), &[5.into()], context); + /// .call(&JsValue::undefined(), &[5.into()], context)?; /// resolvers_c.reject.call( /// &JsValue::undefined(), /// &[js_string!("c error").into()], /// context, - /// ); + /// )?; /// /// context.run_jobs(); /// /// assert_eq!( - /// promise.state()?, + /// promise.state(), /// 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 - pub fn race(promises: I, context: &mut Context<'_>) -> JsResult + pub fn race(promises: I, context: &mut Context<'_>) -> Self where I: IntoIterator, { @@ -976,12 +950,15 @@ impl JsPromise { .constructor() .into(); - let value = Promise::race(c, &[promises.into()], context)?; - let value = value + let value = Promise::race(c, &[promises.into()], context) + .expect("`Promise.race` cannot fail with the default `%Promise%` constructor"); + + let object = value .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`. @@ -1003,7 +980,7 @@ impl JsPromise { /// let context = &mut Context::default(); /// /// 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 }; /// @@ -1023,7 +1000,7 @@ impl JsPromise { /// # Ok(()) /// # } /// ``` - pub fn into_js_future(self, context: &mut Context<'_>) -> JsResult { + pub fn into_js_future(self, context: &mut Context<'_>) -> JsFuture { // Mostly based from: // https://docs.rs/wasm-bindgen-futures/0.4.37/src/wasm_bindgen_futures/lib.rs.html#109-168 @@ -1057,39 +1034,35 @@ impl JsPromise { let resolve = { let state = state.clone(); - FunctionObjectBuilder::new( - context.realm(), - NativeFunction::from_copy_closure_with_captures( - move |_, args, state, _| { - finish(state, Ok(args.get_or_undefined(0).clone())); - Ok(JsValue::undefined()) - }, - state, - ), + NativeFunction::from_copy_closure_with_captures( + move |_, args, state, _| { + finish(state, Ok(args.get_or_undefined(0).clone())); + Ok(JsValue::undefined()) + }, + state, ) - .build() }; let reject = { let state = state.clone(); - FunctionObjectBuilder::new( - context.realm(), - NativeFunction::from_copy_closure_with_captures( - move |_, args, state, _| { - let err = JsError::from_opaque(args.get_or_undefined(0).clone()); - finish(state, Err(err)); - Ok(JsValue::undefined()) - }, - state, - ), + NativeFunction::from_copy_closure_with_captures( + move |_, args, state, _| { + let err = JsError::from_opaque(args.get_or_undefined(0).clone()); + finish(state, Err(err)); + Ok(JsValue::undefined()) + }, + 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 } } } diff --git a/boa_engine/src/object/builtins/jsregexp.rs b/boa_engine/src/object/builtins/jsregexp.rs index dfa4e3493d..fc5b2e68a9 100644 --- a/boa_engine/src/object/builtins/jsregexp.rs +++ b/boa_engine/src/object/builtins/jsregexp.rs @@ -60,15 +60,7 @@ impl JsRegExp { where S: Into, { - let constructor = &context - .intrinsics() - .constructors() - .regexp() - .constructor() - .into(); - let obj = RegExp::alloc(constructor, context)?; - - let regexp = RegExp::initialize(obj, &pattern.into(), &flags.into(), context)? + let regexp = RegExp::initialize(None, &pattern.into(), &flags.into(), context)? .as_object() .expect("RegExp::initialize must return a RegExp object") .clone(); diff --git a/boa_engine/src/object/internal_methods/function.rs b/boa_engine/src/object/internal_methods/function.rs index d621739403..2d65f009d3 100644 --- a/boa_engine/src/object/internal_methods/function.rs +++ b/boa_engine/src/object/internal_methods/function.rs @@ -2,9 +2,10 @@ use crate::{ builtins::function::{arguments::Arguments, FunctionKind, ThisMode}, context::intrinsics::StandardConstructors, environments::{FunctionSlots, ThisBindingStatus}, + native_function::NativeFunctionObject, object::{ internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, - JsObject, ObjectData, ObjectKind, + JsObject, ObjectData, }, vm::{CallFrame, CallFrameFlags}, 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. context.check_runtime_limits()?; let this_function_object = obj.clone(); - let object = obj.borrow(); - let ObjectKind::NativeFunction { - function, + let NativeFunctionObject { + f: function, constructor, realm, - } = object.kind() - else { - unreachable!("the object should be a native function object"); - }; + } = obj + .borrow() + .as_native_function() + .cloned() + .expect("the object should be a native function object"); - let mut realm = realm.clone(); - let function = function.clone(); - let constructor = *constructor; - drop(object); + let mut realm = realm.unwrap_or_else(|| context.realm().clone()); context.swap_realm(&mut realm); 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. context.check_runtime_limits()?; let this_function_object = obj.clone(); - let object = obj.borrow(); - let ObjectKind::NativeFunction { - function, + let NativeFunctionObject { + f: function, constructor, realm, - } = object.kind() - else { - unreachable!("the object should be a native function object"); - }; + } = obj + .borrow() + .as_native_function() + .cloned() + .expect("the object should be a native function object"); - let mut realm = realm.clone(); - let function = function.clone(); - let constructor = *constructor; - drop(object); + let mut realm = realm.unwrap_or_else(|| context.realm().clone()); context.swap_realm(&mut realm); context.vm.native_active_function = Some(this_function_object); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 1b215db797..50fa5d0ed1 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -67,7 +67,7 @@ use crate::{ context::intrinsics::StandardConstructor, js_string, module::ModuleNamespace, - native_function::NativeFunction, + native_function::{NativeFunction, NativeFunctionObject}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, string::utf16, @@ -356,16 +356,7 @@ pub enum ObjectKind { GeneratorFunction(OrdinaryFunction), /// A native rust function. - NativeFunction { - /// The rust function. - function: NativeFunction, - - /// The kind of the function constructor if it is a constructor. - constructor: Option, - - /// The [`Realm`] in which the function is defined. - realm: Realm, - }, + NativeFunction(NativeFunctionObject), /// The `Set` object kind. Set(OrderedSet), @@ -510,10 +501,7 @@ unsafe impl Trace for ObjectKind { Self::OrdinaryFunction(f) | Self::GeneratorFunction(f) | Self::AsyncGeneratorFunction(f) => mark(f), Self::BoundFunction(f) => mark(f), Self::Generator(g) => mark(g), - Self::NativeFunction { function, constructor: _, realm } => { - mark(function); - mark(realm); - } + Self::NativeFunction(f) => mark(f), Self::Set(s) => mark(s), Self::SetIterator(i) => mark(i), Self::StringIterator(i) => mark(i), @@ -659,9 +647,9 @@ impl ObjectData { /// Create the `RegExp` object data #[must_use] - pub fn reg_exp(reg_exp: Box) -> Self { + pub fn regexp(reg_exp: RegExp) -> Self { Self { - kind: ObjectKind::RegExp(reg_exp), + kind: ObjectKind::RegExp(Box::new(reg_exp)), internal_methods: &ORDINARY_INTERNAL_METHODS, } } @@ -737,8 +725,8 @@ impl ObjectData { /// Create the native function object data #[must_use] - pub fn native_function(function: NativeFunction, constructor: bool, realm: Realm) -> Self { - let internal_methods = if constructor { + pub fn native_function(function: NativeFunctionObject) -> Self { + let internal_methods = if function.constructor.is_some() { &NATIVE_CONSTRUCTOR_INTERNAL_METHODS } else { &NATIVE_FUNCTION_INTERNAL_METHODS @@ -746,11 +734,7 @@ impl ObjectData { Self { internal_methods, - kind: ObjectKind::NativeFunction { - function, - constructor: constructor.then_some(ConstructorKind::Base), - realm, - }, + kind: ObjectKind::NativeFunction(function), } } @@ -1210,9 +1194,12 @@ impl Debug for ObjectKind { } impl Object { - /// Returns a mutable reference to the kind of an object. - pub(crate) fn kind_mut(&mut self) -> &mut ObjectKind { - &mut self.kind + /// Creates a new `Object` with the specified `ObjectKind`. + pub(crate) fn with_kind(kind: ObjectKind) -> Self { + Self { + kind, + ..Self::default() + } } /// 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. #[inline] #[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. #[inline] #[must_use] @@ -1990,13 +2011,6 @@ impl Object { 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`. #[inline] #[must_use] @@ -2602,11 +2616,11 @@ impl<'realm> FunctionObjectBuilder<'realm> { #[must_use] pub fn build(self) -> JsFunction { let object = self.realm.intrinsics().templates().function().create( - ObjectData::native_function( - self.function, - self.constructor.is_some(), - self.realm.clone(), - ), + ObjectData::native_function(NativeFunctionObject { + f: self.function, + constructor: self.constructor, + realm: Some(self.realm.clone()), + }), vec![self.length.into(), self.name.into()], ); @@ -3044,11 +3058,11 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> { let mut constructor = self.constructor_object; constructor.insert(utf16!("length"), length); constructor.insert(utf16!("name"), name); - let data = ObjectData::native_function( - self.function, - self.kind.is_some(), - self.context.realm().clone(), - ); + let data = ObjectData::native_function(NativeFunctionObject { + f: self.function, + constructor: self.kind, + realm: Some(self.context.realm().clone()), + }); constructor.kind = data.kind; diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index c54f0837b6..42a450be9c 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -720,8 +720,8 @@ impl JsObject { return Ok(fun.realm().clone()); } - if let ObjectKind::NativeFunction { realm, .. } = constructor.kind() { - return Ok(realm.clone()); + if let ObjectKind::NativeFunction(f) = constructor.kind() { + return Ok(f.realm.clone().unwrap_or_else(|| context.realm().clone())); } if let Some(bound) = constructor.as_bound_function() { diff --git a/boa_examples/src/bin/modules.rs b/boa_examples/src/bin/modules.rs index e6c22600ca..02f6d69bc4 100644 --- a/boa_examples/src/bin/modules.rs +++ b/boa_examples/src/bin/modules.rs @@ -4,7 +4,6 @@ use boa_engine::{ builtins::promise::PromiseState, js_string, module::{ModuleLoader, SimpleModuleLoader}, - object::FunctionObjectBuilder, Context, JsError, JsNativeError, JsValue, Module, NativeFunction, }; use boa_parser::Source; @@ -58,49 +57,43 @@ fn main() -> Result<(), Box> { .load(context) .then( Some( - FunctionObjectBuilder::new( - context.realm(), - NativeFunction::from_copy_closure_with_captures( - |_, _, module, context| { - // After loading, link all modules by resolving the imports - // and exports on the full module graph, initializing module - // environments. This returns a plain `Err` since all modules - // must link at the same time. - module.link(context)?; - Ok(JsValue::undefined()) - }, - module.clone(), - ), + NativeFunction::from_copy_closure_with_captures( + |_, _, module, context| { + // After loading, link all modules by resolving the imports + // and exports on the full module graph, initializing module + // environments. This returns a plain `Err` since all modules + // must link at the same time. + module.link(context)?; + Ok(JsValue::undefined()) + }, + module.clone(), ) - .build(), + .to_js_function(context.realm()), ), None, context, - )? + ) .then( Some( - FunctionObjectBuilder::new( - context.realm(), - NativeFunction::from_copy_closure_with_captures( - // Finally, evaluate the root module. - // This returns a `JsPromise` since a module could have - // top-level await statements, which defers module execution to the - // job queue. - |_, _, module, context| Ok(module.evaluate(context).into()), - module.clone(), - ), + NativeFunction::from_copy_closure_with_captures( + // Finally, evaluate the root module. + // This returns a `JsPromise` since a module could have + // top-level await statements, which defers module execution to the + // job queue. + |_, _, module, context| Ok(module.evaluate(context).into()), + module.clone(), ) - .build(), + .to_js_function(context.realm()), ), None, context, - )?; + ); // Very important to push forward the job queue after queueing promises. context.run_jobs(); // 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::Fulfilled(v) => { assert_eq!(v, JsValue::undefined()) diff --git a/boa_examples/src/bin/synthetic.rs b/boa_examples/src/bin/synthetic.rs index 54564cf340..c194d74656 100644 --- a/boa_examples/src/bin/synthetic.rs +++ b/boa_examples/src/bin/synthetic.rs @@ -59,13 +59,13 @@ fn main() -> Result<(), Box> { // 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 // `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. context.run_jobs(); // 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::Fulfilled(v) => { assert_eq!(v, JsValue::undefined()) diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index 356e3d831f..a8656c5856 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -250,17 +250,11 @@ impl Test { module.clone(), ); - let promise = match module.load_link_evaluate(context) { - Ok(promise) => promise, - Err(err) => return (false, format!("Uncaught {err}")), - }; + let promise = module.load_link_evaluate(context); context.run_jobs(); - match promise - .state() - .expect("tester can only use builtin promises") - { + match promise.state() { PromiseState::Pending => { return (false, "module should have been executed".to_string()) } @@ -364,10 +358,7 @@ impl Test { context.run_jobs(); - match promise - .state() - .expect("tester can only use builtin promises") - { + match promise.state() { PromiseState::Pending => { return (false, "module didn't try to load".to_string()) } @@ -428,10 +419,7 @@ impl Test { context.run_jobs(); - match promise - .state() - .expect("tester can only use builtin promises") - { + match promise.state() { PromiseState::Pending => { return (false, "module didn't try to load".to_string()) } @@ -449,10 +437,7 @@ impl Test { context.run_jobs(); - match promise - .state() - .expect("tester can only use builtin promises") - { + match promise.state() { PromiseState::Pending => { return (false, "module didn't try to evaluate".to_string()) }