diff --git a/boa/examples/closures.rs b/boa/examples/closures.rs index db049da0278..37f7c355aec 100644 --- a/boa/examples/closures.rs +++ b/boa/examples/closures.rs @@ -1,19 +1,20 @@ -use boa::{Context, JsString}; +use boa::{Context, JsString, Value}; -fn main() { +fn main() -> Result<(), Value> { let mut context = Context::new(); let variable = JsString::new("I am a captured variable"); - context - .register_global_closure("closure", 0, move |_, _, _| { - // This value is captured from main function. - Ok(variable.clone().into()) - }) - .unwrap(); + // We register a global closure function that has the name 'closure' with length 0. + context.register_global_closure("closure", 0, move |_, _, _| { + // This value is captured from main function. + Ok(variable.clone().into()) + })?; assert_eq!( - context.eval("closure()"), - Ok("I am an captured variable".into()) + context.eval("closure()")?, + "I am a captured variable".into() ); + + Ok(()) } diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 20b1e1a5374..f7a800a17e3 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -23,13 +23,17 @@ use crate::{ }; use bitflags::bitflags; use std::fmt::{self, Debug}; +use std::rc::Rc; #[cfg(test)] mod tests; -/// _fn(this, arguments, context) -> ResultValue_ - The signature of a built-in function +/// _fn(this, arguments, context) -> ResultValue_ - The signature of a native built-in function pub type NativeFunction = fn(&Value, &[Value], &mut Context) -> Result; +/// _fn(this, arguments, context) -> ResultValue_ - The signature of a closure built-in function +pub type ClosureFunction = dyn Fn(&Value, &[Value], &mut Context) -> Result; + #[derive(Clone, Copy, Finalize)] pub struct BuiltInFunction(pub(crate) NativeFunction); @@ -85,8 +89,7 @@ pub enum Function { constructable: bool, }, Closure { - #[allow(clippy::type_complexity)] - function: Box Result>, + function: Rc, constructable: bool, }, Ordinary { diff --git a/boa/src/context.rs b/boa/src/context.rs index f253931ecad..2ce7c401600 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -542,17 +542,20 @@ impl Context { Ok(function.into()) } - /// Register a global function. + /// Register a global native function. /// - /// The function will be both `callable` and `constructable` (call with `new`). + /// This is more efficient that creating a closure function, since this does not allocate, + /// it is just a function pointer. + /// + /// 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 /// - /// If you want to make a function only `callable` or `constructable`, or wish to bind it differently - /// to the global object, you can create the function object with [`FunctionBuilder`](crate::object::FunctionBuilder). + /// 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. #[inline] pub fn register_global_function( @@ -575,6 +578,18 @@ impl Context { Ok(()) } + /// 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 + /// + /// 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. #[inline] pub fn register_global_closure(&mut self, name: &str, length: usize, body: F) -> Result<()> where diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index 63bd5b8d698..bcb2ef21317 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -4,7 +4,9 @@ use super::{NativeObject, Object, PROTOTYPE}; use crate::{ - builtins::function::{create_unmapped_arguments_object, Function, NativeFunction}, + builtins::function::{ + create_unmapped_arguments_object, ClosureFunction, Function, NativeFunction, + }, context::StandardConstructor, environment::{ environment_record_trait::EnvironmentRecordTrait, @@ -24,6 +26,7 @@ use std::{ collections::HashMap, error::Error, fmt::{self, Debug, Display}, + rc::Rc, result::Result as StdResult, }; @@ -44,6 +47,7 @@ pub struct GcObject(Gc>); enum FunctionBody { BuiltInFunction(NativeFunction), BuiltInConstructor(NativeFunction), + Closure(Rc), Ordinary(RcStatementList), } @@ -149,9 +153,7 @@ impl GcObject { FunctionBody::BuiltInFunction(function.0) } } - Function::Closure { function, .. } => { - return (function)(this_target, args, context); - } + Function::Closure { function, .. } => FunctionBody::Closure(function.clone()), Function::Ordinary { body, params, @@ -300,6 +302,7 @@ impl GcObject { function(&Value::undefined(), args, context) } FunctionBody::BuiltInFunction(function) => function(this_target, args, context), + FunctionBody::Closure(function) => (function)(this_target, args, context), FunctionBody::Ordinary(body) => { let result = body.run(context); let this = context.get_this_binding(); diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 8f822d66c8c..ec75c87f897 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -22,6 +22,7 @@ use std::{ any::Any, fmt::{self, Debug, Display}, ops::{Deref, DerefMut}, + rc::Rc, }; #[cfg(test)] @@ -683,12 +684,12 @@ where pub struct FunctionBuilder<'context> { context: &'context mut Context, function: Option, - name: Option, + name: JsString, length: usize, } impl<'context> FunctionBuilder<'context> { - /// Create a new `FunctionBuilder` + /// Create a new `FunctionBuilder` for creating a native function. #[inline] pub fn native(context: &'context mut Context, function: NativeFunction) -> Self { Self { @@ -697,11 +698,12 @@ impl<'context> FunctionBuilder<'context> { function: function.into(), constructable: false, }), - name: None, + name: JsString::default(), length: 0, } } + /// Create a new `FunctionBuilder` for creating a closure function. #[inline] pub fn closure(context: &'context mut Context, function: F) -> Self where @@ -710,10 +712,10 @@ impl<'context> FunctionBuilder<'context> { Self { context, function: Some(Function::Closure { - function: Box::new(function), + function: Rc::new(function), constructable: false, }), - name: None, + name: JsString::default(), length: 0, } } @@ -726,7 +728,7 @@ impl<'context> FunctionBuilder<'context> { where N: AsRef, { - self.name = Some(name.as_ref().into()); + self.name = name.as_ref().into(); self } @@ -766,11 +768,7 @@ impl<'context> FunctionBuilder<'context> { .into(), ); let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - if let Some(name) = self.name.take() { - function.insert_property("name", name, attribute); - } else { - function.insert_property("name", "", attribute); - } + function.insert_property("name", self.name.clone(), attribute); function.insert_property("length", self.length, attribute); GcObject::new(function) @@ -788,11 +786,7 @@ impl<'context> FunctionBuilder<'context> { .into(), ); let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - if let Some(name) = self.name.take() { - object.insert_property("name", name, attribute); - } else { - object.insert_property("name", "", attribute); - } + object.insert_property("name", self.name.clone(), attribute); object.insert_property("length", self.length, attribute); } } @@ -879,7 +873,7 @@ pub struct ConstructorBuilder<'context> { constructor_function: NativeFunction, constructor_object: GcObject, prototype: GcObject, - name: Option, + name: JsString, length: usize, callable: bool, constructable: bool, @@ -910,7 +904,7 @@ impl<'context> ConstructorBuilder<'context> { constructor_object: GcObject::new(Object::default()), prototype: GcObject::new(Object::default()), length: 0, - name: None, + name: JsString::default(), callable: true, constructable: true, inherit: None, @@ -929,7 +923,7 @@ impl<'context> ConstructorBuilder<'context> { constructor_object: object.constructor, prototype: object.prototype, length: 0, - name: None, + name: JsString::default(), callable: true, constructable: true, inherit: None, @@ -1082,7 +1076,7 @@ impl<'context> ConstructorBuilder<'context> { where N: AsRef, { - self.name = Some(name.as_ref().into()); + self.name = name.as_ref().into(); self } @@ -1133,7 +1127,7 @@ impl<'context> ConstructorBuilder<'context> { Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); let name = DataDescriptor::new( - self.name.take().unwrap_or_else(|| String::from("[object]")), + self.name.clone(), Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, );