diff --git a/boa/examples/closures.rs b/boa/examples/closures.rs new file mode 100644 index 00000000000..db049da0278 --- /dev/null +++ b/boa/examples/closures.rs @@ -0,0 +1,19 @@ +use boa::{Context, JsString}; + +fn main() { + 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(); + + assert_eq!( + context.eval("closure()"), + Ok("I am an captured variable".into()) + ); +} diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index daaa5d10865..36ead95a184 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -45,16 +45,14 @@ impl BuiltIn for Array { let symbol_iterator = WellKnownSymbols::iterator(); - let get_species = FunctionBuilder::new(context, Self::get_species) + let get_species = FunctionBuilder::native(context, Self::get_species) .name("get [Symbol.species]") .constructable(false) - .callable(true) .build(); - let values_function = FunctionBuilder::new(context, Self::values) + let values_function = FunctionBuilder::native(context, Self::values) .name("values") .length(0) - .callable(true) .constructable(false) .build(); diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index c8d2cd3a1e6..20b1e1a5374 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -15,7 +15,7 @@ use crate::object::PROTOTYPE; use crate::{ builtins::{Array, BuiltIn}, environment::lexical_environment::Environment, - gc::{empty_trace, Finalize, Trace}, + gc::{custom_trace, empty_trace, Finalize, Trace}, object::{ConstructorBuilder, FunctionBuilder, GcObject, Object, ObjectData}, property::{Attribute, DataDescriptor}, syntax::ast::node::{FormalParameter, RcStatementList}, @@ -52,31 +52,12 @@ impl Debug for BuiltInFunction { bitflags! { #[derive(Finalize, Default)] pub struct FunctionFlags: u8 { - const CALLABLE = 0b0000_0001; const CONSTRUCTABLE = 0b0000_0010; const LEXICAL_THIS_MODE = 0b0000_0100; } } impl FunctionFlags { - pub(crate) fn from_parameters(callable: bool, constructable: bool) -> Self { - let mut flags = Self::default(); - - if callable { - flags |= Self::CALLABLE; - } - if constructable { - flags |= Self::CONSTRUCTABLE; - } - - flags - } - - #[inline] - pub(crate) fn is_callable(&self) -> bool { - self.contains(Self::CALLABLE) - } - #[inline] pub(crate) fn is_constructable(&self) -> bool { self.contains(Self::CONSTRUCTABLE) @@ -97,9 +78,17 @@ unsafe impl Trace for FunctionFlags { /// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) /// /// -#[derive(Debug, Clone, Finalize, Trace)] +#[derive(Finalize)] pub enum Function { - BuiltIn(BuiltInFunction, FunctionFlags), + Native { + function: BuiltInFunction, + constructable: bool, + }, + Closure { + #[allow(clippy::type_complexity)] + function: Box Result>, + constructable: bool, + }, Ordinary { flags: FunctionFlags, body: RcStatementList, @@ -108,6 +97,24 @@ pub enum Function { }, } +impl Debug for Function { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Function {{ ... }}") + } +} + +unsafe impl Trace for Function { + custom_trace!(this, { + match this { + Function::Native { .. } => {} + Function::Closure { .. } => {} + Function::Ordinary { environment, .. } => { + mark(environment); + } + } + }); +} + impl Function { // Adds the final rest parameters to the Environment as an array pub(crate) fn add_rest_param( @@ -154,18 +161,11 @@ impl Function { .expect("Failed to intialize binding"); } - /// Returns true if the function object is callable. - pub fn is_callable(&self) -> bool { - match self { - Self::BuiltIn(_, flags) => flags.is_callable(), - Self::Ordinary { flags, .. } => flags.is_callable(), - } - } - /// Returns true if the function object is constructable. pub fn is_constructable(&self) -> bool { match self { - Self::BuiltIn(_, flags) => flags.is_constructable(), + Self::Native { constructable, .. } => *constructable, + Self::Closure { constructable, .. } => *constructable, Self::Ordinary { flags, .. } => flags.is_constructable(), } } @@ -230,7 +230,10 @@ pub fn make_builtin_fn( let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init"); let mut function = Object::function( - Function::BuiltIn(function.into(), FunctionFlags::CALLABLE), + Function::Native { + function: function.into(), + constructable: false, + }, interpreter .standard_objects() .function_object() @@ -270,10 +273,10 @@ impl BuiltInFunctionObject { .expect("this should be an object") .set_prototype_instance(prototype.into()); - this.set_data(ObjectData::Function(Function::BuiltIn( - BuiltInFunction(|_, _, _| Ok(Value::undefined())), - FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, - ))); + this.set_data(ObjectData::Function(Function::Native { + function: BuiltInFunction(|_, _, _| Ok(Value::undefined())), + constructable: true, + })); Ok(this) } @@ -342,10 +345,9 @@ impl BuiltIn for BuiltInFunctionObject { let _timer = BoaProfiler::global().start_event("function", "init"); let function_prototype = context.standard_objects().function_object().prototype(); - FunctionBuilder::new(context, Self::prototype) + FunctionBuilder::native(context, Self::prototype) .name("") .length(0) - .callable(true) .constructable(false) .build_function_prototype(&function_prototype); diff --git a/boa/src/builtins/map/mod.rs b/boa/src/builtins/map/mod.rs index 7629ee0d5b6..f2aee3d1388 100644 --- a/boa/src/builtins/map/mod.rs +++ b/boa/src/builtins/map/mod.rs @@ -46,16 +46,14 @@ impl BuiltIn for Map { let to_string_tag = WellKnownSymbols::to_string_tag(); let iterator_symbol = WellKnownSymbols::iterator(); - let get_species = FunctionBuilder::new(context, Self::get_species) + let get_species = FunctionBuilder::native(context, Self::get_species) .name("get [Symbol.species]") .constructable(false) - .callable(true) .build(); - let entries_function = FunctionBuilder::new(context, Self::entries) + let entries_function = FunctionBuilder::native(context, Self::entries) .name("entries") .length(0) - .callable(true) .constructable(false) .build(); diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 2b0cef2025a..60f9c17f8b5 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -75,53 +75,44 @@ impl BuiltIn for RegExp { fn init(context: &mut Context) -> (&'static str, Value, Attribute) { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - let get_species = FunctionBuilder::new(context, Self::get_species) + let get_species = FunctionBuilder::native(context, Self::get_species) .name("get [Symbol.species]") .constructable(false) - .callable(true) .build(); let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_global = FunctionBuilder::new(context, Self::get_global) + let get_global = FunctionBuilder::native(context, Self::get_global) .name("get global") .constructable(false) - .callable(true) .build(); - let get_ignore_case = FunctionBuilder::new(context, Self::get_ignore_case) + let get_ignore_case = FunctionBuilder::native(context, Self::get_ignore_case) .name("get ignoreCase") .constructable(false) - .callable(true) .build(); - let get_multiline = FunctionBuilder::new(context, Self::get_multiline) + let get_multiline = FunctionBuilder::native(context, Self::get_multiline) .name("get multiline") .constructable(false) - .callable(true) .build(); - let get_dot_all = FunctionBuilder::new(context, Self::get_dot_all) + let get_dot_all = FunctionBuilder::native(context, Self::get_dot_all) .name("get dotAll") .constructable(false) - .callable(true) .build(); - let get_unicode = FunctionBuilder::new(context, Self::get_unicode) + let get_unicode = FunctionBuilder::native(context, Self::get_unicode) .name("get unicode") .constructable(false) - .callable(true) .build(); - let get_sticky = FunctionBuilder::new(context, Self::get_sticky) + let get_sticky = FunctionBuilder::native(context, Self::get_sticky) .name("get sticky") .constructable(false) - .callable(true) .build(); - let get_flags = FunctionBuilder::new(context, Self::get_flags) + let get_flags = FunctionBuilder::native(context, Self::get_flags) .name("get flags") .constructable(false) - .callable(true) .build(); - let get_source = FunctionBuilder::new(context, Self::get_source) + let get_source = FunctionBuilder::native(context, Self::get_source) .name("get source") .constructable(false) - .callable(true) .build(); let regexp_object = ConstructorBuilder::with_standard_object( context, diff --git a/boa/src/builtins/set/mod.rs b/boa/src/builtins/set/mod.rs index 556d2d7de80..1a52eddf9d3 100644 --- a/boa/src/builtins/set/mod.rs +++ b/boa/src/builtins/set/mod.rs @@ -39,14 +39,12 @@ impl BuiltIn for Set { fn init(context: &mut Context) -> (&'static str, Value, Attribute) { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - let get_species = FunctionBuilder::new(context, Self::get_species) + let get_species = FunctionBuilder::native(context, Self::get_species) .name("get [Symbol.species]") .constructable(false) - .callable(true) .build(); - let size_getter = FunctionBuilder::new(context, Self::size_getter) - .callable(true) + let size_getter = FunctionBuilder::native(context, Self::size_getter) .constructable(false) .name("get size") .build(); @@ -55,10 +53,9 @@ impl BuiltIn for Set { let to_string_tag = WellKnownSymbols::to_string_tag(); - let values_function = FunctionBuilder::new(context, Self::values) + let values_function = FunctionBuilder::native(context, Self::values) .name("values") .length(0) - .callable(true) .constructable(false) .build(); diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 8ae2cf469fd..25685247bec 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -97,10 +97,9 @@ impl BuiltIn for Symbol { let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - let get_description = FunctionBuilder::new(context, Self::get_description) + let get_description = FunctionBuilder::native(context, Self::get_description) .name("get description") .constructable(false) - .callable(true) .build(); let symbol_object = ConstructorBuilder::with_standard_object( diff --git a/boa/src/context.rs b/boa/src/context.rs index b4938eb9ddc..61cd30543db 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -543,10 +543,28 @@ impl Context { length: usize, body: NativeFunction, ) -> Result<()> { - let function = FunctionBuilder::new(self, body) + let function = FunctionBuilder::native(self, body) + .name(name) + .length(length) + .constructable(true) + .build(); + + self.global_object().insert_property( + name, + function, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ); + Ok(()) + } + + #[inline] + pub fn register_global_closure(&mut self, name: &str, length: usize, body: F) -> Result<()> + where + F: Fn(&Value, &[Value], &mut Context) -> Result + 'static, + { + let function = FunctionBuilder::closure(self, body) .name(name) .length(length) - .callable(true) .constructable(true) .build(); diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index bc0f7866443..63bd5b8d698 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -4,9 +4,7 @@ use super::{NativeObject, Object, PROTOTYPE}; use crate::{ - builtins::function::{ - create_unmapped_arguments_object, BuiltInFunction, Function, NativeFunction, - }, + builtins::function::{create_unmapped_arguments_object, Function, NativeFunction}, context::StandardConstructor, environment::{ environment_record_trait::EnvironmentRecordTrait, @@ -139,17 +137,21 @@ impl GcObject { .display() .to_string(); return context.throw_type_error(format!("{} is not a constructor", name)); - } else if !construct && !function.is_callable() { - return context.throw_type_error("function object is not callable"); } else { match function { - Function::BuiltIn(BuiltInFunction(function), flags) => { - if flags.is_constructable() || construct { - FunctionBody::BuiltInConstructor(*function) + Function::Native { + function, + constructable, + } => { + if *constructable || construct { + FunctionBody::BuiltInConstructor(function.0) } else { - FunctionBody::BuiltInFunction(*function) + FunctionBody::BuiltInFunction(function.0) } } + Function::Closure { function, .. } => { + return (function)(this_target, args, context); + } Function::Ordinary { body, params, diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index f3248b0b561..8f822d66c8c 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -3,7 +3,7 @@ use crate::{ builtins::{ array::array_iterator::ArrayIterator, - function::{BuiltInFunction, Function, FunctionFlags, NativeFunction}, + function::{Function, NativeFunction}, map::map_iterator::MapIterator, map::ordered_map::OrderedMap, regexp::regexp_string_iterator::RegExpStringIterator, @@ -264,7 +264,7 @@ impl Object { /// [spec]: https://tc39.es/ecma262/#sec-iscallable #[inline] pub fn is_callable(&self) -> bool { - matches!(self.data, ObjectData::Function(ref f) if f.is_callable()) + matches!(self.data, ObjectData::Function(_)) } /// It determines if Object is a function object with a `[[Construct]]` internal method. @@ -682,24 +682,39 @@ where #[derive(Debug)] pub struct FunctionBuilder<'context> { context: &'context mut Context, - function: BuiltInFunction, + function: Option, name: Option, length: usize, - callable: bool, - constructable: bool, } impl<'context> FunctionBuilder<'context> { /// Create a new `FunctionBuilder` #[inline] - pub fn new(context: &'context mut Context, function: NativeFunction) -> Self { + pub fn native(context: &'context mut Context, function: NativeFunction) -> Self { Self { context, - function: function.into(), + function: Some(Function::Native { + function: function.into(), + constructable: false, + }), + name: None, + length: 0, + } + } + + #[inline] + pub fn closure(context: &'context mut Context, function: F) -> Self + where + F: Fn(&Value, &[Value], &mut Context) -> Result + 'static, + { + Self { + context, + function: Some(Function::Closure { + function: Box::new(function), + constructable: false, + }), name: None, length: 0, - callable: true, - constructable: false, } } @@ -726,21 +741,16 @@ impl<'context> FunctionBuilder<'context> { self } - /// Specify the whether the object function object can be called. - /// - /// The default is `true`. - #[inline] - pub fn callable(&mut self, yes: bool) -> &mut Self { - self.callable = yes; - self - } - /// Specify the whether the object function object can be called with `new` keyword. /// /// The default is `false`. #[inline] pub fn constructable(&mut self, yes: bool) -> &mut Self { - self.constructable = yes; + match self.function.as_mut() { + Some(Function::Native { constructable, .. }) => *constructable = yes, + Some(Function::Closure { constructable, .. }) => *constructable = yes, + _ => unreachable!(), + } self } @@ -748,10 +758,7 @@ impl<'context> FunctionBuilder<'context> { #[inline] pub fn build(&mut self) -> GcObject { let mut function = Object::function( - Function::BuiltIn( - self.function, - FunctionFlags::from_parameters(self.callable, self.constructable), - ), + self.function.take().unwrap(), self.context .standard_objects() .function_object() @@ -772,10 +779,7 @@ impl<'context> FunctionBuilder<'context> { /// Initializes the `Function.prototype` function object. pub(crate) fn build_function_prototype(&mut self, object: &GcObject) { let mut object = object.borrow_mut(); - object.data = ObjectData::Function(Function::BuiltIn( - self.function, - FunctionFlags::from_parameters(self.callable, self.constructable), - )); + object.data = ObjectData::Function(self.function.take().unwrap()); object.set_prototype_instance( self.context .standard_objects() @@ -783,7 +787,7 @@ impl<'context> FunctionBuilder<'context> { .prototype() .into(), ); - let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; + let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; if let Some(name) = self.name.take() { object.insert_property("name", name, attribute); } else { @@ -836,10 +840,9 @@ impl<'context> ObjectInitializer<'context> { B: Into, { let binding = binding.into(); - let function = FunctionBuilder::new(self.context, function) + let function = FunctionBuilder::native(self.context, function) .name(binding.name) .length(length) - .callable(true) .constructable(false) .build(); @@ -940,10 +943,9 @@ impl<'context> ConstructorBuilder<'context> { B: Into, { let binding = binding.into(); - let function = FunctionBuilder::new(self.context, function) + let function = FunctionBuilder::native(self.context, function) .name(binding.name) .length(length) - .callable(true) .constructable(false) .build(); @@ -967,10 +969,9 @@ impl<'context> ConstructorBuilder<'context> { B: Into, { let binding = binding.into(); - let function = FunctionBuilder::new(self.context, function) + let function = FunctionBuilder::native(self.context, function) .name(binding.name) .length(length) - .callable(true) .constructable(false) .build(); @@ -1122,10 +1123,10 @@ impl<'context> ConstructorBuilder<'context> { /// Build the constructor function object. pub fn build(&mut self) -> GcObject { // Create the native function - let function = Function::BuiltIn( - self.constructor_function.into(), - FunctionFlags::from_parameters(self.callable, self.constructable), - ); + let function = Function::Native { + function: self.constructor_function.into(), + constructable: self.constructable, + }; let length = DataDescriptor::new( self.length, diff --git a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs index 5645727d722..217c460af75 100644 --- a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs @@ -76,9 +76,7 @@ impl Executable for ArrowFunctionDecl { context.create_function( self.params().to_vec(), self.body().to_vec(), - FunctionFlags::CALLABLE - | FunctionFlags::CONSTRUCTABLE - | FunctionFlags::LEXICAL_THIS_MODE, + FunctionFlags::CONSTRUCTABLE | FunctionFlags::LEXICAL_THIS_MODE, ) } } diff --git a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs index 355d0d0fa0f..2a3df2041ac 100644 --- a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -91,7 +91,7 @@ impl Executable for FunctionDecl { let val = context.create_function( self.parameters().to_vec(), self.body().to_vec(), - FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, + FunctionFlags::CONSTRUCTABLE, )?; // Set the name and assign it in the current environment diff --git a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs index d409beb3827..bb6ea404429 100644 --- a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs @@ -102,7 +102,7 @@ impl Executable for FunctionExpr { let val = context.create_function( self.parameters().to_vec(), self.body().to_vec(), - FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, + FunctionFlags::CONSTRUCTABLE, )?; if let Some(name) = self.name() { diff --git a/boa/src/value/operations.rs b/boa/src/value/operations.rs index 87fe39c4138..88040bace20 100644 --- a/boa/src/value/operations.rs +++ b/boa/src/value/operations.rs @@ -11,9 +11,6 @@ impl Value { (Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) + y), (Self::Rational(x), Self::Integer(y)) => Self::rational(x + f64::from(*y)), - (Self::String(ref x), Self::String(ref y)) => Self::string(format!("{}{}", x, y)), - (Self::String(ref x), y) => Self::string(format!("{}{}", x, y.to_string(context)?)), - (x, Self::String(ref y)) => Self::string(format!("{}{}", x.to_string(context)?, y)), (Self::String(ref x), Self::String(ref y)) => Self::from(JsString::concat(x, y)), (Self::String(ref x), y) => Self::from(JsString::concat(x, y.to_string(context)?)), (x, Self::String(ref y)) => Self::from(JsString::concat(x.to_string(context)?, y)),