diff --git a/Cargo.lock b/Cargo.lock index 098e4a4f2e..15d581e956 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,7 +200,6 @@ dependencies = [ "boa_profiler", "chrono", "criterion", - "dyn-clone", "fast-float", "float-cmp", "icu_calendar", @@ -993,12 +992,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dyn-clone" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" - [[package]] name = "dynasm" version = "1.2.3" diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index 361e21e0f7..16524ca067 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -57,7 +57,6 @@ ryu-js = "0.2.2" chrono = "0.4.23" fast-float = "0.2.0" unicode-normalization = "0.1.22" -dyn-clone = "1.0.10" once_cell = "1.17.0" tap = "1.0.1" sptr = "0.3.2" diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 3f748b2d88..8d48b77419 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -26,9 +26,10 @@ use crate::{ context::intrinsics::StandardConstructors, error::JsNativeError, js_string, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsFunction, JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsFunction, JsObject, ObjectData, }, property::{Attribute, PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, @@ -50,10 +51,11 @@ impl BuiltIn for Array { let symbol_iterator = WellKnownSymbols::iterator(); let symbol_unscopables = WellKnownSymbols::unscopables(); - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); let values_function = context.intrinsics().objects().array_prototype_values(); let unscopables_object = Self::unscopables_intrinsic(context); @@ -2847,7 +2849,7 @@ impl Array { /// Creates an `Array.prototype.values( )` function object. pub(crate) fn create_array_prototype_values(context: &mut Context<'_>) -> JsFunction { - FunctionBuilder::native(context, Self::values) + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::values)) .name("values") .length(0) .constructor(false) diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 53a0cfbce3..69120c8298 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -14,9 +14,10 @@ use crate::{ builtins::{typed_array::TypedArrayKind, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, error::JsNativeError, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsObject, ObjectData, }, property::Attribute, symbol::WellKnownSymbols, @@ -55,14 +56,16 @@ impl BuiltIn for ArrayBuffer { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); - let get_byte_length = FunctionBuilder::native(context, Self::get_byte_length) - .name("get byteLength") - .build(); + let get_byte_length = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_byte_length)) + .name("get byteLength") + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/async_function/mod.rs b/boa_engine/src/builtins/async_function/mod.rs index 3d647496fd..abf7ac04c3 100644 --- a/boa_engine/src/builtins/async_function/mod.rs +++ b/boa_engine/src/builtins/async_function/mod.rs @@ -12,6 +12,7 @@ use crate::{ function::{ConstructorKind, Function}, BuiltIn, }, + native_function::NativeFunction, object::ObjectData, property::PropertyDescriptor, symbol::WellKnownSymbols, @@ -62,7 +63,7 @@ impl BuiltIn for AsyncFunction { .configurable(false); constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().data = ObjectData::function(Function::Native { - function: Self::constructor, + function: NativeFunction::from_fn_ptr(Self::constructor), constructor: Some(ConstructorKind::Base), }); diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs index 270c9ba4f6..b524600eb1 100644 --- a/boa_engine/src/builtins/async_generator/mod.rs +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -11,7 +11,8 @@ use crate::{ promise::if_abrupt_reject_promise, promise::PromiseCapability, BuiltIn, JsArgs, Promise, }, error::JsNativeError, - object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData}, + native_function::NativeFunction, + object::{ConstructorBuilder, FunctionObjectBuilder, JsObject, ObjectData}, property::{Attribute, PropertyDescriptor}, symbol::WellKnownSymbols, value::JsValue, @@ -627,32 +628,34 @@ impl AsyncGenerator { // 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called: // 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). - let on_fulfilled = FunctionBuilder::closure_with_captures( + let on_fulfilled = FunctionObjectBuilder::new( context, - |_this, args, generator, context| { - let mut generator_borrow_mut = generator.borrow_mut(); - let gen = generator_borrow_mut - .as_async_generator_mut() - .expect("already checked before"); + NativeFunction::from_copy_closure_with_captures( + |_this, args, generator, context| { + let mut generator_borrow_mut = generator.borrow_mut(); + let gen = generator_borrow_mut + .as_async_generator_mut() + .expect("already checked before"); - // a. Set generator.[[AsyncGeneratorState]] to completed. - gen.state = AsyncGeneratorState::Completed; + // a. Set generator.[[AsyncGeneratorState]] to completed. + gen.state = AsyncGeneratorState::Completed; - // b. Let result be NormalCompletion(value). - let result = Ok(args.get_or_undefined(0).clone()); + // b. Let result be NormalCompletion(value). + let result = Ok(args.get_or_undefined(0).clone()); - // c. Perform AsyncGeneratorCompleteStep(generator, result, true). - let next = gen.queue.pop_front().expect("must have one entry"); - drop(generator_borrow_mut); - Self::complete_step(&next, result, true, context); + // c. Perform AsyncGeneratorCompleteStep(generator, result, true). + let next = gen.queue.pop_front().expect("must have one entry"); + drop(generator_borrow_mut); + Self::complete_step(&next, result, true, context); - // d. Perform AsyncGeneratorDrainQueue(generator). - Self::drain_queue(generator, context); + // d. Perform AsyncGeneratorDrainQueue(generator). + Self::drain_queue(generator, context); - // e. Return undefined. - Ok(JsValue::undefined()) - }, - generator.clone(), + // e. Return undefined. + Ok(JsValue::undefined()) + }, + generator.clone(), + ), ) .name("") .length(1) @@ -660,32 +663,34 @@ impl AsyncGenerator { // 9. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called: // 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). - let on_rejected = FunctionBuilder::closure_with_captures( + let on_rejected = FunctionObjectBuilder::new( context, - |_this, args, generator, context| { - let mut generator_borrow_mut = generator.borrow_mut(); - let gen = generator_borrow_mut - .as_async_generator_mut() - .expect("already checked before"); + NativeFunction::from_copy_closure_with_captures( + |_this, args, generator, context| { + let mut generator_borrow_mut = generator.borrow_mut(); + let gen = generator_borrow_mut + .as_async_generator_mut() + .expect("already checked before"); - // a. Set generator.[[AsyncGeneratorState]] to completed. - gen.state = AsyncGeneratorState::Completed; + // a. Set generator.[[AsyncGeneratorState]] to completed. + gen.state = AsyncGeneratorState::Completed; - // b. Let result be ThrowCompletion(reason). - let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone())); + // b. Let result be ThrowCompletion(reason). + let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone())); - // c. Perform AsyncGeneratorCompleteStep(generator, result, true). - let next = gen.queue.pop_front().expect("must have one entry"); - drop(generator_borrow_mut); - Self::complete_step(&next, result, true, context); + // c. Perform AsyncGeneratorCompleteStep(generator, result, true). + let next = gen.queue.pop_front().expect("must have one entry"); + drop(generator_borrow_mut); + Self::complete_step(&next, result, true, context); - // d. Perform AsyncGeneratorDrainQueue(generator). - Self::drain_queue(generator, context); + // d. Perform AsyncGeneratorDrainQueue(generator). + Self::drain_queue(generator, context); - // e. Return undefined. - Ok(JsValue::undefined()) - }, - generator, + // e. Return undefined. + Ok(JsValue::undefined()) + }, + generator, + ), ) .name("") .length(1) diff --git a/boa_engine/src/builtins/async_generator_function/mod.rs b/boa_engine/src/builtins/async_generator_function/mod.rs index caee02c0e4..1ac40d3126 100644 --- a/boa_engine/src/builtins/async_generator_function/mod.rs +++ b/boa_engine/src/builtins/async_generator_function/mod.rs @@ -10,6 +10,7 @@ use crate::{ function::{BuiltInFunctionObject, ConstructorKind, Function}, BuiltIn, }, + native_function::NativeFunction, object::ObjectData, property::PropertyDescriptor, symbol::WellKnownSymbols, @@ -67,7 +68,7 @@ impl BuiltIn for AsyncGeneratorFunction { .configurable(false); constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().data = ObjectData::function(Function::Native { - function: Self::constructor, + function: NativeFunction::from_fn_ptr(Self::constructor), constructor: Some(ConstructorKind::Base), }); diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index ab90791f33..33a0b1afff 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -11,9 +11,10 @@ use crate::{ builtins::{array_buffer::SharedMemoryOrder, typed_array::TypedArrayKind, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, error::JsNativeError, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsObject, ObjectData, }, property::Attribute, symbol::WellKnownSymbols, @@ -37,17 +38,20 @@ impl BuiltIn for DataView { fn init(context: &mut Context<'_>) -> Option { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_buffer = FunctionBuilder::native(context, Self::get_buffer) - .name("get buffer") - .build(); + let get_buffer = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_buffer)) + .name("get buffer") + .build(); - let get_byte_length = FunctionBuilder::native(context, Self::get_byte_length) - .name("get byteLength") - .build(); + let get_byte_length = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_byte_length)) + .name("get byteLength") + .build(); - let get_byte_offset = FunctionBuilder::native(context, Self::get_byte_offset) - .name("get byteOffset") - .build(); + let get_byte_offset = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_byte_offset)) + .name("get byteOffset") + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 572a57adea..cd14382ce2 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -19,6 +19,7 @@ use crate::{ builtins::{function::Function, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, error::JsNativeError, + native_function::NativeFunction, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, }, @@ -103,7 +104,7 @@ pub(crate) fn create_throw_type_error(context: &mut Context<'_>) -> JsObject { let function = JsObject::from_proto_and_data( context.intrinsics().constructors().function().prototype(), ObjectData::function(Function::Native { - function: throw_type_error, + function: NativeFunction::from_fn_ptr(throw_type_error), constructor: None, }), ); diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 77e544eb25..8a01e2e25b 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -13,7 +13,8 @@ use crate::{ builtins::{BuiltIn, JsArgs}, environments::DeclarativeEnvironment, error::JsNativeError, - object::FunctionBuilder, + native_function::NativeFunction, + object::FunctionObjectBuilder, property::Attribute, Context, JsResult, JsString, JsValue, }; @@ -37,7 +38,7 @@ impl BuiltIn for Eval { fn init(context: &mut Context<'_>) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let object = FunctionBuilder::native(context, Self::eval) + let object = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::eval)) .name("eval") .length(1) .constructor(false) diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 5fadb013ed..562fdee5f4 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -18,11 +18,9 @@ use crate::{ environments::DeclarativeEnvironmentStack, error::JsNativeError, js_string, - object::{ - internal_methods::get_prototype_from_constructor, JsObject, NativeObject, Object, - ObjectData, - }, - object::{ConstructorBuilder, FunctionBuilder, JsFunction, PrivateElement, Ref, RefMut}, + native_function::{NativeFunction, NativeFunctionPointer}, + object::{internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData}, + object::{ConstructorBuilder, FunctionObjectBuilder, JsFunction, PrivateElement}, property::{Attribute, PropertyDescriptor, PropertyKey}, string::utf16, symbol::WellKnownSymbols, @@ -34,72 +32,20 @@ use boa_ast::{ operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, StatementList, }; -use boa_gc::{self, custom_trace, Finalize, Gc, GcCell, Trace}; +use boa_gc::{self, custom_trace, Finalize, Gc, Trace}; use boa_interner::Sym; use boa_parser::Parser; use boa_profiler::Profiler; -use dyn_clone::DynClone; -use std::{ - any::Any, - fmt, - ops::{Deref, DerefMut}, -}; use tap::{Conv, Pipe}; +use std::fmt; + use super::promise::PromiseCapability; pub(crate) mod arguments; #[cfg(test)] mod tests; -/// Type representing a native built-in function a.k.a. function pointer. -/// -/// Native functions need to have this signature in order to -/// be callable from Javascript. -/// -/// # Arguments -/// -/// - The first argument represents the `this` variable of every Javascript function. -/// -/// - The second argument represents a list of all arguments passed to the function. -/// -/// - The last argument is the [`Context`] of the engine. -pub type NativeFunctionSignature = fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult; - -// Allows restricting closures to only `Copy` ones. -// Used the sealed pattern to disallow external implementations -// of `DynCopy`. -mod sealed { - pub trait Sealed {} - impl Sealed for T {} -} - -/// This trait is implemented by any type that implements [`core::marker::Copy`]. -pub trait DynCopy: sealed::Sealed {} - -impl DynCopy for T {} - -/// Trait representing a native built-in closure. -/// -/// Closures need to have this signature in order to -/// be callable from Javascript, but most of the time the compiler -/// is smart enough to correctly infer the types. -pub trait ClosureFunctionSignature: - Fn(&JsValue, &[JsValue], Captures, &mut Context<'_>) -> JsResult - + DynCopy - + DynClone - + 'static -{ -} - -impl ClosureFunctionSignature for T where - T: Fn(&JsValue, &[JsValue], Captures, &mut Context<'_>) -> JsResult + Copy + 'static -{ -} - -// Allows cloning Box -dyn_clone::clone_trait_object!(ClosureFunctionSignature); - /// Represents the `[[ThisMode]]` internal slot of function objects. /// /// More information: @@ -196,47 +142,6 @@ unsafe impl Trace for ClassFieldDefinition { }} } -/// Wrapper for `Gc>` that allows passing additional -/// captures through a `Copy` closure. -/// -/// Any type implementing `Trace + Any + Debug` -/// can be used as a capture context, so you can pass e.g. a String, -/// a tuple or even a full struct. -/// -/// You can cast to `Any` with `as_any`, `as_mut_any` and downcast -/// with `Any::downcast_ref` and `Any::downcast_mut` to recover the original -/// type. -#[derive(Clone, Debug, Trace, Finalize)] -pub struct Captures(Gc>>); - -impl Captures { - /// Creates a new capture context. - pub(crate) fn new(captures: T) -> Self - where - T: NativeObject, - { - Self(Gc::new(GcCell::new(Box::new(captures)))) - } - - /// Casts `Captures` to `Any` - /// - /// # Panics - /// - /// Panics if it's already borrowed as `&mut Any` - pub fn as_any(&self) -> boa_gc::GcCellRef<'_, dyn Any> { - Ref::map(self.0.borrow(), |data| data.deref().as_any()) - } - - /// Mutably casts `Captures` to `Any` - /// - /// # Panics - /// - /// Panics if it's already borrowed as `&mut Any` - pub fn as_mut_any(&self) -> boa_gc::GcCellRefMut<'_, Box, dyn Any> { - RefMut::map(self.0.borrow_mut(), |data| data.deref_mut().as_mut_any()) - } -} - /// Boa representation of a Function Object. /// /// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code @@ -248,24 +153,11 @@ pub enum Function { /// A rust function. Native { /// The rust function. - function: NativeFunctionSignature, + function: NativeFunction, /// The kind of the function constructor if it is a constructor. constructor: Option, }, - - /// A rust function that may contain captured values. - Closure { - /// The rust function. - function: Box, - - /// The kind of the function constructor if it is a constructor. - constructor: Option, - - /// The captured values. - captures: Captures, - }, - /// A bytecode function. Ordinary { /// The code block containing the compiled function. @@ -330,8 +222,7 @@ pub enum Function { unsafe impl Trace for Function { custom_trace! {this, { match this { - Self::Native { .. } => {} - Self::Closure { captures, .. } => mark(captures), + Self::Native { function, .. } => {mark(function)} Self::Ordinary { code, environments, home_object, fields, private_methods, .. } => { mark(code); mark(environments); @@ -367,9 +258,7 @@ impl Function { /// Returns true if the function object is a constructor. pub fn is_constructor(&self) -> bool { match self { - Self::Native { constructor, .. } | Self::Closure { constructor, .. } => { - constructor.is_some() - } + Self::Native { constructor, .. } => constructor.is_some(), Self::Generator { .. } | Self::AsyncGenerator { .. } | Self::Async { .. } => false, Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical), } @@ -394,7 +283,7 @@ impl Function { | Self::Async { home_object, .. } | Self::Generator { home_object, .. } | Self::AsyncGenerator { home_object, .. } => home_object.as_ref(), - _ => None, + Self::Native { .. } => None, } } @@ -405,7 +294,7 @@ impl Function { | Self::Async { home_object, .. } | Self::Generator { home_object, .. } | Self::AsyncGenerator { home_object, .. } => *home_object = Some(object), - _ => {} + Self::Native { .. } => {} } } @@ -487,7 +376,7 @@ impl Function { /// If no length is provided, the length will be set to 0. // TODO: deprecate/remove this. pub(crate) fn make_builtin_fn( - function: NativeFunctionSignature, + function: NativeFunctionPointer, name: N, parent: &JsObject, length: usize, @@ -505,7 +394,7 @@ pub(crate) fn make_builtin_fn( .function() .prototype(), ObjectData::function(Function::Native { - function, + function: NativeFunction::from_fn_ptr(function), constructor: None, }), ); @@ -905,7 +794,7 @@ impl BuiltInFunctionObject { .unwrap_or_else(|| "anonymous".into()); match function { - Function::Native { .. } | Function::Closure { .. } | Function::Ordinary { .. } => { + Function::Native { .. } | Function::Ordinary { .. } => { Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into()) } Function::Async { .. } => { @@ -949,7 +838,7 @@ impl BuiltIn for BuiltInFunctionObject { let _timer = Profiler::global().start_event("function", "init"); let function_prototype = context.intrinsics().constructors().function().prototype(); - FunctionBuilder::native(context, Self::prototype) + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::prototype)) .name("") .length(0) .constructor(false) @@ -957,11 +846,12 @@ impl BuiltIn for BuiltInFunctionObject { let symbol_has_instance = WellKnownSymbols::has_instance(); - let has_instance = FunctionBuilder::native(context, Self::has_instance) - .name("[Symbol.iterator]") - .length(1) - .constructor(false) - .build(); + let has_instance = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::has_instance)) + .name("[Symbol.iterator]") + .length(1) + .constructor(false) + .build(); let throw_type_error = context.intrinsics().objects().throw_type_error(); diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index 6518d6e7ef..76f786d50e 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -1,7 +1,8 @@ use crate::{ error::JsNativeError, forward, forward_val, js_string, - object::{FunctionBuilder, JsObject}, + native_function::NativeFunction, + object::{FunctionObjectBuilder, JsObject}, property::{Attribute, PropertyDescriptor}, string::utf16, Context, JsNativeErrorKind, @@ -244,22 +245,26 @@ fn closure_capture_clone() { ) .unwrap(); - let func = FunctionBuilder::closure_with_captures( + let func = FunctionObjectBuilder::new( &mut context, - |_, _, captures, context| { - let (string, object) = &captures; + NativeFunction::from_copy_closure_with_captures( + |_, _, captures, context| { + let (string, object) = &captures; - let hw = js_string!( - string, - &object - .__get_own_property__(&"key".into(), context)? - .and_then(|prop| prop.value().cloned()) - .and_then(|val| val.as_string().cloned()) - .ok_or_else(|| JsNativeError::typ().with_message("invalid `key` property"))? - ); - Ok(hw.into()) - }, - (string, object), + let hw = js_string!( + string, + &object + .__get_own_property__(&"key".into(), context)? + .and_then(|prop| prop.value().cloned()) + .and_then(|val| val.as_string().cloned()) + .ok_or_else( + || JsNativeError::typ().with_message("invalid `key` property") + )? + ); + Ok(hw.into()) + }, + (string, object), + ), ) .name("closure") .build(); diff --git a/boa_engine/src/builtins/generator_function/mod.rs b/boa_engine/src/builtins/generator_function/mod.rs index 60571e8179..845538b5d0 100644 --- a/boa_engine/src/builtins/generator_function/mod.rs +++ b/boa_engine/src/builtins/generator_function/mod.rs @@ -15,6 +15,7 @@ use crate::{ function::{BuiltInFunctionObject, ConstructorKind, Function}, BuiltIn, }, + native_function::NativeFunction, object::ObjectData, property::PropertyDescriptor, symbol::WellKnownSymbols, @@ -72,7 +73,7 @@ impl BuiltIn for GeneratorFunction { .configurable(false); constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().data = ObjectData::function(Function::Native { - function: Self::constructor, + function: NativeFunction::from_fn_ptr(Self::constructor), constructor: Some(ConstructorKind::Base), }); diff --git a/boa_engine/src/builtins/intl/collator/mod.rs b/boa_engine/src/builtins/intl/collator/mod.rs index 77fb974c05..2bd3f30d63 100644 --- a/boa_engine/src/builtins/intl/collator/mod.rs +++ b/boa_engine/src/builtins/intl/collator/mod.rs @@ -14,9 +14,10 @@ use tap::{Conv, Pipe}; use crate::{ builtins::{BuiltIn, JsArgs}, context::{intrinsics::StandardConstructors, BoaProvider}, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsFunction, JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsFunction, JsObject, ObjectData, }, property::Attribute, symbol::WellKnownSymbols, @@ -160,10 +161,11 @@ impl BuiltIn for Collator { fn init(context: &mut Context<'_>) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let compare = FunctionBuilder::native(context, Self::compare) - .name("get compare") - .constructor(false) - .build(); + let compare = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::compare)) + .name("get compare") + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, @@ -415,32 +417,34 @@ impl Collator { let bound_compare = if let Some(f) = collator.bound_compare.clone() { f } else { - let bound_compare = FunctionBuilder::closure_with_captures( + let bound_compare = FunctionObjectBuilder::new( context, // 10.3.3.1. Collator Compare Functions // https://tc39.es/ecma402/#sec-collator-compare-functions - |_, args, collator, context| { - // 1. Let collator be F.[[Collator]]. - // 2. Assert: Type(collator) is Object and collator has an [[InitializedCollator]] internal slot. - let collator = collator.borrow(); - let collator = collator - .as_collator() - .expect("checked above that the object was a collator object"); - - // 3. If x is not provided, let x be undefined. - // 5. Let X be ? ToString(x). - let x = args.get_or_undefined(0).to_string(context)?; - - // 4. If y is not provided, let y be undefined. - // 6. Let Y be ? ToString(y). - let y = args.get_or_undefined(1).to_string(context)?; - - // 7. Return CompareStrings(collator, X, Y). - let result = collator.collator.compare_utf16(&x, &y) as i32; - - Ok(result.into()) - }, - collator_obj, + NativeFunction::from_copy_closure_with_captures( + |_, args, collator, context| { + // 1. Let collator be F.[[Collator]]. + // 2. Assert: Type(collator) is Object and collator has an [[InitializedCollator]] internal slot. + let collator = collator.borrow(); + let collator = collator + .as_collator() + .expect("checked above that the object was a collator object"); + + // 3. If x is not provided, let x be undefined. + // 5. Let X be ? ToString(x). + let x = args.get_or_undefined(0).to_string(context)?; + + // 4. If y is not provided, let y be undefined. + // 6. Let Y be ? ToString(y). + let y = args.get_or_undefined(1).to_string(context)?; + + // 7. Return CompareStrings(collator, X, Y). + let result = collator.collator.compare_utf16(&x, &y) as i32; + + Ok(result.into()) + }, + collator_obj, + ), ) .length(2) .build(); diff --git a/boa_engine/src/builtins/intl/locale/mod.rs b/boa_engine/src/builtins/intl/locale/mod.rs index bdd0f2dad1..e87b3203f0 100644 --- a/boa_engine/src/builtins/intl/locale/mod.rs +++ b/boa_engine/src/builtins/intl/locale/mod.rs @@ -20,9 +20,10 @@ use crate::{ builtins::{BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, js_string, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsObject, ObjectData, }, property::Attribute, symbol::WellKnownSymbols, @@ -40,52 +41,62 @@ impl BuiltIn for Locale { fn init(context: &mut Context<'_>) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let base_name = FunctionBuilder::native(context, Self::base_name) - .name("get baseName") - .constructor(false) - .build(); - - let calendar = FunctionBuilder::native(context, Self::calendar) - .name("get calendar") - .constructor(false) - .build(); - - let case_first = FunctionBuilder::native(context, Self::case_first) - .name("get caseFirst") - .constructor(false) - .build(); - - let collation = FunctionBuilder::native(context, Self::collation) - .name("get collation") - .constructor(false) - .build(); - - let hour_cycle = FunctionBuilder::native(context, Self::hour_cycle) - .name("get hourCycle") - .constructor(false) - .build(); - - let numeric = FunctionBuilder::native(context, Self::numeric) - .name("get numeric") - .constructor(false) - .build(); - - let numbering_system = FunctionBuilder::native(context, Self::numbering_system) - .name("get numberingSystem") - .constructor(false) - .build(); + let base_name = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::base_name)) + .name("get baseName") + .constructor(false) + .build(); + + let calendar = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::calendar)) + .name("get calendar") + .constructor(false) + .build(); + + let case_first = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::case_first)) + .name("get caseFirst") + .constructor(false) + .build(); + + let collation = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::collation)) + .name("get collation") + .constructor(false) + .build(); + + let hour_cycle = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::hour_cycle)) + .name("get hourCycle") + .constructor(false) + .build(); + + let numeric = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::numeric)) + .name("get numeric") + .constructor(false) + .build(); + + let numbering_system = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(Self::numbering_system), + ) + .name("get numberingSystem") + .constructor(false) + .build(); - let language = FunctionBuilder::native(context, Self::language) - .name("get language") - .constructor(false) - .build(); + let language = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::language)) + .name("get language") + .constructor(false) + .build(); - let script = FunctionBuilder::native(context, Self::script) + let script = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::script)) .name("get script") .constructor(false) .build(); - let region = FunctionBuilder::native(context, Self::region) + let region = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::region)) .name("get region") .constructor(false) .build(); diff --git a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs index fd3fd49db1..58e79865c8 100644 --- a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -4,7 +4,8 @@ use crate::{ promise::{if_abrupt_reject_promise, PromiseCapability}, JsArgs, Promise, }, - object::{FunctionBuilder, JsObject, ObjectData}, + native_function::NativeFunction, + object::{FunctionObjectBuilder, JsObject, ObjectData}, property::PropertyDescriptor, Context, JsNativeError, JsResult, JsValue, }; @@ -29,18 +30,27 @@ pub(crate) fn create_async_from_sync_iterator_prototype(context: &mut Context<'_ ObjectData::ordinary(), ); - let next_function = FunctionBuilder::native(context, AsyncFromSyncIterator::next) - .name("next") - .length(1) - .build(); - let return_function = FunctionBuilder::native(context, AsyncFromSyncIterator::r#return) - .name("return") - .length(1) - .build(); - let throw_function = FunctionBuilder::native(context, AsyncFromSyncIterator::throw) - .name("throw") - .length(1) - .build(); + let next_function = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(AsyncFromSyncIterator::next), + ) + .name("next") + .length(1) + .build(); + let return_function = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(AsyncFromSyncIterator::r#return), + ) + .name("return") + .length(1) + .build(); + let throw_function = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(AsyncFromSyncIterator::throw), + ) + .name("throw") + .length(1) + .build(); { let mut prototype_mut = prototype.borrow_mut(); @@ -400,17 +410,16 @@ impl AsyncFromSyncIterator { // 8. Let unwrap be a new Abstract Closure with parameters (value) // that captures done and performs the following steps when called: // 9. Let onFulfilled be CreateBuiltinFunction(unwrap, 1, "", « »). - let on_fulfilled = FunctionBuilder::closure_with_captures( + let on_fulfilled = FunctionObjectBuilder::new( context, - |_this, args, done, context| { + NativeFunction::from_copy_closure(move |_this, args, context| { // a. Return CreateIterResultObject(value, done). Ok(create_iter_result_object( args.get_or_undefined(0).clone(), - *done, + done, context, )) - }, - done, + }), ) .name("") .length(1) diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 2e8138924b..1f9e6388e9 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -16,9 +16,10 @@ use crate::{ builtins::BuiltIn, context::intrinsics::StandardConstructors, error::JsNativeError, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsObject, ObjectData, }, property::{Attribute, PropertyNameKind}, symbol::WellKnownSymbols, @@ -42,22 +43,25 @@ impl BuiltIn for Map { fn init(context: &mut Context<'_>) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); - - let get_size = FunctionBuilder::native(context, Self::get_size) - .name("get size") - .length(0) - .constructor(false) - .build(); - - let entries_function = FunctionBuilder::native(context, Self::entries) - .name("entries") - .length(0) - .constructor(false) - .build(); + let get_species = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); + + let get_size = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_size)) + .name("get size") + .length(0) + .constructor(false) + .build(); + + let entries_function = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::entries)) + .name("entries") + .length(0) + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 937e77abe5..39812437f8 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -138,7 +138,7 @@ fn init_builtin(context: &mut Context<'_>) { /// Initializes built-in objects and functions pub fn init(context: &mut Context<'_>) { macro_rules! globals { - ($( $builtin:ty ),*) => { + ($( $builtin:ty ),*$(,)?) => { $(init_builtin::<$builtin>(context) );* } @@ -195,7 +195,7 @@ pub fn init(context: &mut Context<'_>) { AsyncGenerator, AsyncGeneratorFunction, Uri, - WeakRef + WeakRef, }; #[cfg(feature = "intl")] diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index fe367437b1..28ef64007f 100644 --- a/boa_engine/src/builtins/number/mod.rs +++ b/boa_engine/src/builtins/number/mod.rs @@ -17,9 +17,10 @@ use crate::{ builtins::{string::is_trimmable_whitespace, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, error::JsNativeError, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsObject, ObjectData, }, property::Attribute, string::utf16, @@ -50,17 +51,19 @@ impl BuiltIn for Number { fn init(context: &mut Context<'_>) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let parse_int = FunctionBuilder::native(context, Self::parse_int) - .name("parseInt") - .length(2) - .constructor(false) - .build(); + let parse_int = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::parse_int)) + .name("parseInt") + .length(2) + .constructor(false) + .build(); - let parse_float = FunctionBuilder::native(context, Self::parse_float) - .name("parseFloat") - .length(1) - .constructor(false) - .build(); + let parse_float = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::parse_float)) + .name("parseFloat") + .length(1) + .constructor(false) + .build(); context.register_global_property( "parseInt", @@ -73,8 +76,16 @@ impl BuiltIn for Number { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); - context.register_global_builtin_function("isFinite", 1, Self::global_is_finite); - context.register_global_builtin_function("isNaN", 1, Self::global_is_nan); + context.register_global_builtin_callable( + "isFinite", + 1, + NativeFunction::from_fn_ptr(Self::global_is_finite), + ); + context.register_global_builtin_callable( + "isNaN", + 1, + NativeFunction::from_fn_ptr(Self::global_is_nan), + ); let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 85ed1de401..d98ee1300e 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -21,9 +21,10 @@ use crate::{ context::intrinsics::StandardConstructors, error::JsNativeError, js_string, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - IntegrityLevel, JsObject, ObjectData, ObjectKind, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, IntegrityLevel, JsObject, ObjectData, ObjectKind, }, property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, string::utf16, @@ -48,13 +49,19 @@ impl BuiltIn for Object { fn init(context: &mut Context<'_>) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let legacy_proto_getter = FunctionBuilder::native(context, Self::legacy_proto_getter) - .name("get __proto__") - .build(); + let legacy_proto_getter = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(Self::legacy_proto_getter), + ) + .name("get __proto__") + .build(); - let legacy_setter_proto = FunctionBuilder::native(context, Self::legacy_proto_setter) - .name("set __proto__") - .build(); + let legacy_setter_proto = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(Self::legacy_proto_setter), + ) + .name("set __proto__") + .build(); ConstructorBuilder::with_standard_constructor( context, @@ -1268,22 +1275,24 @@ impl Object { // 4. Let closure be a new Abstract Closure with parameters (key, value) that captures // obj and performs the following steps when called: - let closure = FunctionBuilder::closure_with_captures( + let closure = FunctionObjectBuilder::new( context, - |_, args, obj, context| { - let key = args.get_or_undefined(0); - let value = args.get_or_undefined(1); + NativeFunction::from_copy_closure_with_captures( + |_, args, obj, context| { + let key = args.get_or_undefined(0); + let value = args.get_or_undefined(1); - // a. Let propertyKey be ? ToPropertyKey(key). - let property_key = key.to_property_key(context)?; + // a. Let propertyKey be ? ToPropertyKey(key). + let property_key = key.to_property_key(context)?; - // b. Perform ! CreateDataPropertyOrThrow(obj, propertyKey, value). - obj.create_data_property_or_throw(property_key, value.clone(), context)?; + // b. Perform ! CreateDataPropertyOrThrow(obj, propertyKey, value). + obj.create_data_property_or_throw(property_key, value.clone(), context)?; - // c. Return undefined. - Ok(JsValue::undefined()) - }, - obj.clone(), + // c. Return undefined. + Ok(JsValue::undefined()) + }, + obj.clone(), + ), ); // 5. Let adder be ! CreateBuiltinFunction(closure, 2, "", « »). diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index f92f42a194..7c678cb92f 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -12,9 +12,10 @@ use crate::{ context::intrinsics::StandardConstructors, error::JsNativeError, job::JobCallback, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsFunction, JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsFunction, JsObject, ObjectData, }, property::{Attribute, PropertyDescriptorBuilder}, symbol::WellKnownSymbols, @@ -152,37 +153,39 @@ impl PromiseCapability { // 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called: // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). - let executor = FunctionBuilder::closure_with_captures( + let executor = FunctionObjectBuilder::new( context, - |_this, args: &[JsValue], captures, _| { - let mut promise_capability = captures.borrow_mut(); - // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception. - if !promise_capability.resolve.is_undefined() { - return Err(JsNativeError::typ() - .with_message("promiseCapability.[[Resolve]] is not undefined") - .into()); - } - - // b. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception. - if !promise_capability.reject.is_undefined() { - return Err(JsNativeError::typ() - .with_message("promiseCapability.[[Reject]] is not undefined") - .into()); - } - - let resolve = args.get_or_undefined(0); - let reject = args.get_or_undefined(1); - - // c. Set promiseCapability.[[Resolve]] to resolve. - promise_capability.resolve = resolve.clone(); - - // d. Set promiseCapability.[[Reject]] to reject. - promise_capability.reject = reject.clone(); - - // e. Return undefined. - Ok(JsValue::Undefined) - }, - promise_capability.clone(), + NativeFunction::from_copy_closure_with_captures( + |_this, args: &[JsValue], captures, _| { + let mut promise_capability = captures.borrow_mut(); + // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception. + if !promise_capability.resolve.is_undefined() { + return Err(JsNativeError::typ() + .with_message("promiseCapability.[[Resolve]] is not undefined") + .into()); + } + + // b. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception. + if !promise_capability.reject.is_undefined() { + return Err(JsNativeError::typ() + .with_message("promiseCapability.[[Reject]] is not undefined") + .into()); + } + + let resolve = args.get_or_undefined(0); + let reject = args.get_or_undefined(1); + + // c. Set promiseCapability.[[Resolve]] to resolve. + promise_capability.resolve = resolve.clone(); + + // d. Set promiseCapability.[[Reject]] to reject. + promise_capability.reject = reject.clone(); + + // e. Return undefined. + Ok(JsValue::Undefined) + }, + promise_capability.clone(), + ), ) .name("") .length(2) @@ -254,10 +257,11 @@ impl BuiltIn for Promise { fn init(context: &mut Context<'_>) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, @@ -542,59 +546,62 @@ impl Promise { // o. Set onFulfilled.[[Values]] to values. // p. Set onFulfilled.[[Capability]] to resultCapability. // q. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. - let on_fulfilled = FunctionBuilder::closure_with_captures( + let on_fulfilled = FunctionObjectBuilder::new( context, - |_, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions - - // 1. Let F be the active function object. - // 2. If F.[[AlreadyCalled]] is true, return undefined. - if captures.already_called.get() { - return Ok(JsValue::undefined()); - } + NativeFunction::from_copy_closure_with_captures( + |_, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions + + // 1. Let F be the active function object. + // 2. If F.[[AlreadyCalled]] is true, return undefined. + if captures.already_called.get() { + return Ok(JsValue::undefined()); + } - // 3. Set F.[[AlreadyCalled]] to true. - captures.already_called.set(true); + // 3. Set F.[[AlreadyCalled]] to true. + captures.already_called.set(true); - // 4. Let index be F.[[Index]]. - // 5. Let values be F.[[Values]]. - // 6. Let promiseCapability be F.[[Capability]]. - // 7. Let remainingElementsCount be F.[[RemainingElements]]. + // 4. Let index be F.[[Index]]. + // 5. Let values be F.[[Values]]. + // 6. Let promiseCapability be F.[[Capability]]. + // 7. Let remainingElementsCount be F.[[RemainingElements]]. - // 8. Set values[index] to x. - captures.values.borrow_mut()[captures.index] = args.get_or_undefined(0).clone(); + // 8. Set values[index] to x. + captures.values.borrow_mut()[captures.index] = + args.get_or_undefined(0).clone(); - // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - captures - .remaining_elements_count - .set(captures.remaining_elements_count.get() - 1); + // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + captures + .remaining_elements_count + .set(captures.remaining_elements_count.get() - 1); - // 10. If remainingElementsCount.[[Value]] is 0, then - if captures.remaining_elements_count.get() == 0 { - // a. Let valuesArray be CreateArrayFromList(values). - let values_array = crate::builtins::Array::create_array_from_list( - captures.values.borrow().as_slice().iter().cloned(), - context, - ); + // 10. If remainingElementsCount.[[Value]] is 0, then + if captures.remaining_elements_count.get() == 0 { + // a. Let valuesArray be CreateArrayFromList(values). + let values_array = crate::builtins::Array::create_array_from_list( + captures.values.borrow().as_slice().iter().cloned(), + context, + ); - // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). - return captures.capability_resolve.call( - &JsValue::undefined(), - &[values_array.into()], - context, - ); - } + // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). + return captures.capability_resolve.call( + &JsValue::undefined(), + &[values_array.into()], + context, + ); + } - // 11. Return undefined. - Ok(JsValue::undefined()) - }, - ResolveElementCaptures { - already_called: Rc::new(Cell::new(false)), - index, - values: values.clone(), - capability_resolve: result_capability.resolve.clone(), - remaining_elements_count: remaining_elements_count.clone(), - }, + // 11. Return undefined. + Ok(JsValue::undefined()) + }, + ResolveElementCaptures { + already_called: Rc::new(Cell::new(false)), + index, + values: values.clone(), + capability_resolve: result_capability.resolve.clone(), + remaining_elements_count: remaining_elements_count.clone(), + }, + ), ) .name("") .length(1) @@ -784,76 +791,78 @@ impl Promise { // p. Set onFulfilled.[[Values]] to values. // q. Set onFulfilled.[[Capability]] to resultCapability. // r. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. - let on_fulfilled = FunctionBuilder::closure_with_captures( + let on_fulfilled = FunctionObjectBuilder::new( context, - |_, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions + NativeFunction::from_copy_closure_with_captures( + |_, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions - // 1. Let F be the active function object. - // 2. Let alreadyCalled be F.[[AlreadyCalled]]. + // 1. Let F be the active function object. + // 2. Let alreadyCalled be F.[[AlreadyCalled]]. - // 3. If alreadyCalled.[[Value]] is true, return undefined. - if captures.already_called.get() { - return Ok(JsValue::undefined()); - } + // 3. If alreadyCalled.[[Value]] is true, return undefined. + if captures.already_called.get() { + return Ok(JsValue::undefined()); + } - // 4. Set alreadyCalled.[[Value]] to true. - captures.already_called.set(true); + // 4. Set alreadyCalled.[[Value]] to true. + captures.already_called.set(true); - // 5. Let index be F.[[Index]]. - // 6. Let values be F.[[Values]]. - // 7. Let promiseCapability be F.[[Capability]]. - // 8. Let remainingElementsCount be F.[[RemainingElements]]. + // 5. Let index be F.[[Index]]. + // 6. Let values be F.[[Values]]. + // 7. Let promiseCapability be F.[[Capability]]. + // 8. Let remainingElementsCount be F.[[RemainingElements]]. - // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = JsObject::with_object_proto(context); + // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). + let obj = JsObject::with_object_proto(context); - // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled"). - obj.create_data_property_or_throw("status", "fulfilled", context) - .expect("cannot fail per spec"); + // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled"). + obj.create_data_property_or_throw("status", "fulfilled", context) + .expect("cannot fail per spec"); - // 11. Perform ! CreateDataPropertyOrThrow(obj, "value", x). - obj.create_data_property_or_throw( - "value", - args.get_or_undefined(0).clone(), - context, - ) - .expect("cannot fail per spec"); - - // 12. Set values[index] to obj. - captures.values.borrow_mut()[captures.index] = obj.into(); - - // 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - captures - .remaining_elements - .set(captures.remaining_elements.get() - 1); - - // 14. If remainingElementsCount.[[Value]] is 0, then - if captures.remaining_elements.get() == 0 { - // a. Let valuesArray be CreateArrayFromList(values). - let values_array = Array::create_array_from_list( - captures.values.borrow().as_slice().iter().cloned(), + // 11. Perform ! CreateDataPropertyOrThrow(obj, "value", x). + obj.create_data_property_or_throw( + "value", + args.get_or_undefined(0).clone(), context, - ); + ) + .expect("cannot fail per spec"); - // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). - return captures.capability.call( - &JsValue::undefined(), - &[values_array.into()], - context, - ); - } + // 12. Set values[index] to obj. + captures.values.borrow_mut()[captures.index] = obj.into(); - // 15. Return undefined. - Ok(JsValue::undefined()) - }, - ResolveRejectElementCaptures { - already_called: Rc::new(Cell::new(false)), - index, - values: values.clone(), - capability: result_capability.resolve.clone(), - remaining_elements: remaining_elements_count.clone(), - }, + // 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + captures + .remaining_elements + .set(captures.remaining_elements.get() - 1); + + // 14. If remainingElementsCount.[[Value]] is 0, then + if captures.remaining_elements.get() == 0 { + // a. Let valuesArray be CreateArrayFromList(values). + let values_array = Array::create_array_from_list( + captures.values.borrow().as_slice().iter().cloned(), + context, + ); + + // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). + return captures.capability.call( + &JsValue::undefined(), + &[values_array.into()], + context, + ); + } + + // 15. Return undefined. + Ok(JsValue::undefined()) + }, + ResolveRejectElementCaptures { + already_called: Rc::new(Cell::new(false)), + index, + values: values.clone(), + capability: result_capability.resolve.clone(), + remaining_elements: remaining_elements_count.clone(), + }, + ), ) .name("") .length(1) @@ -868,76 +877,78 @@ impl Promise { // x. Set onRejected.[[Values]] to values. // y. Set onRejected.[[Capability]] to resultCapability. // z. Set onRejected.[[RemainingElements]] to remainingElementsCount. - let on_rejected = FunctionBuilder::closure_with_captures( + let on_rejected = FunctionObjectBuilder::new( context, - |_, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions + NativeFunction::from_copy_closure_with_captures( + |_, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions - // 1. Let F be the active function object. - // 2. Let alreadyCalled be F.[[AlreadyCalled]]. + // 1. Let F be the active function object. + // 2. Let alreadyCalled be F.[[AlreadyCalled]]. - // 3. If alreadyCalled.[[Value]] is true, return undefined. - if captures.already_called.get() { - return Ok(JsValue::undefined()); - } + // 3. If alreadyCalled.[[Value]] is true, return undefined. + if captures.already_called.get() { + return Ok(JsValue::undefined()); + } - // 4. Set alreadyCalled.[[Value]] to true. - captures.already_called.set(true); + // 4. Set alreadyCalled.[[Value]] to true. + captures.already_called.set(true); - // 5. Let index be F.[[Index]]. - // 6. Let values be F.[[Values]]. - // 7. Let promiseCapability be F.[[Capability]]. - // 8. Let remainingElementsCount be F.[[RemainingElements]]. + // 5. Let index be F.[[Index]]. + // 6. Let values be F.[[Values]]. + // 7. Let promiseCapability be F.[[Capability]]. + // 8. Let remainingElementsCount be F.[[RemainingElements]]. - // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = JsObject::with_object_proto(context); + // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). + let obj = JsObject::with_object_proto(context); - // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected"). - obj.create_data_property_or_throw("status", "rejected", context) - .expect("cannot fail per spec"); + // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected"). + obj.create_data_property_or_throw("status", "rejected", context) + .expect("cannot fail per spec"); - // 11. Perform ! CreateDataPropertyOrThrow(obj, "reason", x). - obj.create_data_property_or_throw( - "reason", - args.get_or_undefined(0).clone(), - context, - ) - .expect("cannot fail per spec"); - - // 12. Set values[index] to obj. - captures.values.borrow_mut()[captures.index] = obj.into(); - - // 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - captures - .remaining_elements - .set(captures.remaining_elements.get() - 1); - - // 14. If remainingElementsCount.[[Value]] is 0, then - if captures.remaining_elements.get() == 0 { - // a. Let valuesArray be CreateArrayFromList(values). - let values_array = Array::create_array_from_list( - captures.values.borrow().as_slice().iter().cloned(), + // 11. Perform ! CreateDataPropertyOrThrow(obj, "reason", x). + obj.create_data_property_or_throw( + "reason", + args.get_or_undefined(0).clone(), context, - ); + ) + .expect("cannot fail per spec"); - // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). - return captures.capability.call( - &JsValue::undefined(), - &[values_array.into()], - context, - ); - } + // 12. Set values[index] to obj. + captures.values.borrow_mut()[captures.index] = obj.into(); - // 15. Return undefined. - Ok(JsValue::undefined()) - }, - ResolveRejectElementCaptures { - already_called: Rc::new(Cell::new(false)), - index, - values: values.clone(), - capability: result_capability.resolve.clone(), - remaining_elements: remaining_elements_count.clone(), - }, + // 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + captures + .remaining_elements + .set(captures.remaining_elements.get() - 1); + + // 14. If remainingElementsCount.[[Value]] is 0, then + if captures.remaining_elements.get() == 0 { + // a. Let valuesArray be CreateArrayFromList(values). + let values_array = Array::create_array_from_list( + captures.values.borrow().as_slice().iter().cloned(), + context, + ); + + // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). + return captures.capability.call( + &JsValue::undefined(), + &[values_array.into()], + context, + ); + } + + // 15. Return undefined. + Ok(JsValue::undefined()) + }, + ResolveRejectElementCaptures { + already_called: Rc::new(Cell::new(false)), + index, + values: values.clone(), + capability: result_capability.resolve.clone(), + remaining_elements: remaining_elements_count.clone(), + }, + ), ) .name("") .length(1) @@ -1138,79 +1149,82 @@ impl Promise { // o. Set onRejected.[[Errors]] to errors. // p. Set onRejected.[[Capability]] to resultCapability. // q. Set onRejected.[[RemainingElements]] to remainingElementsCount. - let on_rejected = FunctionBuilder::closure_with_captures( + let on_rejected = FunctionObjectBuilder::new( context, - |_, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise.any-reject-element-functions - - // 1. Let F be the active function object. - - // 2. If F.[[AlreadyCalled]] is true, return undefined. - if captures.already_called.get() { - return Ok(JsValue::undefined()); - } - - // 3. Set F.[[AlreadyCalled]] to true. - captures.already_called.set(true); + NativeFunction::from_copy_closure_with_captures( + |_, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise.any-reject-element-functions - // 4. Let index be F.[[Index]]. - // 5. Let errors be F.[[Errors]]. - // 6. Let promiseCapability be F.[[Capability]]. - // 7. Let remainingElementsCount be F.[[RemainingElements]]. + // 1. Let F be the active function object. - // 8. Set errors[index] to x. - captures.errors.borrow_mut()[captures.index] = args.get_or_undefined(0).clone(); - - // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - captures - .remaining_elements_count - .set(captures.remaining_elements_count.get() - 1); - - // 10. If remainingElementsCount.[[Value]] is 0, then - if captures.remaining_elements_count.get() == 0 { - // a. Let error be a newly created AggregateError object. - let error = JsObject::from_proto_and_data( - context - .intrinsics() - .constructors() - .aggregate_error() - .prototype(), - ObjectData::error(ErrorKind::Aggregate), - ); + // 2. If F.[[AlreadyCalled]] is true, return undefined. + if captures.already_called.get() { + return Ok(JsValue::undefined()); + } - // b. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }). - error - .define_property_or_throw( - "errors", - PropertyDescriptorBuilder::new() - .configurable(true) - .enumerable(false) - .writable(true) - .value(Array::create_array_from_list( - captures.errors.borrow().as_slice().iter().cloned(), - context, - )), + // 3. Set F.[[AlreadyCalled]] to true. + captures.already_called.set(true); + + // 4. Let index be F.[[Index]]. + // 5. Let errors be F.[[Errors]]. + // 6. Let promiseCapability be F.[[Capability]]. + // 7. Let remainingElementsCount be F.[[RemainingElements]]. + + // 8. Set errors[index] to x. + captures.errors.borrow_mut()[captures.index] = + args.get_or_undefined(0).clone(); + + // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + captures + .remaining_elements_count + .set(captures.remaining_elements_count.get() - 1); + + // 10. If remainingElementsCount.[[Value]] is 0, then + if captures.remaining_elements_count.get() == 0 { + // a. Let error be a newly created AggregateError object. + let error = JsObject::from_proto_and_data( + context + .intrinsics() + .constructors() + .aggregate_error() + .prototype(), + ObjectData::error(ErrorKind::Aggregate), + ); + + // b. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }). + error + .define_property_or_throw( + "errors", + PropertyDescriptorBuilder::new() + .configurable(true) + .enumerable(false) + .writable(true) + .value(Array::create_array_from_list( + captures.errors.borrow().as_slice().iter().cloned(), + context, + )), + context, + ) + .expect("cannot fail per spec"); + // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »). + return captures.capability_reject.call( + &JsValue::undefined(), + &[error.into()], context, - ) - .expect("cannot fail per spec"); - // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »). - return captures.capability_reject.call( - &JsValue::undefined(), - &[error.into()], - context, - ); - } + ); + } - // 11. Return undefined. - Ok(JsValue::undefined()) - }, - RejectElementCaptures { - already_called: Rc::new(Cell::new(false)), - index, - errors: errors.clone(), - capability_reject: result_capability.reject.clone(), - remaining_elements_count: remaining_elements_count.clone(), - }, + // 11. Return undefined. + Ok(JsValue::undefined()) + }, + RejectElementCaptures { + already_called: Rc::new(Cell::new(false)), + index, + errors: errors.clone(), + capability_reject: result_capability.reject.clone(), + remaining_elements_count: remaining_elements_count.clone(), + }, + ), ) .name("") .length(1) @@ -1262,49 +1276,50 @@ impl Promise { // 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions. // 3. Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions. // 4. Let resolve be CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »). - let resolve = FunctionBuilder::closure_with_captures( + let resolve = FunctionObjectBuilder::new( context, - |_this, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise-resolve-functions - - // 1. Let F be the active function object. - // 2. Assert: F has a [[Promise]] internal slot whose value is an Object. - // 3. Let promise be F.[[Promise]]. - // 4. Let alreadyResolved be F.[[AlreadyResolved]]. - let RejectResolveCaptures { - promise, - already_resolved, - } = captures; + NativeFunction::from_copy_closure_with_captures( + |_this, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise-resolve-functions - // 5. If alreadyResolved.[[Value]] is true, return undefined. - if already_resolved.get() { - return Ok(JsValue::Undefined); - } + // 1. Let F be the active function object. + // 2. Assert: F has a [[Promise]] internal slot whose value is an Object. + // 3. Let promise be F.[[Promise]]. + // 4. Let alreadyResolved be F.[[AlreadyResolved]]. + let RejectResolveCaptures { + promise, + already_resolved, + } = captures; + + // 5. If alreadyResolved.[[Value]] is true, return undefined. + if already_resolved.get() { + return Ok(JsValue::Undefined); + } - // 6. Set alreadyResolved.[[Value]] to true. - already_resolved.set(true); + // 6. Set alreadyResolved.[[Value]] to true. + already_resolved.set(true); - let resolution = args.get_or_undefined(0); + let resolution = args.get_or_undefined(0); - // 7. If SameValue(resolution, promise) is true, then - if JsValue::same_value(resolution, &promise.clone().into()) { - // a. Let selfResolutionError be a newly created TypeError object. - let self_resolution_error = JsNativeError::typ() - .with_message("SameValue(resolution, promise) is true") - .to_opaque(context); + // 7. If SameValue(resolution, promise) is true, then + if JsValue::same_value(resolution, &promise.clone().into()) { + // a. Let selfResolutionError be a newly created TypeError object. + let self_resolution_error = JsNativeError::typ() + .with_message("SameValue(resolution, promise) is true") + .to_opaque(context); - // b. Perform RejectPromise(promise, selfResolutionError). - promise - .borrow_mut() - .as_promise_mut() - .expect("Expected promise to be a Promise") - .reject_promise(&self_resolution_error.into(), context); + // b. Perform RejectPromise(promise, selfResolutionError). + promise + .borrow_mut() + .as_promise_mut() + .expect("Expected promise to be a Promise") + .reject_promise(&self_resolution_error.into(), context); - // c. Return undefined. - return Ok(JsValue::Undefined); - } + // c. Return undefined. + return Ok(JsValue::Undefined); + } - let Some(then) = resolution.as_object() else { + let Some(then) = resolution.as_object() else { // 8. If Type(resolution) is not Object, then // a. Perform FulfillPromise(promise, resolution). promise @@ -1317,58 +1332,59 @@ impl Promise { return Ok(JsValue::Undefined); }; - // 9. Let then be Completion(Get(resolution, "then")). - let then_action = match then.get("then", context) { - // 10. If then is an abrupt completion, then - Err(e) => { - // a. Perform RejectPromise(promise, then.[[Value]]). - promise - .borrow_mut() - .as_promise_mut() - .expect("Expected promise to be a Promise") - .reject_promise(&e.to_opaque(context), context); - - // b. Return undefined. - return Ok(JsValue::Undefined); - } - // 11. Let thenAction be then.[[Value]]. - Ok(then) => then, - }; - - // 12. If IsCallable(thenAction) is false, then - let then_action = match then_action.as_object() { - Some(then_action) if then_action.is_callable() => then_action, - _ => { - // a. Perform FulfillPromise(promise, resolution). - promise - .borrow_mut() - .as_promise_mut() - .expect("Expected promise to be a Promise") - .fulfill_promise(resolution, context); - - // b. Return undefined. - return Ok(JsValue::Undefined); - } - }; + // 9. Let then be Completion(Get(resolution, "then")). + let then_action = match then.get("then", context) { + // 10. If then is an abrupt completion, then + Err(e) => { + // a. Perform RejectPromise(promise, then.[[Value]]). + promise + .borrow_mut() + .as_promise_mut() + .expect("Expected promise to be a Promise") + .reject_promise(&e.to_opaque(context), context); + + // b. Return undefined. + return Ok(JsValue::Undefined); + } + // 11. Let thenAction be then.[[Value]]. + Ok(then) => then, + }; + + // 12. If IsCallable(thenAction) is false, then + let then_action = match then_action.as_object() { + Some(then_action) if then_action.is_callable() => then_action, + _ => { + // a. Perform FulfillPromise(promise, resolution). + promise + .borrow_mut() + .as_promise_mut() + .expect("Expected promise to be a Promise") + .fulfill_promise(resolution, context); + + // b. Return undefined. + return Ok(JsValue::Undefined); + } + }; - // 13. Let thenJobCallback be HostMakeJobCallback(thenAction). - let then_job_callback = JobCallback::make_job_callback(then_action.clone()); + // 13. Let thenJobCallback be HostMakeJobCallback(thenAction). + let then_job_callback = JobCallback::make_job_callback(then_action.clone()); - // 14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback). - let job: JobCallback = PromiseJob::new_promise_resolve_thenable_job( - promise.clone(), - resolution.clone(), - then_job_callback, - context, - ); + // 14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback). + let job: JobCallback = PromiseJob::new_promise_resolve_thenable_job( + promise.clone(), + resolution.clone(), + then_job_callback, + context, + ); - // 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). - context.host_enqueue_promise_job(job); + // 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). + context.host_enqueue_promise_job(job); - // 16. Return undefined. - Ok(JsValue::Undefined) - }, - resolve_captures, + // 16. Return undefined. + Ok(JsValue::Undefined) + }, + resolve_captures, + ), ) .name("") .length(1) @@ -1385,40 +1401,42 @@ impl Promise { // 7. Let stepsReject be the algorithm steps defined in Promise Reject Functions. // 8. Let lengthReject be the number of non-optional parameters of the function definition in Promise Reject Functions. // 9. Let reject be CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »). - let reject = FunctionBuilder::closure_with_captures( + let reject = FunctionObjectBuilder::new( context, - |_this, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise-reject-functions - - // 1. Let F be the active function object. - // 2. Assert: F has a [[Promise]] internal slot whose value is an Object. - // 3. Let promise be F.[[Promise]]. - // 4. Let alreadyResolved be F.[[AlreadyResolved]]. - let RejectResolveCaptures { - promise, - already_resolved, - } = captures; + NativeFunction::from_copy_closure_with_captures( + |_this, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise-reject-functions - // 5. If alreadyResolved.[[Value]] is true, return undefined. - if already_resolved.get() { - return Ok(JsValue::Undefined); - } + // 1. Let F be the active function object. + // 2. Assert: F has a [[Promise]] internal slot whose value is an Object. + // 3. Let promise be F.[[Promise]]. + // 4. Let alreadyResolved be F.[[AlreadyResolved]]. + let RejectResolveCaptures { + promise, + already_resolved, + } = captures; + + // 5. If alreadyResolved.[[Value]] is true, return undefined. + if already_resolved.get() { + return Ok(JsValue::Undefined); + } - // 6. Set alreadyResolved.[[Value]] to true. - already_resolved.set(true); + // 6. Set alreadyResolved.[[Value]] to true. + already_resolved.set(true); - // let reason = args.get_or_undefined(0); - // 7. Perform RejectPromise(promise, reason). - promise - .borrow_mut() - .as_promise_mut() - .expect("Expected promise to be a Promise") - .reject_promise(args.get_or_undefined(0), context); + // let reason = args.get_or_undefined(0); + // 7. Perform RejectPromise(promise, reason). + promise + .borrow_mut() + .as_promise_mut() + .expect("Expected promise to be a Promise") + .reject_promise(args.get_or_undefined(0), context); - // 8. Return undefined. - Ok(JsValue::Undefined) - }, - reject_captures, + // 8. Return undefined. + Ok(JsValue::Undefined) + }, + reject_captures, + ), ) .name("") .length(1) @@ -1817,94 +1835,104 @@ impl Promise { } // a. Let thenFinallyClosure be a new Abstract Closure with parameters (value) that captures onFinally and C and performs the following steps when called: - let then_finally_closure = FunctionBuilder::closure_with_captures( + let then_finally_closure = FunctionObjectBuilder::new( context, - |_this, args, captures, context| { - /// Capture object for the abstract `returnValue` closure. - #[derive(Debug, Trace, Finalize)] - struct ReturnValueCaptures { - value: JsValue, - } + NativeFunction::from_copy_closure_with_captures( + |_this, args, captures, context| { + /// Capture object for the abstract `returnValue` closure. + #[derive(Debug, Trace, Finalize)] + struct ReturnValueCaptures { + value: JsValue, + } - let value = args.get_or_undefined(0); + let value = args.get_or_undefined(0); - // i. Let result be ? Call(onFinally, undefined). - let result = captures - .on_finally - .call(&JsValue::undefined(), &[], context)?; + // i. Let result be ? Call(onFinally, undefined). + let result = + captures + .on_finally + .call(&JsValue::undefined(), &[], context)?; - // ii. Let promise be ? PromiseResolve(C, result). - let promise = Self::promise_resolve(captures.c.clone(), result, context)?; + // ii. Let promise be ? PromiseResolve(C, result). + let promise = Self::promise_resolve(captures.c.clone(), result, context)?; - // iii. Let returnValue be a new Abstract Closure with no parameters that captures value and performs the following steps when called: - let return_value = FunctionBuilder::closure_with_captures( - context, - |_this, _args, captures, _context| { - // 1. Return value. - Ok(captures.value.clone()) - }, - ReturnValueCaptures { - value: value.clone(), - }, - ); + // iii. Let returnValue be a new Abstract Closure with no parameters that captures value and performs the following steps when called: + let return_value = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + |_this, _args, captures, _context| { + // 1. Return value. + Ok(captures.value.clone()) + }, + ReturnValueCaptures { + value: value.clone(), + }, + ), + ); - // iv. Let valueThunk be CreateBuiltinFunction(returnValue, 0, "", « »). - let value_thunk = return_value.length(0).name("").build(); + // iv. Let valueThunk be CreateBuiltinFunction(returnValue, 0, "", « »). + let value_thunk = return_value.length(0).name("").build(); - // v. Return ? Invoke(promise, "then", « valueThunk »). - promise.invoke("then", &[value_thunk.into()], context) - }, - FinallyCaptures { - on_finally: on_finally.clone(), - c: c.clone(), - }, + // v. Return ? Invoke(promise, "then", « valueThunk »). + promise.invoke("then", &[value_thunk.into()], context) + }, + FinallyCaptures { + on_finally: on_finally.clone(), + c: c.clone(), + }, + ), ); // b. Let thenFinally be CreateBuiltinFunction(thenFinallyClosure, 1, "", « »). let then_finally = then_finally_closure.length(1).name("").build(); // c. Let catchFinallyClosure be a new Abstract Closure with parameters (reason) that captures onFinally and C and performs the following steps when called: - let catch_finally_closure = FunctionBuilder::closure_with_captures( + let catch_finally_closure = FunctionObjectBuilder::new( context, - |_this, args, captures, context| { - /// Capture object for the abstract `throwReason` closure. - #[derive(Debug, Trace, Finalize)] - struct ThrowReasonCaptures { - reason: JsValue, - } + NativeFunction::from_copy_closure_with_captures( + |_this, args, captures, context| { + /// Capture object for the abstract `throwReason` closure. + #[derive(Debug, Trace, Finalize)] + struct ThrowReasonCaptures { + reason: JsValue, + } - let reason = args.get_or_undefined(0); + let reason = args.get_or_undefined(0); - // i. Let result be ? Call(onFinally, undefined). - let result = captures - .on_finally - .call(&JsValue::undefined(), &[], context)?; + // i. Let result be ? Call(onFinally, undefined). + let result = + captures + .on_finally + .call(&JsValue::undefined(), &[], context)?; - // ii. Let promise be ? PromiseResolve(C, result). - let promise = Self::promise_resolve(captures.c.clone(), result, context)?; + // ii. Let promise be ? PromiseResolve(C, result). + let promise = Self::promise_resolve(captures.c.clone(), result, context)?; - // iii. Let throwReason be a new Abstract Closure with no parameters that captures reason and performs the following steps when called: - let throw_reason = FunctionBuilder::closure_with_captures( - context, - |_this, _args, captures, _context| { - // 1. Return ThrowCompletion(reason). - Err(JsError::from_opaque(captures.reason.clone())) - }, - ThrowReasonCaptures { - reason: reason.clone(), - }, - ); + // iii. Let throwReason be a new Abstract Closure with no parameters that captures reason and performs the following steps when called: + let throw_reason = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + |_this, _args, captures, _context| { + // 1. Return ThrowCompletion(reason). + Err(JsError::from_opaque(captures.reason.clone())) + }, + ThrowReasonCaptures { + reason: reason.clone(), + }, + ), + ); - // iv. Let thrower be CreateBuiltinFunction(throwReason, 0, "", « »). - let thrower = throw_reason.length(0).name("").build(); + // iv. Let thrower be CreateBuiltinFunction(throwReason, 0, "", « »). + let thrower = throw_reason.length(0).name("").build(); - // v. Return ? Invoke(promise, "then", « thrower »). - promise.invoke("then", &[thrower.into()], context) - }, - FinallyCaptures { - on_finally: on_finally.clone(), - c, - }, + // v. Return ? Invoke(promise, "then", « thrower »). + promise.invoke("then", &[thrower.into()], context) + }, + FinallyCaptures { + on_finally: on_finally.clone(), + c, + }, + ), ); // d. Let catchFinally be CreateBuiltinFunction(catchFinallyClosure, 1, "", « »). diff --git a/boa_engine/src/builtins/promise/promise_job.rs b/boa_engine/src/builtins/promise/promise_job.rs index 12d9caae07..6288f11d18 100644 --- a/boa_engine/src/builtins/promise/promise_job.rs +++ b/boa_engine/src/builtins/promise/promise_job.rs @@ -2,7 +2,8 @@ use super::{Promise, PromiseCapability}; use crate::{ builtins::promise::{ReactionRecord, ReactionType}, job::JobCallback, - object::{FunctionBuilder, JsObject}, + native_function::NativeFunction, + object::{FunctionObjectBuilder, JsObject}, Context, JsValue, }; use boa_gc::{Finalize, Trace}; @@ -27,75 +28,77 @@ impl PromiseJob { } // 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called: - let job = FunctionBuilder::closure_with_captures( + let job = FunctionObjectBuilder::new( context, - |_this, _args, captures, context| { - let ReactionJobCaptures { reaction, argument } = captures; - - let ReactionRecord { - // a. Let promiseCapability be reaction.[[Capability]]. - promise_capability, - // b. Let type be reaction.[[Type]]. - reaction_type, - // c. Let handler be reaction.[[Handler]]. - handler, - } = reaction; - - let handler_result = match handler { - // d. If handler is empty, then - None => match reaction_type { - // i. If type is Fulfill, let handlerResult be NormalCompletion(argument). - ReactionType::Fulfill => Ok(argument.clone()), - // ii. Else, - // 1. Assert: type is Reject. - ReactionType::Reject => { - // 2. Let handlerResult be ThrowCompletion(argument). - Err(argument.clone()) - } - }, - // e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)). - Some(handler) => handler - .call_job_callback(&JsValue::Undefined, &[argument.clone()], context) - .map_err(|e| e.to_opaque(context)), - }; - - match promise_capability { - None => { - // f. If promiseCapability is undefined, then - // i. Assert: handlerResult is not an abrupt completion. - assert!( - handler_result.is_ok(), - "Assertion: failed" - ); - - // ii. Return empty. - Ok(JsValue::Undefined) - } - Some(promise_capability_record) => { - // g. Assert: promiseCapability is a PromiseCapability Record. - let PromiseCapability { - promise: _, - resolve, - reject, - } = promise_capability_record; - - match handler_result { - // h. If handlerResult is an abrupt completion, then - Err(value) => { - // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). - reject.call(&JsValue::Undefined, &[value], context) + NativeFunction::from_copy_closure_with_captures( + |_this, _args, captures, context| { + let ReactionJobCaptures { reaction, argument } = captures; + + let ReactionRecord { + // a. Let promiseCapability be reaction.[[Capability]]. + promise_capability, + // b. Let type be reaction.[[Type]]. + reaction_type, + // c. Let handler be reaction.[[Handler]]. + handler, + } = reaction; + + let handler_result = match handler { + // d. If handler is empty, then + None => match reaction_type { + // i. If type is Fulfill, let handlerResult be NormalCompletion(argument). + ReactionType::Fulfill => Ok(argument.clone()), + // ii. Else, + // 1. Assert: type is Reject. + ReactionType::Reject => { + // 2. Let handlerResult be ThrowCompletion(argument). + Err(argument.clone()) } - - // i. Else, - Ok(value) => { - // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). - resolve.call(&JsValue::Undefined, &[value], context) + }, + // e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)). + Some(handler) => handler + .call_job_callback(&JsValue::Undefined, &[argument.clone()], context) + .map_err(|e| e.to_opaque(context)), + }; + + match promise_capability { + None => { + // f. If promiseCapability is undefined, then + // i. Assert: handlerResult is not an abrupt completion. + assert!( + handler_result.is_ok(), + "Assertion: failed" + ); + + // ii. Return empty. + Ok(JsValue::Undefined) + } + Some(promise_capability_record) => { + // g. Assert: promiseCapability is a PromiseCapability Record. + let PromiseCapability { + promise: _, + resolve, + reject, + } = promise_capability_record; + + match handler_result { + // h. If handlerResult is an abrupt completion, then + Err(value) => { + // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). + reject.call(&JsValue::Undefined, &[value], context) + } + + // i. Else, + Ok(value) => { + // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). + resolve.call(&JsValue::Undefined, &[value], context) + } } } } - } - }, - ReactionJobCaptures { reaction, argument }, + }, + ReactionJobCaptures { reaction, argument }, + ), ) .build() .into(); @@ -121,42 +124,46 @@ impl PromiseJob { context: &mut Context<'_>, ) -> JobCallback { // 1. Let job be a new Job Abstract Closure with no parameters that captures promiseToResolve, thenable, and then and performs the following steps when called: - let job = FunctionBuilder::closure_with_captures( + let job = FunctionObjectBuilder::new( context, - |_this: &JsValue, _args: &[JsValue], captures, context: &mut Context<'_>| { - let JobCapture { - promise_to_resolve, - thenable, - then, - } = captures; - - // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve). - let resolving_functions = - Promise::create_resolving_functions(promise_to_resolve, context); - - // b. Let thenCallResult be Completion(HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)). - let then_call_result = then.call_job_callback( - thenable, - &[ - resolving_functions.resolve.clone().into(), - resolving_functions.reject.clone().into(), - ], - context, - ); - - // c. If thenCallResult is an abrupt completion, then - if let Err(value) = then_call_result { - let value = value.to_opaque(context); - // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). - return resolving_functions - .reject - .call(&JsValue::Undefined, &[value], context); - } - - // d. Return ? thenCallResult. - then_call_result - }, - JobCapture::new(promise_to_resolve, thenable, then), + NativeFunction::from_copy_closure_with_captures( + |_this: &JsValue, _args: &[JsValue], captures, context: &mut Context<'_>| { + let JobCapture { + promise_to_resolve, + thenable, + then, + } = captures; + + // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve). + let resolving_functions = + Promise::create_resolving_functions(promise_to_resolve, context); + + // b. Let thenCallResult be Completion(HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)). + let then_call_result = then.call_job_callback( + thenable, + &[ + resolving_functions.resolve.clone().into(), + resolving_functions.reject.clone().into(), + ], + context, + ); + + // c. If thenCallResult is an abrupt completion, then + if let Err(value) = then_call_result { + let value = value.to_opaque(context); + // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). + return resolving_functions.reject.call( + &JsValue::Undefined, + &[value], + context, + ); + } + + // d. Return ? thenCallResult. + then_call_result + }, + JobCapture::new(promise_to_resolve, thenable, then), + ), ) .build(); diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index e1a7e67481..0f6729bbde 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -10,10 +10,13 @@ //! [spec]: https://tc39.es/ecma262/#sec-proxy-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy +use std::cell::Cell; + use crate::{ builtins::{BuiltIn, JsArgs}, error::JsNativeError, - object::{ConstructorBuilder, FunctionBuilder, JsFunction, JsObject, ObjectData}, + native_function::NativeFunction, + object::{ConstructorBuilder, FunctionObjectBuilder, JsFunction, JsObject, ObjectData}, Context, JsResult, JsValue, }; use boa_gc::{Finalize, Trace}; @@ -134,27 +137,29 @@ impl Proxy { pub(crate) fn revoker(proxy: JsObject, context: &mut Context<'_>) -> JsFunction { // 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »). // 4. Set revoker.[[RevocableProxy]] to p. - FunctionBuilder::closure_with_captures( + FunctionObjectBuilder::new( context, - |_, _, revocable_proxy, _| { - // a. Let F be the active function object. - // b. Let p be F.[[RevocableProxy]]. - // d. Set F.[[RevocableProxy]] to null. - if let Some(p) = revocable_proxy.take() { - // e. Assert: p is a Proxy object. - // f. Set p.[[ProxyTarget]] to null. - // g. Set p.[[ProxyHandler]] to null. - p.borrow_mut() - .as_proxy_mut() - .expect("[[RevocableProxy]] must be a proxy object") - .data = None; - } - - // c. If p is null, return undefined. - // h. Return undefined. - Ok(JsValue::undefined()) - }, - Some(proxy), + NativeFunction::from_copy_closure_with_captures( + |_, _, revocable_proxy, _| { + // a. Let F be the active function object. + // b. Let p be F.[[RevocableProxy]]. + // d. Set F.[[RevocableProxy]] to null. + if let Some(p) = revocable_proxy.take() { + // e. Assert: p is a Proxy object. + // f. Set p.[[ProxyTarget]] to null. + // g. Set p.[[ProxyHandler]] to null. + p.borrow_mut() + .as_proxy_mut() + .expect("[[RevocableProxy]] must be a proxy object") + .data = None; + } + + // c. If p is null, return undefined. + // h. Return undefined. + Ok(JsValue::undefined()) + }, + Cell::new(Some(proxy)), + ), ) .build() } diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 1fd970f027..68e2eaddf9 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -18,9 +18,10 @@ use crate::{ context::intrinsics::StandardConstructors, error::JsNativeError, js_string, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsObject, ObjectData, }, property::{Attribute, PropertyDescriptorBuilder}, string::{utf16, CodePoint}, @@ -53,49 +54,59 @@ impl BuiltIn for RegExp { fn init(context: &mut Context<'_>) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_has_indices = FunctionBuilder::native(context, Self::get_has_indices) - .name("get hasIndices") - .constructor(false) - .build(); - let get_global = FunctionBuilder::native(context, Self::get_global) - .name("get global") - .constructor(false) - .build(); - let get_ignore_case = FunctionBuilder::native(context, Self::get_ignore_case) - .name("get ignoreCase") - .constructor(false) - .build(); - let get_multiline = FunctionBuilder::native(context, Self::get_multiline) - .name("get multiline") - .constructor(false) - .build(); - let get_dot_all = FunctionBuilder::native(context, Self::get_dot_all) - .name("get dotAll") - .constructor(false) - .build(); - let get_unicode = FunctionBuilder::native(context, Self::get_unicode) - .name("get unicode") - .constructor(false) - .build(); - let get_sticky = FunctionBuilder::native(context, Self::get_sticky) - .name("get sticky") - .constructor(false) - .build(); - let get_flags = FunctionBuilder::native(context, Self::get_flags) - .name("get flags") - .constructor(false) - .build(); - let get_source = FunctionBuilder::native(context, Self::get_source) - .name("get source") - .constructor(false) - .build(); + let get_has_indices = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_has_indices)) + .name("get hasIndices") + .constructor(false) + .build(); + let get_global = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_global)) + .name("get global") + .constructor(false) + .build(); + let get_ignore_case = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_ignore_case)) + .name("get ignoreCase") + .constructor(false) + .build(); + let get_multiline = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_multiline)) + .name("get multiline") + .constructor(false) + .build(); + let get_dot_all = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_dot_all)) + .name("get dotAll") + .constructor(false) + .build(); + let get_unicode = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_unicode)) + .name("get unicode") + .constructor(false) + .build(); + let get_sticky = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_sticky)) + .name("get sticky") + .constructor(false) + .build(); + let get_flags = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_flags)) + .name("get flags") + .constructor(false) + .build(); + let get_source = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_source)) + .name("get source") + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, Self::constructor, diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index d6dfb927f8..228f9bbc87 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -16,9 +16,10 @@ use crate::{ builtins::BuiltIn, context::intrinsics::StandardConstructors, error::JsNativeError, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsObject, ObjectData, }, property::{Attribute, PropertyNameKind}, symbol::WellKnownSymbols, @@ -41,25 +42,28 @@ impl BuiltIn for Set { fn init(context: &mut Context<'_>) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); - let size_getter = FunctionBuilder::native(context, Self::size_getter) - .constructor(false) - .name("get size") - .build(); + let size_getter = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::size_getter)) + .constructor(false) + .name("get size") + .build(); let iterator_symbol = WellKnownSymbols::iterator(); let to_string_tag = WellKnownSymbols::to_string_tag(); - let values_function = FunctionBuilder::native(context, Self::values) - .name("values") - .length(0) - .constructor(false) - .build(); + let values_function = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::values)) + .name("values") + .length(0) + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/symbol/mod.rs b/boa_engine/src/builtins/symbol/mod.rs index 0c8b02475b..6f85fe9e97 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -22,7 +22,8 @@ use super::JsArgs; use crate::{ builtins::BuiltIn, error::JsNativeError, - object::{ConstructorBuilder, FunctionBuilder}, + native_function::NativeFunction, + object::{ConstructorBuilder, FunctionObjectBuilder}, property::Attribute, symbol::{JsSymbol, WellKnownSymbols}, value::JsValue, @@ -96,16 +97,18 @@ impl BuiltIn for Symbol { let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - let to_primitive = FunctionBuilder::native(context, Self::to_primitive) - .name("[Symbol.toPrimitive]") - .length(1) - .constructor(false) - .build(); + let to_primitive = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::to_primitive)) + .name("[Symbol.toPrimitive]") + .length(1) + .constructor(false) + .build(); - let get_description = FunctionBuilder::native(context, Self::get_description) - .name("get description") - .constructor(false) - .build(); + let get_description = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_description)) + .name("get description") + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index d1abe52712..12dce54308 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -22,9 +22,10 @@ use crate::{ context::intrinsics::{StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, + native_function::NativeFunction, object::{ - internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, + FunctionObjectBuilder, JsObject, ObjectData, }, property::{Attribute, PropertyNameKind}, symbol::WellKnownSymbols, @@ -66,10 +67,13 @@ macro_rules! typed_array { .typed_array() .prototype(); - let get_species = FunctionBuilder::native(context, TypedArray::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(TypedArray::get_species), + ) + .name("get [Symbol.species]") + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, @@ -250,41 +254,48 @@ pub(crate) struct TypedArray; impl BuiltIn for TypedArray { const NAME: &'static str = "TypedArray"; fn init(context: &mut Context<'_>) -> Option { - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); - - let get_buffer = FunctionBuilder::native(context, Self::buffer) - .name("get buffer") - .constructor(false) - .build(); - - let get_byte_length = FunctionBuilder::native(context, Self::byte_length) - .name("get byteLength") - .constructor(false) - .build(); - - let get_byte_offset = FunctionBuilder::native(context, Self::byte_offset) - .name("get byteOffset") - .constructor(false) - .build(); - - let get_length = FunctionBuilder::native(context, Self::length) - .name("get length") - .constructor(false) - .build(); - - let get_to_string_tag = FunctionBuilder::native(context, Self::to_string_tag) - .name("get [Symbol.toStringTag]") - .constructor(false) - .build(); - - let values_function = FunctionBuilder::native(context, Self::values) - .name("values") - .length(0) - .constructor(false) - .build(); + let get_species = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); + + let get_buffer = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::buffer)) + .name("get buffer") + .constructor(false) + .build(); + + let get_byte_length = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::byte_length)) + .name("get byteLength") + .constructor(false) + .build(); + + let get_byte_offset = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::byte_offset)) + .name("get byteOffset") + .constructor(false) + .build(); + + let get_length = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::length)) + .name("get length") + .constructor(false) + .build(); + + let get_to_string_tag = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::to_string_tag)) + .name("get [Symbol.toStringTag]") + .constructor(false) + .build(); + + let values_function = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::values)) + .name("values") + .length(0) + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/uri/mod.rs b/boa_engine/src/builtins/uri/mod.rs index 35958bc6bb..427c812d7a 100644 --- a/boa_engine/src/builtins/uri/mod.rs +++ b/boa_engine/src/builtins/uri/mod.rs @@ -19,8 +19,8 @@ use self::consts::{ use super::BuiltIn; use crate::{ - builtins::JsArgs, js_string, object::FunctionBuilder, property::Attribute, string::CodePoint, - Context, JsNativeError, JsResult, JsString, JsValue, + builtins::JsArgs, js_string, native_function::NativeFunction, object::FunctionObjectBuilder, + property::Attribute, string::CodePoint, Context, JsNativeError, JsResult, JsString, JsValue, }; /// URI Handling Functions @@ -31,11 +31,12 @@ impl BuiltIn for Uri { const NAME: &'static str = "Uri"; fn init(context: &mut Context<'_>) -> Option { - let decode_uri = FunctionBuilder::native(context, Self::decode_uri) - .name("decodeURI") - .length(1) - .constructor(false) - .build(); + let decode_uri = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::decode_uri)) + .name("decodeURI") + .length(1) + .constructor(false) + .build(); context.register_global_property( "decodeURI", @@ -43,11 +44,14 @@ impl BuiltIn for Uri { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); - let decode_uri_component = FunctionBuilder::native(context, Self::decode_uri_component) - .name("decodeURIComponent") - .length(1) - .constructor(false) - .build(); + let decode_uri_component = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(Self::decode_uri_component), + ) + .name("decodeURIComponent") + .length(1) + .constructor(false) + .build(); context.register_global_property( "decodeURIComponent", @@ -55,11 +59,12 @@ impl BuiltIn for Uri { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); - let encode_uri = FunctionBuilder::native(context, Self::encode_uri) - .name("encodeURI") - .length(1) - .constructor(false) - .build(); + let encode_uri = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::encode_uri)) + .name("encodeURI") + .length(1) + .constructor(false) + .build(); context.register_global_property( "encodeURI", @@ -67,11 +72,14 @@ impl BuiltIn for Uri { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); - let encode_uri_component = FunctionBuilder::native(context, Self::encode_uri_component) - .name("encodeURIComponent") - .length(1) - .constructor(false) - .build(); + let encode_uri_component = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(Self::encode_uri_component), + ) + .name("encodeURIComponent") + .length(1) + .constructor(false) + .build(); context.register_global_property( "encodeURIComponent", diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index 643a6b444a..5e465c01a3 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -62,8 +62,8 @@ //! [class-trait]: ./trait.Class.html use crate::{ - builtins::function::NativeFunctionSignature, error::JsNativeError, + native_function::NativeFunctionPointer, object::{ConstructorBuilder, JsFunction, JsObject, NativeObject, ObjectData, PROTOTYPE}, property::{Attribute, PropertyDescriptor, PropertyKey}, Context, JsResult, JsValue, @@ -183,7 +183,7 @@ impl<'ctx, 'icu> ClassBuilder<'ctx, 'icu> { &mut self, name: N, length: usize, - function: NativeFunctionSignature, + function: NativeFunctionPointer, ) -> &mut Self where N: AsRef, @@ -199,7 +199,7 @@ impl<'ctx, 'icu> ClassBuilder<'ctx, 'icu> { &mut self, name: N, length: usize, - function: NativeFunctionSignature, + function: NativeFunctionPointer, ) -> &mut Self where N: AsRef, diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index b8a965351d..597d6c7a6b 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -17,11 +17,12 @@ pub use std::marker::PhantomData; #[cfg(feature = "console")] use crate::builtins::console::Console; use crate::{ - builtins::{self, function::NativeFunctionSignature}, + builtins, bytecompiler::ByteCompiler, class::{Class, ClassBuilder}, job::JobCallback, - object::{FunctionBuilder, GlobalPropertyMap, JsObject}, + native_function::NativeFunction, + object::{FunctionObjectBuilder, GlobalPropertyMap, JsObject}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, vm::{CallFrame, CodeBlock, FinallyReturn, GeneratorResumeKind, Vm}, @@ -268,10 +269,7 @@ impl Context<'_> { ); } - /// Register a global native function. - /// - /// This is more efficient that creating a closure function, since this does not allocate, - /// it is just a function pointer. + /// Register a global native callable. /// /// The function will be both `constructable` (call with `new ()`) and `callable` (call /// with `()`). @@ -281,18 +279,10 @@ impl Context<'_> { /// /// # Note /// - /// If you want to make a function only `constructable`, or wish to bind it differently - /// to the global object, you can create the function object with - /// [`FunctionBuilder`](crate::object::FunctionBuilder::native). And bind it to the global - /// object with [`Context::register_global_property`](Context::register_global_property) - /// method. - pub fn register_global_function( - &mut self, - name: &str, - length: usize, - body: NativeFunctionSignature, - ) { - let function = FunctionBuilder::native(self, body) + /// If you wish to only create the function object without binding it to the global object, you + /// can use the [`FunctionObjectBuilder`] API. + pub fn register_global_callable(&mut self, name: &str, length: usize, body: NativeFunction) { + let function = FunctionObjectBuilder::new(self, body) .name(name) .length(length) .constructor(true) @@ -311,24 +301,20 @@ impl Context<'_> { /// Register a global native function that is not a constructor. /// - /// This is more efficient that creating a closure function, since this does not allocate, - /// it is just a function pointer. - /// /// The function will be bound to the global object with `writable`, `non-enumerable` /// and `configurable` attributes. The same as when you create a function in JavaScript. /// /// # Note /// - /// The difference to [`Context::register_global_function`](Context::register_global_function) is, - /// that the function will not be `constructable`. - /// Usage of the function as a constructor will produce a `TypeError`. - pub fn register_global_builtin_function( + /// The difference to [`Context::register_global_callable`] is, that the function will not be + /// `constructable`. Usage of the function as a constructor will produce a `TypeError`. + pub fn register_global_builtin_callable( &mut self, name: &str, length: usize, - body: NativeFunctionSignature, + body: NativeFunction, ) { - let function = FunctionBuilder::native(self, body) + let function = FunctionObjectBuilder::new(self, body) .name(name) .length(length) .constructor(false) @@ -345,51 +331,6 @@ impl Context<'_> { ); } - /// Register a global closure function. - /// - /// The function will be both `constructable` (call with `new`). - /// - /// The function will be bound to the global object with `writable`, `non-enumerable` - /// and `configurable` attributes. The same as when you create a function in JavaScript. - /// - /// # Note #1 - /// - /// If you want to make a function only `constructable`, or wish to bind it differently - /// to the global object, you can create the function object with - /// [`FunctionBuilder`](crate::object::FunctionBuilder::closure). And bind it to the global - /// object with [`Context::register_global_property`](Context::register_global_property) - /// method. - /// - /// # Note #2 - /// - /// This function will only accept `Copy` closures, meaning you cannot - /// move `Clone` types, just `Copy` types. If you need to move `Clone` types - /// as captures, see [`FunctionBuilder::closure_with_captures`]. - /// - /// See for an explanation on - /// why we need to restrict the set of accepted closures. - pub fn register_global_closure(&mut self, name: &str, length: usize, body: F) -> JsResult<()> - where - F: Fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult + Copy + 'static, - { - let function = FunctionBuilder::closure(self, body) - .name(name) - .length(length) - .constructor(true) - .build(); - - self.global_bindings_mut().insert( - name.into(), - PropertyDescriptor::builder() - .value(function) - .writable(true) - .enumerable(false) - .configurable(true) - .build(), - ); - Ok(()) - } - /// Register a global class of type `T`, where `T` implements `Class`. /// /// # Example diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index aea181a5bb..7f2a8604ad 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -115,6 +115,7 @@ pub mod context; pub mod environments; pub mod error; pub mod job; +pub mod native_function; pub mod object; pub mod property; pub mod realm; diff --git a/boa_engine/src/native_function.rs b/boa_engine/src/native_function.rs new file mode 100644 index 0000000000..35dd26bd79 --- /dev/null +++ b/boa_engine/src/native_function.rs @@ -0,0 +1,365 @@ +//! Boa's wrappers for native Rust functions to be compatible with ECMAScript calls. +//! +//! [`NativeFunction`] is the main type of this module, providing APIs to create native callables +//! from native Rust functions and closures. + +use std::marker::PhantomData; + +use boa_gc::{custom_trace, Finalize, Gc, Trace}; + +use crate::{Context, JsResult, JsValue}; + +/// The required signature for all native built-in function pointers. +/// +/// # Arguments +/// +/// - The first argument represents the `this` variable of every ECMAScript function. +/// +/// - The second argument represents the list of all arguments passed to the function. +/// +/// - The last argument is the engine [`Context`]. +pub type NativeFunctionPointer = fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult; + +trait TraceableClosure: Trace { + fn call( + &self, + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult; +} + +#[derive(Trace, Finalize)] +struct Closure +where + F: Fn(&JsValue, &[JsValue], &T, &mut Context<'_>) -> JsResult, + T: Trace, +{ + // SAFETY: `NativeFunction`'s safe API ensures only `Copy` closures are stored; its unsafe API, + // on the other hand, explains the invariants to hold in order for this to be safe, shifting + // the responsibility to the caller. + #[unsafe_ignore_trace] + f: F, + captures: T, +} + +impl TraceableClosure for Closure +where + F: Fn(&JsValue, &[JsValue], &T, &mut Context<'_>) -> JsResult, + T: Trace, +{ + fn call( + &self, + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + (self.f)(this, args, &self.captures, context) + } +} + +/// A callable Rust function that can be invoked by the engine. +/// +/// `NativeFunction` functions are divided in two: +/// - Function pointers a.k.a common functions (see [`NativeFunctionPointer`]). +/// - Closure functions that can capture the current environment. +/// +/// # Caveats +/// +/// By limitations of the Rust language, the garbage collector currently cannot inspect closures +/// in order to trace their captured variables. This means that only [`Copy`] closures are 100% safe +/// to use. All other closures can also be stored in a `NativeFunction`, albeit by using an `unsafe` +/// API, but note that passing closures implicitly capturing traceable types could cause +/// **Undefined Behaviour**. +#[derive(Clone)] +pub struct NativeFunction { + inner: Inner, +} + +#[derive(Clone)] +enum Inner { + PointerFn(NativeFunctionPointer), + Closure(Gc), +} + +impl Finalize for NativeFunction { + fn finalize(&self) { + if let Inner::Closure(c) = &self.inner { + c.finalize(); + } + } +} + +// Manual implementation because deriving `Trace` triggers the `single_use_lifetimes` lint. +// SAFETY: Only closures can contain `Trace` captures, so this implementation is safe. +unsafe impl Trace for NativeFunction { + custom_trace!(this, { + if let Inner::Closure(c) = &this.inner { + mark(c); + } + }); +} + +impl std::fmt::Debug for NativeFunction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NativeFunction").finish_non_exhaustive() + } +} + +impl NativeFunction { + /// Creates a `NativeFunction` from a function pointer. + #[inline] + pub fn from_fn_ptr(function: NativeFunctionPointer) -> Self { + Self { + inner: Inner::PointerFn(function), + } + } + + /// Creates a `NativeFunction` from a `Copy` closure. + pub fn from_copy_closure(closure: F) -> Self + where + F: Fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult + Copy + 'static, + { + // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. + unsafe { Self::from_closure(closure) } + } + + /// Creates a `NativeFunction` from a `Copy` closure and a list of traceable captures. + pub fn from_copy_closure_with_captures(closure: F, captures: T) -> Self + where + F: Fn(&JsValue, &[JsValue], &T, &mut Context<'_>) -> JsResult + Copy + 'static, + T: Trace + 'static, + { + // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. + unsafe { Self::from_closure_with_captures(closure, captures) } + } + + /// Creates a new `NativeFunction` from a closure. + /// + /// # Safety + /// + /// Passing a closure that contains a captured variable that needs to be traced by the garbage + /// collector could cause an use after free, memory corruption or other kinds of **Undefined + /// Behaviour**. See for a technical explanation + /// on why that is the case. + pub unsafe fn from_closure(closure: F) -> Self + where + F: Fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult + 'static, + { + // SAFETY: The caller must ensure the invariants of the closure hold. + unsafe { + Self::from_closure_with_captures( + move |this, args, _, context| closure(this, args, context), + (), + ) + } + } + + /// Create a new `NativeFunction` from a closure and a list of traceable captures. + /// + /// # Safety + /// + /// Passing a closure that contains a captured variable that needs to be traced by the garbage + /// collector could cause an use after free, memory corruption or other kinds of **Undefined + /// Behaviour**. See for a technical explanation + /// on why that is the case. + pub unsafe fn from_closure_with_captures(closure: F, captures: T) -> Self + where + F: Fn(&JsValue, &[JsValue], &T, &mut Context<'_>) -> JsResult + 'static, + T: Trace + 'static, + { + // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the + // future: https://github.com/rust-lang/rust/issues/18598 + let ptr = Gc::into_raw(Gc::new(Closure { + f: closure, + captures, + })); + // SAFETY: The pointer returned by `into_raw` is only used to coerce to a trait object, + // meaning this is safe. + unsafe { + Self { + inner: Inner::Closure(Gc::from_raw(ptr)), + } + } + } + + /// Calls this `NativeFunction`, forwarding the arguments to the corresponding function. + #[inline] + pub fn call( + &self, + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + match self.inner { + Inner::PointerFn(f) => f(this, args, context), + Inner::Closure(ref c) => c.call(this, args, context), + } + } +} + +trait TraceableGenericClosure: Trace { + fn call(&mut self, args: Args, context: &mut Context<'_>) -> Ret; +} + +#[derive(Trace, Finalize)] +struct GenericClosure +where + F: FnMut(Args, &mut T, &mut Context<'_>) -> Ret, + T: Trace, +{ + // SAFETY: `GenericNativeFunction`'s safe API ensures only `Copy` closures are stored; its unsafe API, + // on the other hand, explains the invariants to hold in order for this to be safe, shifting + // the responsibility to the caller. + #[unsafe_ignore_trace] + f: F, + captures: T, + #[allow(clippy::type_complexity)] + phantom: PhantomData) -> Ret>>, +} + +impl TraceableGenericClosure for GenericClosure +where + F: FnMut(Args, &mut T, &mut Context<'_>) -> Ret, + T: Trace, +{ + fn call(&mut self, args: Args, context: &mut Context<'_>) -> Ret { + (self.f)(args, &mut self.captures, context) + } +} + +/// A callable generic Rust function that can be invoked by the engine. +/// +/// This is a more general struct of the [`NativeFunction`] API, useful for callbacks defined in the +/// host that are useful to the engine, such as [`HostCallJobCallback`] or [`HostEnqueuePromiseJob`]. +/// +/// `GenericNativeFunction` functions are divided in two: +/// - Function pointers a.k.a common functions. +/// - Closure functions that can capture the current environment. +/// +/// # Caveats +/// +/// - Since the Rust language doesn't support [**variadic generics**], all functions requiring +/// more than 1 argument (excluding the required [`Context`]), will define its generic parameter +/// `Args` as a tuple instead, which slightly worsens the API. We hope this can improve when +/// variadic generics arrive. +/// +/// - By limitations of the Rust language, the garbage collector currently cannot inspect closures +/// in order to trace their captured variables. This means that only [`Copy`] closures are 100% safe +/// to use. All other closures can also be stored in a `GenericNativeFunction`, albeit by using an +/// `unsafe` API, but note that passing closures implicitly capturing traceable types could cause +/// **Undefined Behaviour**. +/// +/// [`HostCallJobCallback`]: https://tc39.es/ecma262/#sec-hostcalljobcallback +/// [`HostEnqueuePromiseJob`]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob +/// [**variadic generics**]: https://github.com/rust-lang/rfcs/issues/376 +pub struct GenericNativeFunction { + inner: GenericInner, +} + +enum GenericInner { + PointerFn(fn(Args, &mut Context<'_>) -> Ret), + Closure(Box>), +} + +impl Finalize for GenericNativeFunction { + fn finalize(&self) { + if let GenericInner::Closure(c) = &self.inner { + c.finalize(); + } + } +} + +// Manual implementation because deriving `Trace` triggers the `single_use_lifetimes` lint. +// SAFETY: Only closures can contain `Trace` captures, so this implementation is safe. +unsafe impl Trace for GenericNativeFunction { + custom_trace!(this, { + if let GenericInner::Closure(c) = &this.inner { + mark(c); + } + }); +} + +impl std::fmt::Debug for GenericNativeFunction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NativeFunction").finish_non_exhaustive() + } +} + +impl GenericNativeFunction { + /// Creates a `GenericNativeFunction` from a function pointer. + #[inline] + pub fn from_fn_ptr(function: fn(Args, &mut Context<'_>) -> Ret) -> Self { + Self { + inner: GenericInner::PointerFn(function), + } + } + + /// Creates a `GenericNativeFunction` from a `Copy` closure. + pub fn from_copy_closure(closure: F) -> Self + where + F: FnMut(Args, &mut Context<'_>) -> Ret + Copy + 'static, + { + // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. + unsafe { Self::from_closure(closure) } + } + + /// Creates a `GenericNativeFunction` from a `Copy` closure and a list of traceable captures. + pub fn from_copy_closure_with_captures(closure: F, captures: T) -> Self + where + F: FnMut(Args, &mut T, &mut Context<'_>) -> Ret + Copy + 'static, + T: Trace + 'static, + { + // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure. + unsafe { Self::from_closure_with_captures(closure, captures) } + } + + /// Creates a new `GenericNativeFunction` from a closure. + /// + /// # Safety + /// + /// Passing a closure that contains a captured variable that needs to be traced by the garbage + /// collector could cause an use after free, memory corruption or other kinds of **Undefined + /// Behaviour**. See for a technical explanation + /// on why that is the case. + pub unsafe fn from_closure(mut closure: F) -> Self + where + F: FnMut(Args, &mut Context<'_>) -> Ret + 'static, + { + // SAFETY: The caller must ensure the invariants of the closure hold. + unsafe { + Self::from_closure_with_captures(move |args, _, context| closure(args, context), ()) + } + } + + /// Create a new `GenericNativeFunction` from a closure and a list of traceable captures. + /// + /// # Safety + /// + /// Passing a closure that contains a captured variable that needs to be traced by the garbage + /// collector could cause an use after free, memory corruption or other kinds of **Undefined + /// Behaviour**. See for a technical explanation + /// on why that is the case. + pub unsafe fn from_closure_with_captures(closure: F, captures: T) -> Self + where + F: FnMut(Args, &mut T, &mut Context<'_>) -> Ret + 'static, + T: Trace + 'static, + { + Self { + inner: GenericInner::Closure(Box::new(GenericClosure { + f: closure, + captures, + phantom: PhantomData, + })), + } + } + + /// Calls this `GenericNativeFunction`, forwarding the arguments to the corresponding function. + #[inline] + pub fn call(&mut self, args: Args, context: &mut Context<'_>) -> Ret { + match self.inner { + GenericInner::PointerFn(f) => f(args, context), + GenericInner::Closure(ref mut c) => c.call(args, context), + } + } +} diff --git a/boa_engine/src/object/builtins/jsproxy.rs b/boa_engine/src/object/builtins/jsproxy.rs index 32a4776878..950df9b26c 100644 --- a/boa_engine/src/object/builtins/jsproxy.rs +++ b/boa_engine/src/object/builtins/jsproxy.rs @@ -2,8 +2,9 @@ use boa_gc::{Finalize, Trace}; use crate::{ - builtins::{function::NativeFunctionSignature, Proxy}, - object::{FunctionBuilder, JsObject, JsObjectType, ObjectData}, + builtins::Proxy, + native_function::{NativeFunction, NativeFunctionPointer}, + object::{FunctionObjectBuilder, JsObject, JsObjectType, ObjectData}, Context, JsResult, JsValue, }; @@ -104,19 +105,19 @@ impl std::ops::Deref for JsRevocableProxy { #[derive(Clone)] pub struct JsProxyBuilder { target: JsObject, - apply: Option, - construct: Option, - define_property: Option, - delete_property: Option, - get: Option, - get_own_property_descriptor: Option, - get_prototype_of: Option, - has: Option, - is_extensible: Option, - own_keys: Option, - prevent_extensions: Option, - set: Option, - set_prototype_of: Option, + apply: Option, + construct: Option, + define_property: Option, + delete_property: Option, + get: Option, + get_own_property_descriptor: Option, + get_prototype_of: Option, + has: Option, + is_extensible: Option, + own_keys: Option, + prevent_extensions: Option, + set: Option, + set_prototype_of: Option, } impl std::fmt::Debug for JsProxyBuilder { @@ -195,7 +196,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply #[inline] - pub fn apply(mut self, apply: NativeFunctionSignature) -> Self { + pub fn apply(mut self, apply: NativeFunctionPointer) -> Self { self.apply = Some(apply); self } @@ -213,7 +214,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/construct #[inline] - pub fn construct(mut self, construct: NativeFunctionSignature) -> Self { + pub fn construct(mut self, construct: NativeFunctionPointer) -> Self { self.construct = Some(construct); self } @@ -226,7 +227,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty #[inline] - pub fn define_property(mut self, define_property: NativeFunctionSignature) -> Self { + pub fn define_property(mut self, define_property: NativeFunctionPointer) -> Self { self.define_property = Some(define_property); self } @@ -239,7 +240,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty #[inline] - pub fn delete_property(mut self, delete_property: NativeFunctionSignature) -> Self { + pub fn delete_property(mut self, delete_property: NativeFunctionPointer) -> Self { self.delete_property = Some(delete_property); self } @@ -252,7 +253,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get #[inline] - pub fn get(mut self, get: NativeFunctionSignature) -> Self { + pub fn get(mut self, get: NativeFunctionPointer) -> Self { self.get = Some(get); self } @@ -267,7 +268,7 @@ impl JsProxyBuilder { #[inline] pub fn get_own_property_descriptor( mut self, - get_own_property_descriptor: NativeFunctionSignature, + get_own_property_descriptor: NativeFunctionPointer, ) -> Self { self.get_own_property_descriptor = Some(get_own_property_descriptor); self @@ -281,7 +282,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf #[inline] - pub fn get_prototype_of(mut self, get_prototype_of: NativeFunctionSignature) -> Self { + pub fn get_prototype_of(mut self, get_prototype_of: NativeFunctionPointer) -> Self { self.get_prototype_of = Some(get_prototype_of); self } @@ -294,7 +295,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has #[inline] - pub fn has(mut self, has: NativeFunctionSignature) -> Self { + pub fn has(mut self, has: NativeFunctionPointer) -> Self { self.has = Some(has); self } @@ -307,7 +308,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/isExtensible #[inline] - pub fn is_extensible(mut self, is_extensible: NativeFunctionSignature) -> Self { + pub fn is_extensible(mut self, is_extensible: NativeFunctionPointer) -> Self { self.is_extensible = Some(is_extensible); self } @@ -320,7 +321,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys #[inline] - pub fn own_keys(mut self, own_keys: NativeFunctionSignature) -> Self { + pub fn own_keys(mut self, own_keys: NativeFunctionPointer) -> Self { self.own_keys = Some(own_keys); self } @@ -333,7 +334,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/preventExtensions #[inline] - pub fn prevent_extensions(mut self, prevent_extensions: NativeFunctionSignature) -> Self { + pub fn prevent_extensions(mut self, prevent_extensions: NativeFunctionPointer) -> Self { self.prevent_extensions = Some(prevent_extensions); self } @@ -346,7 +347,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set #[inline] - pub fn set(mut self, set: NativeFunctionSignature) -> Self { + pub fn set(mut self, set: NativeFunctionPointer) -> Self { self.set = Some(set); self } @@ -359,7 +360,7 @@ impl JsProxyBuilder { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf #[inline] - pub fn set_prototype_of(mut self, set_prototype_of: NativeFunctionSignature) -> Self { + pub fn set_prototype_of(mut self, set_prototype_of: NativeFunctionPointer) -> Self { self.set_prototype_of = Some(set_prototype_of); self } @@ -374,13 +375,15 @@ impl JsProxyBuilder { let handler = JsObject::with_object_proto(context); if let Some(apply) = self.apply { - let f = FunctionBuilder::native(context, apply).length(3).build(); + let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(apply)) + .length(3) + .build(); handler .create_data_property_or_throw("apply", f, context) .expect("new object should be writable"); } if let Some(construct) = self.construct { - let f = FunctionBuilder::native(context, construct) + let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(construct)) .length(3) .build(); handler @@ -388,51 +391,61 @@ impl JsProxyBuilder { .expect("new object should be writable"); } if let Some(define_property) = self.define_property { - let f = FunctionBuilder::native(context, define_property) - .length(3) - .build(); + let f = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(define_property)) + .length(3) + .build(); handler .create_data_property_or_throw("defineProperty", f, context) .expect("new object should be writable"); } if let Some(delete_property) = self.delete_property { - let f = FunctionBuilder::native(context, delete_property) - .length(2) - .build(); + let f = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(delete_property)) + .length(2) + .build(); handler .create_data_property_or_throw("deleteProperty", f, context) .expect("new object should be writable"); } if let Some(get) = self.get { - let f = FunctionBuilder::native(context, get).length(3).build(); + let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get)) + .length(3) + .build(); handler .create_data_property_or_throw("get", f, context) .expect("new object should be writable"); } if let Some(get_own_property_descriptor) = self.get_own_property_descriptor { - let f = FunctionBuilder::native(context, get_own_property_descriptor) - .length(2) - .build(); + let f = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(get_own_property_descriptor), + ) + .length(2) + .build(); handler .create_data_property_or_throw("getOwnPropertyDescriptor", f, context) .expect("new object should be writable"); } if let Some(get_prototype_of) = self.get_prototype_of { - let f = FunctionBuilder::native(context, get_prototype_of) - .length(1) - .build(); + let f = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get_prototype_of)) + .length(1) + .build(); handler .create_data_property_or_throw("getPrototypeOf", f, context) .expect("new object should be writable"); } if let Some(has) = self.has { - let f = FunctionBuilder::native(context, has).length(2).build(); + let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(has)) + .length(2) + .build(); handler .create_data_property_or_throw("has", f, context) .expect("new object should be writable"); } if let Some(is_extensible) = self.is_extensible { - let f = FunctionBuilder::native(context, is_extensible) + let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(is_extensible)) .length(1) .build(); handler @@ -440,29 +453,37 @@ impl JsProxyBuilder { .expect("new object should be writable"); } if let Some(own_keys) = self.own_keys { - let f = FunctionBuilder::native(context, own_keys).length(1).build(); + let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(own_keys)) + .length(1) + .build(); handler .create_data_property_or_throw("ownKeys", f, context) .expect("new object should be writable"); } if let Some(prevent_extensions) = self.prevent_extensions { - let f = FunctionBuilder::native(context, prevent_extensions) - .length(1) - .build(); + let f = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(prevent_extensions), + ) + .length(1) + .build(); handler .create_data_property_or_throw("preventExtensions", f, context) .expect("new object should be writable"); } if let Some(set) = self.set { - let f = FunctionBuilder::native(context, set).length(4).build(); + let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set)) + .length(4) + .build(); handler .create_data_property_or_throw("set", f, context) .expect("new object should be writable"); } if let Some(set_prototype_of) = self.set_prototype_of { - let f = FunctionBuilder::native(context, set_prototype_of) - .length(2) - .build(); + let f = + FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set_prototype_of)) + .length(2) + .build(); handler .create_data_property_or_throw("setPrototypeOf", f, context) .expect("new object should be writable"); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 34c8938cb8..fe458d4504 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -33,10 +33,7 @@ use crate::{ async_generator::AsyncGenerator, error::ErrorKind, function::arguments::Arguments, - function::{ - arguments::ParameterMap, BoundFunction, Captures, ConstructorKind, Function, - NativeFunctionSignature, - }, + function::{arguments::ParameterMap, BoundFunction, ConstructorKind, Function}, generator::Generator, iterable::AsyncFromSyncIterator, map::map_iterator::MapIterator, @@ -51,10 +48,10 @@ use crate::{ DataView, Date, Promise, RegExp, }, context::intrinsics::StandardConstructor, - error::JsNativeError, js_string, + native_function::{NativeFunction, NativeFunctionPointer}, property::{Attribute, PropertyDescriptor, PropertyKey}, - Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, + Context, JsBigInt, JsString, JsSymbol, JsValue, }; use boa_gc::{custom_trace, Finalize, GcCell, Trace, WeakGc}; @@ -1899,17 +1896,17 @@ where /// Builder for creating native function objects #[derive(Debug)] -pub struct FunctionBuilder<'ctx, 'icu> { +pub struct FunctionObjectBuilder<'ctx, 'icu> { context: &'ctx mut Context<'icu>, function: Function, name: JsString, length: usize, } -impl<'ctx, 'icu> FunctionBuilder<'ctx, 'icu> { +impl<'ctx, 'icu> FunctionObjectBuilder<'ctx, 'icu> { /// Create a new `FunctionBuilder` for creating a native function. #[inline] - pub fn native(context: &'ctx mut Context<'icu>, function: NativeFunctionSignature) -> Self { + pub fn new(context: &'ctx mut Context<'icu>, function: NativeFunction) -> Self { Self { context, function: Function::Native { @@ -1921,57 +1918,6 @@ impl<'ctx, 'icu> FunctionBuilder<'ctx, 'icu> { } } - /// Create a new `FunctionBuilder` for creating a closure function. - pub fn closure(context: &'ctx mut Context<'icu>, function: F) -> Self - where - F: Fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult + Copy + 'static, - { - Self { - context, - function: Function::Closure { - function: Box::new(move |this, args, _, context| function(this, args, context)), - constructor: None, - captures: Captures::new(()), - }, - name: js_string!(), - length: 0, - } - } - - /// Create a new closure function with additional captures. - /// - /// # Note - /// - /// You can only move variables that implement `Debug + Any + Trace + Clone`. - /// In other words, only `NativeObject + Clone` objects are movable. - pub fn closure_with_captures( - context: &'ctx mut Context<'icu>, - function: F, - captures: C, - ) -> Self - where - F: Fn(&JsValue, &[JsValue], &mut C, &mut Context<'_>) -> JsResult + Copy + 'static, - C: NativeObject, - { - Self { - context, - function: Function::Closure { - function: Box::new(move |this, args, captures: Captures, context| { - let mut captures = captures.as_mut_any(); - let captures = captures.downcast_mut::().ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot downcast `Captures` to given type") - })?; - function(this, args, captures, context) - }), - constructor: None, - captures: Captures::new(captures), - }, - name: js_string!(), - length: 0, - } - } - /// Specify the name property of object function object. /// /// The default is `""` (empty string). @@ -2005,10 +1951,6 @@ impl<'ctx, 'icu> FunctionBuilder<'ctx, 'icu> { Function::Native { ref mut constructor, .. - } - | Function::Closure { - ref mut constructor, - .. } => { *constructor = yes.then_some(ConstructorKind::Base); } @@ -2102,7 +2044,7 @@ impl<'ctx, 'icu> ObjectInitializer<'ctx, 'icu> { /// Add a function to the object. pub fn function( &mut self, - function: NativeFunctionSignature, + function: NativeFunctionPointer, binding: B, length: usize, ) -> &mut Self @@ -2110,11 +2052,12 @@ impl<'ctx, 'icu> ObjectInitializer<'ctx, 'icu> { B: Into, { let binding = binding.into(); - let function = FunctionBuilder::native(self.context, function) - .name(binding.name) - .length(length) - .constructor(false) - .build(); + let function = + FunctionObjectBuilder::new(self.context, NativeFunction::from_fn_ptr(function)) + .name(binding.name) + .length(length) + .constructor(false) + .build(); self.object.borrow_mut().insert( binding.binding, @@ -2152,7 +2095,7 @@ impl<'ctx, 'icu> ObjectInitializer<'ctx, 'icu> { /// Builder for creating constructors objects, like `Array`. pub struct ConstructorBuilder<'ctx, 'icu> { context: &'ctx mut Context<'icu>, - function: NativeFunctionSignature, + function: NativeFunctionPointer, object: JsObject, has_prototype_property: bool, prototype: JsObject, @@ -2184,7 +2127,7 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> { #[inline] pub fn new( context: &'ctx mut Context<'icu>, - function: NativeFunctionSignature, + function: NativeFunctionPointer, ) -> ConstructorBuilder<'ctx, 'icu> { Self { context, @@ -2203,7 +2146,7 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> { pub(crate) fn with_standard_constructor( context: &'ctx mut Context<'icu>, - function: NativeFunctionSignature, + function: NativeFunctionPointer, standard_constructor: StandardConstructor, ) -> ConstructorBuilder<'ctx, 'icu> { Self { @@ -2224,7 +2167,7 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> { /// Add new method to the constructors prototype. pub fn method( &mut self, - function: NativeFunctionSignature, + function: NativeFunctionPointer, binding: B, length: usize, ) -> &mut Self @@ -2232,11 +2175,12 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> { B: Into, { let binding = binding.into(); - let function = FunctionBuilder::native(self.context, function) - .name(binding.name) - .length(length) - .constructor(false) - .build(); + let function = + FunctionObjectBuilder::new(self.context, NativeFunction::from_fn_ptr(function)) + .name(binding.name) + .length(length) + .constructor(false) + .build(); self.prototype.borrow_mut().insert( binding.binding, @@ -2252,7 +2196,7 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> { /// Add new static method to the constructors object itself. pub fn static_method( &mut self, - function: NativeFunctionSignature, + function: NativeFunctionPointer, binding: B, length: usize, ) -> &mut Self @@ -2260,11 +2204,12 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> { B: Into, { let binding = binding.into(); - let function = FunctionBuilder::native(self.context, function) - .name(binding.name) - .length(length) - .constructor(false) - .build(); + let function = + FunctionObjectBuilder::new(self.context, NativeFunction::from_fn_ptr(function)) + .name(binding.name) + .length(length) + .constructor(false) + .build(); self.object.borrow_mut().insert( binding.binding, @@ -2443,7 +2388,7 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> { pub fn build(&mut self) -> JsFunction { // Create the native function let function = Function::Native { - function: self.function, + function: NativeFunction::from_fn_ptr(self.function), constructor: self.constructor, }; diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index d1869b4af2..c2834fd1ee 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -705,25 +705,16 @@ impl JsObject { function, constructor, } => { - let function = *function; + let function = function.clone(); let constructor = *constructor; drop(object); if constructor.is_some() { - function(&JsValue::undefined(), args, context) + function.call(&JsValue::undefined(), args, context) } else { - function(this, args, context) + function.call(this, args, context) } } - Function::Closure { - function, captures, .. - } => { - let function = function.clone(); - let captures = captures.clone(); - drop(object); - - (function)(this, args, captures, context) - } Function::Ordinary { code, environments, .. } => { @@ -1288,41 +1279,15 @@ impl JsObject { function, constructor, .. - } => { - let function = *function; - let constructor = *constructor; - drop(object); - - match function(this_target, args, context)? { - JsValue::Object(ref o) => Ok(o.clone()), - val => { - if constructor.expect("hmm").is_base() || val.is_undefined() { - create_this(context) - } else { - Err(JsNativeError::typ() - .with_message( - "Derived constructor can only return an Object or undefined", - ) - .into()) - } - } - } - } - Function::Closure { - function, - captures, - constructor, - .. } => { let function = function.clone(); - let captures = captures.clone(); let constructor = *constructor; drop(object); - match (function)(this_target, args, captures, context)? { + match function.call(this_target, args, context)? { JsValue::Object(ref o) => Ok(o.clone()), val => { - if constructor.expect("hmma").is_base() || val.is_undefined() { + if constructor.expect("hmm").is_base() || val.is_undefined() { create_this(context) } else { Err(JsNativeError::typ() diff --git a/boa_engine/src/vm/opcode/await_stm/mod.rs b/boa_engine/src/vm/opcode/await_stm/mod.rs index 7a1b122470..fb68dc5d92 100644 --- a/boa_engine/src/vm/opcode/await_stm/mod.rs +++ b/boa_engine/src/vm/opcode/await_stm/mod.rs @@ -1,6 +1,9 @@ +use boa_gc::{Gc, GcCell}; + use crate::{ builtins::{JsArgs, Promise}, - object::FunctionBuilder, + native_function::NativeFunction, + object::FunctionObjectBuilder, vm::{call_frame::GeneratorResumeKind, opcode::Operation, ShouldExit}, Context, JsResult, JsValue, }; @@ -28,37 +31,41 @@ impl Operation for Await { // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). - let on_fulfilled = FunctionBuilder::closure_with_captures( + let on_fulfilled = FunctionObjectBuilder::new( context, - |_this, args, (environment, stack, frame), context| { - // a. Let prevContext be the running execution context. - // b. Suspend prevContext. - // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. - // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. - // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. - // f. Return undefined. - - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - context.vm.push_frame(frame.clone()); - - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; - context.vm.push(args.get_or_undefined(0).clone()); - context.run()?; - - *frame = context - .vm - .pop_frame() - .expect("generator call frame must exist"); - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - - Ok(JsValue::undefined()) - }, - ( - context.realm.environments.clone(), - context.vm.stack.clone(), - context.vm.frame().clone(), + NativeFunction::from_copy_closure_with_captures( + |_this, args, captures, context| { + let mut captures = captures.borrow_mut(); + let (environment, stack, frame) = &mut *captures; + // a. Let prevContext be the running execution context. + // b. Suspend prevContext. + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // f. Return undefined. + + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + context.vm.push_frame(frame.clone()); + + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; + context.vm.push(args.get_or_undefined(0).clone()); + context.run()?; + + *frame = context + .vm + .pop_frame() + .expect("generator call frame must exist"); + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + + Ok(JsValue::undefined()) + }, + Gc::new(GcCell::new(( + context.realm.environments.clone(), + context.vm.stack.clone(), + context.vm.frame().clone(), + ))), ), ) .name("") @@ -67,37 +74,41 @@ impl Operation for Await { // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called: // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). - let on_rejected = FunctionBuilder::closure_with_captures( + let on_rejected = FunctionObjectBuilder::new( context, - |_this, args, (environment, stack, frame), context| { - // a. Let prevContext be the running execution context. - // b. Suspend prevContext. - // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. - // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. - // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. - // f. Return undefined. - - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - context.vm.push_frame(frame.clone()); - - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; - context.vm.push(args.get_or_undefined(0).clone()); - context.run()?; - - *frame = context - .vm - .pop_frame() - .expect("generator call frame must exist"); - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - - Ok(JsValue::undefined()) - }, - ( - context.realm.environments.clone(), - context.vm.stack.clone(), - context.vm.frame().clone(), + NativeFunction::from_copy_closure_with_captures( + |_this, args, captures, context| { + let mut captures = captures.borrow_mut(); + let (environment, stack, frame) = &mut *captures; + // a. Let prevContext be the running execution context. + // b. Suspend prevContext. + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // f. Return undefined. + + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + context.vm.push_frame(frame.clone()); + + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; + context.vm.push(args.get_or_undefined(0).clone()); + context.run()?; + + *frame = context + .vm + .pop_frame() + .expect("generator call frame must exist"); + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + + Ok(JsValue::undefined()) + }, + Gc::new(GcCell::new(( + context.realm.environments.clone(), + context.vm.stack.clone(), + context.vm.frame().clone(), + ))), ), ) .name("") diff --git a/boa_examples/src/bin/closures.rs b/boa_examples/src/bin/closures.rs index 1944c20b78..5309694193 100644 --- a/boa_examples/src/bin/closures.rs +++ b/boa_examples/src/bin/closures.rs @@ -1,33 +1,40 @@ -// This example goes into the details on how to pass closures as functions -// inside Rust and call them from Javascript. +// This example goes into the details on how to pass closures as functions inside Rust and call them +// from Javascript. + +use std::cell::{Cell, RefCell}; use boa_engine::{ js_string, - object::{FunctionBuilder, JsObject}, + native_function::NativeFunction, + object::{builtins::JsArray, FunctionObjectBuilder, JsObject}, property::{Attribute, PropertyDescriptor}, string::utf16, - Context, JsError, JsString, JsValue, + Context, JsError, JsNativeError, JsString, JsValue, }; -use boa_gc::{Finalize, Trace}; +use boa_gc::{Finalize, GcCell, Trace}; fn main() -> Result<(), JsError> { // We create a new `Context` to create a new Javascript executor. let mut context = Context::default(); - // We make some operations in Rust that return a `Copy` value that we want - // to pass to a Javascript function. + // We make some operations in Rust that return a `Copy` value that we want to pass to a Javascript + // function. let variable = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1; // We register a global closure function that has the name 'closure' with length 0. - context.register_global_closure("closure", 0, move |_, _, _| { - println!("Called `closure`"); - // `variable` is captured from the main function. - println!("variable = {variable}"); - println!(); + context.register_global_callable( + "closure", + 0, + NativeFunction::from_copy_closure(move |_, _, _| { + println!("Called `closure`"); + // `variable` is captured from the main function. + println!("variable = {variable}"); + println!(); - // We return the moved variable as a `JsValue`. - Ok(JsValue::new(variable)) - })?; + // We return the moved variable as a `JsValue`. + Ok(JsValue::new(variable)) + }), + ); assert_eq!(context.eval("closure()")?, 255.into()); @@ -59,34 +66,38 @@ fn main() -> Result<(), JsError> { object, }; - // We can use `FunctionBuilder` to define a closure with additional - // captures. - let js_function = FunctionBuilder::closure_with_captures( + // We can use `FunctionBuilder` to define a closure with additional captures and custom property + // attributes. + let js_function = FunctionObjectBuilder::new( &mut context, - |_, _, captures, context| { - println!("Called `createMessage`"); - // We obtain the `name` property of `captures.object` - let name = captures.object.get("name", context)?; - - // We create a new message from our captured variable. - let message = js_string!( - utf16!("message from `"), - &name.to_string(context)?, - utf16!("`: "), - &captures.greeting - ); - - // We can also mutate the moved data inside the closure. - captures.greeting = js_string!(&captures.greeting, utf16!(" Hello!")); - - println!("{}", message.to_std_string_escaped()); - println!(); - - // We convert `message` into `JsValue` to be able to return it. - Ok(message.into()) - }, - // Here is where we move `clone_variable` into the closure. - clone_variable, + NativeFunction::from_copy_closure_with_captures( + |_, _, captures, context| { + let mut captures = captures.borrow_mut(); + let BigStruct { greeting, object } = &mut *captures; + println!("Called `createMessage`"); + // We obtain the `name` property of `captures.object` + let name = object.get("name", context)?; + + // We create a new message from our captured variable. + let message = js_string!( + utf16!("message from `"), + &name.to_string(context)?, + utf16!("`: "), + greeting + ); + + // We can also mutate the moved data inside the closure. + captures.greeting = js_string!(greeting, utf16!(" Hello!")); + + println!("{}", message.to_std_string_escaped()); + println!(); + + // We convert `message` into `JsValue` to be able to return it. + Ok(message.into()) + }, + // Here is where we move `clone_variable` into the closure. + GcCell::new(clone_variable), + ), ) // And here we assign `createMessage` to the `name` property of the closure. .name("createMessage") @@ -102,7 +113,7 @@ fn main() -> Result<(), JsError> { // We pass `js_function` as a property value. js_function, // We assign to the "createMessage" property the desired attributes. - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, ); assert_eq!( @@ -119,5 +130,65 @@ fn main() -> Result<(), JsError> { // We have moved `Clone` variables into a closure and executed that closure // inside Javascript! + // ADVANCED + + // If we can ensure the captured variables are not traceable by the garbage collector, + // we can pass any static closure easily. + + let index = Cell::new(0i32); + let numbers = RefCell::new(Vec::new()); + + // We register a global closure that is not `Copy`. + context.register_global_callable( + "enumerate", + 0, + // Note that it is required to use `unsafe` code, since the compiler cannot verify that the + // types captured by the closure are not traceable. + unsafe { + NativeFunction::from_closure(move |_, _, context| { + println!("Called `enumerate`"); + // `index` is captured from the main function. + println!("index = {}", index.get()); + println!(); + + numbers.borrow_mut().push(index.get()); + index.set(index.get() + 1); + + // We return the moved variable as a `JsValue`. + Ok( + JsArray::from_iter( + numbers.borrow().iter().cloned().map(JsValue::from), + context, + ) + .into(), + ) + }) + }, + ); + + // First call should return the array `[0]`. + let result = context.eval("enumerate()")?; + let object = result + .as_object() + .cloned() + .ok_or_else(|| JsNativeError::typ().with_message("not an array!"))?; + let array = JsArray::from_object(object)?; + + assert_eq!(array.get(0, &mut context)?, JsValue::from(0i32)); + assert_eq!(array.get(1, &mut context)?, JsValue::undefined()); + + // First call should return the array `[0, 1]`. + let result = context.eval("enumerate()")?; + let object = result + .as_object() + .cloned() + .ok_or_else(|| JsNativeError::typ().with_message("not an array!"))?; + let array = JsArray::from_object(object)?; + + assert_eq!(array.get(0, &mut context)?, JsValue::from(0i32)); + assert_eq!(array.get(1, &mut context)?, JsValue::from(1i32)); + assert_eq!(array.get(2, &mut context)?, JsValue::undefined()); + + // We have moved non-traceable variables into a closure and executed that closure inside Javascript! Ok(()) } diff --git a/boa_examples/src/bin/jsarray.rs b/boa_examples/src/bin/jsarray.rs index ae79ceddd6..16470c342f 100644 --- a/boa_examples/src/bin/jsarray.rs +++ b/boa_examples/src/bin/jsarray.rs @@ -1,7 +1,8 @@ // This example shows how to manipulate a Javascript array using Rust code. use boa_engine::{ - object::{builtins::JsArray, FunctionBuilder}, + native_function::NativeFunction, + object::{builtins::JsArray, FunctionObjectBuilder}, string::utf16, Context, JsResult, JsValue, }; @@ -60,17 +61,23 @@ fn main() -> JsResult<()> { let joined_array = array.join(Some("::".into()), context)?; assert_eq!(&joined_array, utf16!("14::false::false::false::10")); - let filter_callback = FunctionBuilder::native(context, |_this, args, _context| { - Ok(args.get(0).cloned().unwrap_or_default().is_number().into()) - }) + let filter_callback = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(|_this, args, _context| { + Ok(args.get(0).cloned().unwrap_or_default().is_number().into()) + }), + ) .build(); - let map_callback = FunctionBuilder::native(context, |_this, args, context| { - args.get(0) - .cloned() - .unwrap_or_default() - .pow(&JsValue::new(2), context) - }) + let map_callback = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(|_this, args, context| { + args.get(0) + .cloned() + .unwrap_or_default() + .pow(&JsValue::new(2), context) + }), + ) .build(); let mut data = Vec::new(); @@ -88,12 +95,15 @@ fn main() -> JsResult<()> { assert_eq!(&chained_array.join(None, context)?, utf16!("196,1,2,3")); - let reduce_callback = FunctionBuilder::native(context, |_this, args, context| { - let accumulator = args.get(0).cloned().unwrap_or_default(); - let value = args.get(1).cloned().unwrap_or_default(); + let reduce_callback = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(|_this, args, context| { + let accumulator = args.get(0).cloned().unwrap_or_default(); + let value = args.get(1).cloned().unwrap_or_default(); - accumulator.add(&value, context) - }) + accumulator.add(&value, context) + }), + ) .build(); assert_eq!( diff --git a/boa_examples/src/bin/jstypedarray.rs b/boa_examples/src/bin/jstypedarray.rs index cf9f1cdfb0..fe44342fc0 100644 --- a/boa_examples/src/bin/jstypedarray.rs +++ b/boa_examples/src/bin/jstypedarray.rs @@ -1,7 +1,8 @@ // This example shows how to manipulate a Javascript array using Rust code. use boa_engine::{ - object::{builtins::JsUint8Array, FunctionBuilder}, + native_function::NativeFunction, + object::{builtins::JsUint8Array, FunctionObjectBuilder}, property::Attribute, Context, JsResult, JsValue, }; @@ -23,12 +24,15 @@ fn main() -> JsResult<()> { sum += i; } - let callback = FunctionBuilder::native(context, |_this, args, context| { - let accumulator = args.get(0).cloned().unwrap_or_default(); - let value = args.get(1).cloned().unwrap_or_default(); + let callback = FunctionObjectBuilder::new( + context, + NativeFunction::from_fn_ptr(|_this, args, context| { + let accumulator = args.get(0).cloned().unwrap_or_default(); + let value = args.get(1).cloned().unwrap_or_default(); - accumulator.add(&value, context) - }) + accumulator.add(&value, context) + }), + ) .build(); assert_eq!( diff --git a/boa_examples/src/bin/modulehandler.rs b/boa_examples/src/bin/modulehandler.rs index 55e2e74455..ae400fbb4d 100644 --- a/boa_examples/src/bin/modulehandler.rs +++ b/boa_examples/src/bin/modulehandler.rs @@ -1,7 +1,10 @@ // This example implements a custom module handler which mimics // the require/module.exports pattern -use boa_engine::{prelude::JsObject, property::Attribute, Context, JsResult, JsValue}; +use boa_engine::{ + native_function::NativeFunction, prelude::JsObject, property::Attribute, Context, JsResult, + JsValue, +}; use std::fs::read_to_string; fn main() { @@ -17,7 +20,7 @@ fn main() { let mut ctx = Context::default(); // Adding custom implementation that mimics 'require' - ctx.register_global_function("require", 0, require); + ctx.register_global_callable("require", 0, NativeFunction::from_fn_ptr(require)); // Adding custom object that mimics 'module.exports' let moduleobj = JsObject::default(); diff --git a/boa_gc/src/internals/gc_box.rs b/boa_gc/src/internals/gc_box.rs index 9c31eae27b..0c64e642bf 100644 --- a/boa_gc/src/internals/gc_box.rs +++ b/boa_gc/src/internals/gc_box.rs @@ -102,7 +102,7 @@ impl fmt::Debug for GcBoxHeader { /// A garbage collected allocation. #[derive(Debug)] -pub(crate) struct GcBox { +pub struct GcBox { pub(crate) header: GcBoxHeader, pub(crate) value: T, } diff --git a/boa_gc/src/internals/mod.rs b/boa_gc/src/internals/mod.rs index 7539365723..0074c8f1a4 100644 --- a/boa_gc/src/internals/mod.rs +++ b/boa_gc/src/internals/mod.rs @@ -1,4 +1,5 @@ mod ephemeron_box; mod gc_box; -pub(crate) use self::{ephemeron_box::EphemeronBox, gc_box::GcBox}; +pub(crate) use self::ephemeron_box::EphemeronBox; +pub use self::gc_box::GcBox; diff --git a/boa_gc/src/lib.rs b/boa_gc/src/lib.rs index dcc132ee4c..932507fd19 100644 --- a/boa_gc/src/lib.rs +++ b/boa_gc/src/lib.rs @@ -95,7 +95,6 @@ mod trace; pub(crate) mod internals; use boa_profiler::Profiler; -use internals::GcBox; use std::{ cell::{Cell, RefCell}, mem, @@ -105,6 +104,7 @@ use std::{ pub use crate::trace::{Finalize, Trace}; pub use boa_macros::{Finalize, Trace}; pub use cell::{GcCell, GcCellRef, GcCellRefMut}; +pub use internals::GcBox; pub use pointers::{Ephemeron, Gc, WeakGc}; type GcPointer = NonNull>; diff --git a/boa_gc/src/pointers/gc.rs b/boa_gc/src/pointers/gc.rs index 01c06e7a2e..48054cc099 100644 --- a/boa_gc/src/pointers/gc.rs +++ b/boa_gc/src/pointers/gc.rs @@ -47,6 +47,16 @@ impl Gc { gc.set_root(); gc } + + /// Consumes the `Gc`, returning a wrapped raw pointer. + /// + /// To avoid a memory leak, the pointer must be converted back to a `Gc` using [`Gc::from_raw`]. + #[allow(clippy::use_self)] + pub fn into_raw(this: Gc) -> NonNull> { + let ptr = this.inner_ptr.get(); + std::mem::forget(this); + ptr + } } impl Gc { @@ -55,9 +65,26 @@ impl Gc { GcBox::ptr_eq(this.inner(), other.inner()) } - /// Will return a new rooted `Gc` from a `GcBox` pointer - pub(crate) fn from_ptr(ptr: NonNull>) -> Self { - // SAFETY: the value provided as a pointer MUST be a valid GcBox. + /// Constructs a `Gc` from a raw pointer. + /// + /// The raw pointer must have been returned by a previous call to [`Gc::into_raw`][Gc::into_raw] + /// where `U` must have the same size and alignment as `T`. + /// + /// # Safety + /// + /// This function is unsafe because improper use may lead to memory corruption, double-free, + /// or misbehaviour of the garbage collector. + #[must_use] + pub const unsafe fn from_raw(ptr: NonNull>) -> Self { + Self { + inner_ptr: Cell::new(ptr), + marker: PhantomData, + } + } + + /// Return a rooted `Gc` from a `GcBox` pointer + pub(crate) unsafe fn from_ptr(ptr: NonNull>) -> Self { + // SAFETY: the caller must ensure that the pointer is valid. unsafe { ptr.as_ref().root_inner(); let gc = Self { @@ -159,7 +186,8 @@ unsafe impl Trace for Gc { impl Clone for Gc { fn clone(&self) -> Self { - Self::from_ptr(self.inner_ptr()) + // SAFETY: `&self` is a valid Gc pointer. + unsafe { Self::from_ptr(self.inner_ptr()) } } } diff --git a/boa_gc/src/trace.rs b/boa_gc/src/trace.rs index ec6ddfab13..ccba95027c 100644 --- a/boa_gc/src/trace.rs +++ b/boa_gc/src/trace.rs @@ -1,5 +1,6 @@ use std::{ borrow::{Cow, ToOwned}, + cell::Cell, collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}, hash::{BuildHasher, Hash}, marker::PhantomData, @@ -445,3 +446,21 @@ where } }); } + +impl Finalize for Cell> { + fn finalize(&self) { + if let Some(t) = self.take() { + t.finalize(); + self.set(Some(t)); + } + } +} + +unsafe impl Trace for Cell> { + custom_trace!(this, { + if let Some(t) = this.take() { + mark(&t); + this.set(Some(t)); + } + }); +} diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index 40c4e748bd..55cc9dd82b 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -7,14 +7,13 @@ use super::{ }; use crate::read::ErrorType; use boa_engine::{ - builtins::JsArgs, object::FunctionBuilder, property::Attribute, Context, JsNativeErrorKind, - JsResult, JsValue, + builtins::JsArgs, native_function::NativeFunction, object::FunctionObjectBuilder, + property::Attribute, Context, JsNativeErrorKind, JsValue, }; -use boa_gc::{Finalize, Gc, GcCell, Trace}; use boa_parser::Parser; use colored::Colorize; use rayon::prelude::*; -use std::borrow::Cow; +use std::{borrow::Cow, cell::RefCell, rc::Rc}; impl TestSuite { /// Runs the test suite. @@ -360,11 +359,25 @@ impl Test { /// Registers the print function in the context. fn register_print_fn(context: &mut Context<'_>, async_result: AsyncResult) { // We use `FunctionBuilder` to define a closure with additional captures. - let js_function = - FunctionBuilder::closure_with_captures(context, test262_print, async_result) - .name("print") - .length(1) - .build(); + let js_function = FunctionObjectBuilder::new( + context, + // SAFETY: `AsyncResult` has only non-traceable captures, making this safe. + unsafe { + NativeFunction::from_closure(move |_, args, context| { + let message = args + .get_or_undefined(0) + .to_string(context)? + .to_std_string_escaped(); + if message != "Test262:AsyncTestComplete" { + *async_result.inner.borrow_mut() = Err(message); + } + Ok(JsValue::undefined()) + }) + }, + ) + .name("print") + .length(1) + .build(); context.register_global_property( "print", @@ -375,33 +388,15 @@ impl Test { } /// Object which includes the result of the async operation. -#[derive(Debug, Clone, Trace, Finalize)] +#[derive(Debug, Clone)] struct AsyncResult { - inner: Gc>>, + inner: Rc>>, } impl Default for AsyncResult { fn default() -> Self { Self { - inner: Gc::new(GcCell::new(Ok(()))), + inner: Rc::new(RefCell::new(Ok(()))), } } } - -/// `print()` function required by the test262 suite. -#[allow(clippy::unnecessary_wraps)] -fn test262_print( - _this: &JsValue, - args: &[JsValue], - async_result: &mut AsyncResult, - context: &mut Context<'_>, -) -> JsResult { - let message = args - .get_or_undefined(0) - .to_string(context)? - .to_std_string_escaped(); - if message != "Test262:AsyncTestComplete" { - *async_result.inner.borrow_mut() = Err(message); - } - Ok(JsValue::undefined()) -}