From 62fa5da2877bcacc612918ad9b7e1fdca47f94be Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 10 Oct 2021 18:17:30 -0500 Subject: [PATCH] Enable mutation for `Closure` type inside closures --- boa/examples/closures.rs | 15 +- boa/src/builtins/array_buffer/mod.rs | 41 ++- boa/src/builtins/function/mod.rs | 86 ++--- boa/src/builtins/regexp/mod.rs | 2 +- boa/src/context.rs | 2 +- boa/src/object/internal_methods/function.rs | 363 +++++++++++--------- boa/src/object/mod.rs | 11 +- boa/src/vm/code_block.rs | 291 +++++++++------- 8 files changed, 441 insertions(+), 370 deletions(-) diff --git a/boa/examples/closures.rs b/boa/examples/closures.rs index 36eddf4099a..c4d49eb2045 100644 --- a/boa/examples/closures.rs +++ b/boa/examples/closures.rs @@ -21,6 +21,7 @@ fn main() -> Result<(), JsValue> { 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)) @@ -52,7 +53,7 @@ fn main() -> Result<(), JsValue> { // Now, we execute some operations that return a `Clone` type let clone_variable = BigStruct { - greeting: JsString::from("Hello from Javascript!"), + greeting: JsString::from("Hello!"), object, }; @@ -73,7 +74,11 @@ fn main() -> Result<(), JsValue> { captures.greeting.as_str(), ]); + // We can also mutate the moved data inside the closure. + captures.greeting = format!("{} Hello!", captures.greeting).into(); + println!("{}", message); + println!(); // We convert `message` into `Jsvalue` to be able to return it. Ok(message.into()) @@ -100,7 +105,13 @@ fn main() -> Result<(), JsValue> { assert_eq!( context.eval("createMessage()")?, - "message from `Boa dev`: Hello from Javascript!".into() + "message from `Boa dev`: Hello!".into() + ); + + // The data mutates between calls + assert_eq!( + context.eval("createMessage(); createMessage();")?, + "message from `Boa dev`: Hello! Hello! Hello!".into() ); // We have moved `Clone` variables into a closure and executed that closure diff --git a/boa/src/builtins/array_buffer/mod.rs b/boa/src/builtins/array_buffer/mod.rs index c4c3f158117..acb57174c64 100644 --- a/boa/src/builtins/array_buffer/mod.rs +++ b/boa/src/builtins/array_buffer/mod.rs @@ -232,32 +232,35 @@ impl ArrayBuffer { let new = ctor.construct(&[new_len.into()], &ctor.clone().into(), context)?; // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). - let new_obj = if let Some(obj) = new.as_object() { - obj.clone() - } else { - return context.throw_type_error("ArrayBuffer constructor returned non-object value"); - }; - let mut new_obj_borrow = new_obj.borrow_mut(); - let new_array_buffer = if let Some(b) = new_obj_borrow.as_array_buffer_mut() { - b - } else { - return context.throw_type_error("ArrayBuffer constructor returned invalid object"); - }; - - // TODO: Shared Array Buffer - // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. + let new_obj = new.as_object().cloned().ok_or_else(|| { + context.construct_type_error("ArrayBuffer constructor returned non-object value") + })?; - // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. - if Self::is_detached_buffer(new_array_buffer) { - return context - .throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer"); + { + let new_obj = new_obj.borrow(); + let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| { + context.construct_type_error("ArrayBuffer constructor returned invalid object") + })?; + + // TODO: Shared Array Buffer + // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. + + // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. + if new_array_buffer.is_detached_buffer() { + return context + .throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer"); + } } - // 20. If SameValue(new, O) is true, throw a TypeError exception. if JsValue::same_value(&new, this) { return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer"); } + let mut new_obj_borrow = new_obj.borrow_mut(); + let new_array_buffer = new_obj_borrow + .as_array_buffer_mut() + .expect("Already checked that `new_obj` was an `ArrayBuffer`"); + // 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception. if new_array_buffer.array_buffer_byte_length < new_len { return context.throw_type_error("New ArrayBuffer length too small"); diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index c7d5f5559b2..4d74eaad031 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -12,14 +12,15 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function use std::{ + any::Any, borrow::Cow, fmt, ops::{Deref, DerefMut}, }; use dyn_clone::DynClone; +use gc::{Gc, GcCell}; -use crate::value::IntegerOrInfinity; use crate::{ builtins::BuiltIn, context::StandardObjects, @@ -38,6 +39,10 @@ use crate::{ property::PropertyKey, JsString, }; +use crate::{ + object::{Ref, RefMut}, + value::IntegerOrInfinity, +}; use super::JsArgs; @@ -67,16 +72,12 @@ impl DynCopy for T {} /// 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], &mut Captures, &mut Context) -> JsResult - + DynCopy - + DynClone - + 'static + Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + DynCopy + DynClone + 'static { } -// The `Copy` bound automatically infers `DynCopy` and `DynClone` impl ClosureFunctionSignature for T where - T: Fn(&JsValue, &[JsValue], &mut Captures, &mut Context) -> JsResult + Copy + 'static + T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + Copy + 'static { } @@ -125,19 +126,18 @@ impl ConstructorKind { } } -/// Wrapper for `Box` that allows passing additional +/// 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 downcast to any type and handle the fail case as you like -/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref` -/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast -/// fails. -#[derive(Debug, Trace, Finalize)] -pub struct Captures(Box); +/// 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. @@ -145,51 +145,25 @@ impl Captures { where T: NativeObject, { - Self(Box::new(captures)) - } - - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or `None` otherwise. - pub fn downcast_ref(&self) -> Option<&T> - where - T: NativeObject, - { - self.0.deref().as_any().downcast_ref::() + Self(Gc::new(GcCell::new(Box::new(captures)))) } - /// Mutably downcasts `Captures` to the specified type, returning a - /// mutable reference to the downcasted type if successful or `None` otherwise. - pub fn downcast_mut(&mut self) -> Option<&mut T> - where - T: NativeObject, - { - self.0.deref_mut().as_mut_any().downcast_mut::() - } - - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or a `TypeError` otherwise. - pub fn try_downcast_ref(&self, context: &mut Context) -> JsResult<&T> - where - T: NativeObject, - { - self.0 - .deref() - .as_any() - .downcast_ref::() - .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) + /// Casts `Captures` to `Any` + /// + /// # Panics + /// + /// Panics if it's already borrowed as `&mut Any` + pub fn as_any(&self) -> gc::GcCellRef<'_, dyn Any> { + Ref::map(self.0.borrow(), |data| data.deref().as_any()) } - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or a `TypeError` otherwise. - pub fn try_downcast_mut(&mut self, context: &mut Context) -> JsResult<&mut T> - where - T: NativeObject, - { - self.0 - .deref_mut() - .as_mut_any() - .downcast_mut::() - .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) + /// Mutably casts `Captures` to `Any` + /// + /// # Panics + /// + /// Panics if it's already borrowed as `&mut Any` + pub fn as_mut_any(&self) -> gc::GcCellRefMut<'_, Box, dyn Any> { + RefMut::map(self.0.borrow_mut(), |data| data.deref_mut().as_mut_any()) } } @@ -220,7 +194,7 @@ pub enum Function { }, #[cfg(feature = "vm")] VmOrdinary { - code: gc::Gc, + code: Gc, environment: Environment, }, } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index b96db6a956e..f3d19a572f8 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -1596,7 +1596,7 @@ impl RegExp { let splitter = splitter .as_object() // todo: remove when we handle realms on `get_prototype_from_constructor` and make `construct` always return a `JsObject` - .ok_or_else(||context.construct_type_error("constructor did not return an object"))?; + .ok_or_else(|| context.construct_type_error("constructor did not return an object"))?; // 11. Let A be ! ArrayCreate(0). let a = Array::array_create(0, None, context).unwrap(); diff --git a/boa/src/context.rs b/boa/src/context.rs index 740d8c5a04a..636da4b2778 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -518,7 +518,7 @@ impl Context { args: &[JsValue], ) -> JsResult { f.as_callable() - .ok_or_else(|| self.construct_type_error("not a function")) + .ok_or_else(|| self.construct_type_error("Value is not callable")) .and_then(|obj| obj.call(this, args, self)) } diff --git a/boa/src/object/internal_methods/function.rs b/boa/src/object/internal_methods/function.rs index 38901ef5def..40bcaf527ef 100644 --- a/boa/src/object/internal_methods/function.rs +++ b/boa/src/object/internal_methods/function.rs @@ -1,11 +1,12 @@ use crate::{ - builtins::{function::Function, JsArgs}, + builtins::function::{Captures, ClosureFunctionSignature, Function, NativeFunctionSignature}, environment::{ function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, lexical_environment::Environment, }, exec::{Executable, InterpreterState}, object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + syntax::ast::node::RcStatementList, Context, JsResult, JsValue, }; @@ -83,153 +84,77 @@ pub(super) fn call_construct( context: &mut Context, construct: bool, ) -> JsResult { - let name = obj.get("name", context)?.display().to_string(); + /// The body of a JavaScript function. + /// + /// This is needed for the call method since we cannot mutate the function itself since we + /// already borrow it so we get the function body clone it then drop the borrow and run the body + enum FunctionBody { + BuiltInFunction(NativeFunctionSignature), + BuiltInConstructor(NativeFunctionSignature), + Closure { + function: Box, + captures: Captures, + }, + Ordinary(RcStatementList), + } + let this_function_object = obj.clone(); let mut has_parameter_expressions = false; - // This is needed for the call method since we cannot mutate the function since we - // already borrow it so we get the function body clone it then drop the borrow and run the body - let body = { - let mut mut_obj = obj.borrow_mut(); - let function = mut_obj - .as_function_mut() - .ok_or_else(|| context.construct_type_error("not a function"))?; - + let body = if let Some(function) = obj.borrow().as_function() { if construct && !function.is_constructor() { + let name = obj.get("name", context)?.display().to_string(); return context.throw_type_error(format!("{} is not a constructor", name)); - } - - match function { - Function::Native { - function, - constructor, - } => { - return if !*constructor || construct { - function(this_target, args, context) - } else { - function(&JsValue::Undefined, args, context) - }; - } - Function::Closure { - function, captures, .. - } => return (function)(this_target, args, captures, context), - Function::Ordinary { - constructor: _, - this_mode, - body, - params, - environment, - } => { - let this = if construct { - // If the prototype of the constructor is not an object, then use the default object - // prototype as prototype for the new object - // see - // see - let proto = get_prototype_from_constructor( - this_target, - StandardObjects::object_object, - context, - )?; - JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary()).into() - } else { - this_target.clone() - }; - - // Create a new Function environment whose parent is set to the scope of the function declaration (obj.environment) - // - let local_env = FunctionEnvironmentRecord::new( - this_function_object.clone(), - if construct || !this_mode.is_lexical() { - Some(this.clone()) - } else { - None - }, - Some(environment.clone()), - // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records - if this_mode.is_lexical() { - BindingStatus::Lexical + } else { + match function { + Function::Native { + function, + constructor, + } => { + if *constructor || construct { + FunctionBody::BuiltInConstructor(*function) } else { - BindingStatus::Uninitialized - }, - JsValue::undefined(), - context, - )?; - - let mut arguments_in_parameter_names = false; - let mut is_simple_parameter_list = true; - - for param in params.iter() { - has_parameter_expressions = has_parameter_expressions || param.init().is_some(); - arguments_in_parameter_names = - arguments_in_parameter_names || param.name() == "arguments"; - is_simple_parameter_list = - is_simple_parameter_list && !param.is_rest_param() && param.init().is_none() - } - - // Turn local_env into Environment so it can be cloned - let local_env: Environment = local_env.into(); - - // An arguments object is added when all of the following conditions are met - // - If not in an arrow function (10.2.11.16) - // - If the parameter list does not contain `arguments` (10.2.11.17) - // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18) - // - // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation - if !this_mode.is_lexical() - && !arguments_in_parameter_names - && (has_parameter_expressions - || (!body.lexically_declared_names().contains("arguments") - && !body.function_declared_names().contains("arguments"))) - { - // Add arguments object - let arguments_obj = - if context.strict() || body.strict() || !is_simple_parameter_list { - Arguments::create_unmapped_arguments_object(args, context) - } else { - Arguments::create_mapped_arguments_object( - obj, params, args, &local_env, context, - ) - }; - local_env.create_mutable_binding("arguments", false, true, context)?; - local_env.initialize_binding("arguments", arguments_obj.into(), context)?; - } - - // Push the environment first so that it will be used by default parameters - context.push_environment(local_env.clone()); - - // Add argument bindings to the function environment - for (i, param) in params.iter().enumerate() { - // Rest Parameters - if param.is_rest_param() { - Function::add_rest_param(param, i, args, context, &local_env); - break; + FunctionBody::BuiltInFunction(*function) } - - let value = match args.get_or_undefined(i).clone() { - JsValue::Undefined => param - .init() - .map(|init| init.run(context).ok()) - .flatten() - .unwrap_or_default(), - value => value, - }; - - Function::add_arguments_to_environment(param, value, &local_env, context); } + Function::Closure { + function, captures, .. + } => FunctionBody::Closure { + function: function.clone(), + captures: captures.clone(), + }, + Function::Ordinary { + constructor: _, + this_mode, + body, + params, + environment, + } => { + let this = if construct { + // If the prototype of the constructor is not an object, then use the default object + // prototype as prototype for the new object + // see + // see + let proto = get_prototype_from_constructor( + this_target, + StandardObjects::object_object, + context, + )?; + JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary()).into() + } else { + this_target.clone() + }; - if has_parameter_expressions { - // Create a second environment when default parameter expressions are used - // This prevents variables declared in the function body from being - // used in default parameter initializers. - // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation - let second_env = FunctionEnvironmentRecord::new( - this_function_object, + // Create a new Function environment whose parent is set to the scope of the function declaration (obj.environment) + // + let local_env = FunctionEnvironmentRecord::new( + this_function_object.clone(), if construct || !this_mode.is_lexical() { - Some(this) + Some(this.clone()) } else { None }, - Some(local_env), + Some(environment.clone()), // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records if this_mode.is_lexical() { BindingStatus::Lexical @@ -239,47 +164,151 @@ pub(super) fn call_construct( JsValue::undefined(), context, )?; - context.push_environment(second_env); - } - body.clone() - } - #[cfg(feature = "vm")] - Function::VmOrdinary { .. } => { - todo!("vm call") + let mut arguments_in_parameter_names = false; + let mut is_simple_parameter_list = true; + + for param in params.iter() { + has_parameter_expressions = + has_parameter_expressions || param.init().is_some(); + arguments_in_parameter_names = + arguments_in_parameter_names || param.name() == "arguments"; + is_simple_parameter_list = is_simple_parameter_list + && !param.is_rest_param() + && param.init().is_none() + } + + // Turn local_env into Environment so it can be cloned + let local_env: Environment = local_env.into(); + + // An arguments object is added when all of the following conditions are met + // - If not in an arrow function (10.2.11.16) + // - If the parameter list does not contain `arguments` (10.2.11.17) + // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18) + // + // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation + if !this_mode.is_lexical() + && !arguments_in_parameter_names + && (has_parameter_expressions + || (!body.lexically_declared_names().contains("arguments") + && !body.function_declared_names().contains("arguments"))) + { + // Add arguments object + let arguments_obj = + if context.strict() || body.strict() || !is_simple_parameter_list { + Arguments::create_unmapped_arguments_object(args, context) + } else { + Arguments::create_mapped_arguments_object( + obj, params, args, &local_env, context, + ) + }; + local_env.create_mutable_binding("arguments", false, true, context)?; + local_env.initialize_binding("arguments", arguments_obj.into(), context)?; + } + + // Push the environment first so that it will be used by default parameters + context.push_environment(local_env.clone()); + + // Add argument bindings to the function environment + for (i, param) in params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param() { + Function::add_rest_param(param, i, args, context, &local_env); + break; + } + + let value = match args.get(i).cloned() { + None | Some(JsValue::Undefined) => param + .init() + .map(|init| init.run(context).ok()) + .flatten() + .unwrap_or_default(), + Some(value) => value, + }; + + Function::add_arguments_to_environment(param, value, &local_env, context); + } + + if has_parameter_expressions { + // Create a second environment when default parameter expressions are used + // This prevents variables declared in the function body from being + // used in default parameter initializers. + // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation + let second_env = FunctionEnvironmentRecord::new( + this_function_object, + if construct || !this_mode.is_lexical() { + Some(this) + } else { + None + }, + Some(local_env), + // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records + if this_mode.is_lexical() { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + JsValue::undefined(), + context, + )?; + context.push_environment(second_env); + } + + FunctionBody::Ordinary(body.clone()) + } + #[cfg(feature = "vm")] + Function::VmOrdinary { .. } => { + todo!("vm call") + } } } + } else { + return context.throw_type_error("not a function"); }; - let result = body.run(context); - let this = context.get_this_binding(); + match body { + FunctionBody::BuiltInConstructor(function) if construct => { + function(this_target, args, context) + } + FunctionBody::BuiltInConstructor(function) => { + function(&JsValue::undefined(), args, context) + } + FunctionBody::BuiltInFunction(function) => function(this_target, args, context), + FunctionBody::Closure { function, captures } => { + (function)(this_target, args, captures, context) + } + FunctionBody::Ordinary(body) => { + let result = body.run(context); + let this = context.get_this_binding(); - if has_parameter_expressions { - context.pop_environment(); - } - context.pop_environment(); + if has_parameter_expressions { + context.pop_environment(); + } + context.pop_environment(); - if construct { - // https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget - // 12. If result.[[Type]] is return, then - if context.executor().get_current_state() == &InterpreterState::Return { - // a. If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]). - if let Ok(v) = &result { - if v.is_object() { - return result; + if construct { + // https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget + // 12. If result.[[Type]] is return, then + if context.executor().get_current_state() == &InterpreterState::Return { + // a. If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]). + if let Ok(v) = &result { + if v.is_object() { + return result; + } + } } - } - } - // 13. Else, ReturnIfAbrupt(result). - result?; + // 13. Else, ReturnIfAbrupt(result). + result?; - // 14. Return ? constructorEnv.GetThisBinding(). - this - } else if context.executor().get_current_state() == &InterpreterState::Return { - result - } else { - result?; - Ok(JsValue::undefined()) + // 14. Return ? constructorEnv.GetThisBinding(). + this + } else if context.executor().get_current_state() == &InterpreterState::Return { + result + } else { + result?; + Ok(JsValue::undefined()) + } + } } } diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index d75a08be14a..a22a970a97e 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -1203,14 +1203,17 @@ impl<'context> FunctionBuilder<'context> { ) -> Self where F: Fn(&JsValue, &[JsValue], &mut C, &mut Context) -> JsResult + Copy + 'static, - C: NativeObject + Clone, + C: NativeObject, { Self { context, function: Some(Function::Closure { - function: Box::new(move |this, args, captures: &mut Captures, context| { - let data = captures.try_downcast_mut::(context)?; - function(this, args, data, context) + function: Box::new(move |this, args, captures: Captures, context| { + let mut captures = captures.as_mut_any(); + let captures = captures.downcast_mut::().ok_or_else(|| { + context.construct_type_error("cannot downcast `Captures` to given type") + })?; + function(this, args, captures, context) }), constructor: false, captures: Captures::new(captures), diff --git a/boa/src/vm/code_block.rs b/boa/src/vm/code_block.rs index 3514496d209..4fa45f962ce 100644 --- a/boa/src/vm/code_block.rs +++ b/boa/src/vm/code_block.rs @@ -1,5 +1,7 @@ use crate::{ - builtins::function::{Function, ThisMode}, + builtins::function::{ + Captures, ClosureFunctionSignature, Function, NativeFunctionSignature, ThisMode, + }, context::StandardObjects, environment::{ function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, @@ -343,6 +345,21 @@ impl JsVmFunction { } } +pub(crate) enum FunctionBody { + Ordinary { + code: Gc, + environment: Environment, + }, + Native { + function: NativeFunctionSignature, + }, + Closure { + function: Box, + captures: Captures, + }, +} + +// TODO: this should be modified to not take `exit_on_return` and then moved to `internal_methods` impl JsObject { pub(crate) fn call_internal( &self, @@ -358,77 +375,94 @@ impl JsObject { return context.throw_type_error("not a callable function"); } - let (code, environment) = { - let mut object = self.borrow_mut(); - let function = object.as_function_mut().unwrap(); + let body = { + let object = self.borrow(); + let function = object.as_function().unwrap(); match function { - Function::Native { function, .. } => return function(this, args, context), + Function::Native { function, .. } => FunctionBody::Native { + function: *function, + }, Function::Closure { function, captures, .. - } => return (function)(this, args, captures, context), - Function::VmOrdinary { code, environment } => (code.clone(), environment.clone()), + } => FunctionBody::Closure { + function: function.clone(), + captures: captures.clone(), + }, + Function::VmOrdinary { code, environment } => FunctionBody::Ordinary { + code: code.clone(), + environment: environment.clone(), + }, Function::Ordinary { .. } => unreachable!(), } }; - let lexical_this_mode = code.this_mode == ThisMode::Lexical; - - // Create a new Function environment whose parent is set to the scope of the function declaration (self.environment) - // - let local_env = FunctionEnvironmentRecord::new( - this_function_object, - if !lexical_this_mode { - Some(this.clone()) - } else { - None - }, - Some(environment.clone()), - // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records - if lexical_this_mode { - BindingStatus::Lexical - } else { - BindingStatus::Uninitialized - }, - JsValue::undefined(), - context, - )?; - // Turn local_env into Environment so it can be cloned - let local_env: Environment = local_env.into(); + match body { + FunctionBody::Native { function } => function(this, args, context), + FunctionBody::Closure { function, captures } => { + (function)(this, args, captures, context) + } + FunctionBody::Ordinary { code, environment } => { + let lexical_this_mode = code.this_mode == ThisMode::Lexical; + + // Create a new Function environment whose parent is set to the scope of the function declaration (self.environment) + // + let local_env = FunctionEnvironmentRecord::new( + this_function_object, + if !lexical_this_mode { + Some(this.clone()) + } else { + None + }, + Some(environment.clone()), + // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records + if lexical_this_mode { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + JsValue::undefined(), + context, + )?; - // Push the environment first so that it will be used by default parameters - context.push_environment(local_env.clone()); + // Turn local_env into Environment so it can be cloned + let local_env: Environment = local_env.into(); - // Add argument bindings to the function environment - for (i, param) in code.params.iter().enumerate() { - // Rest Parameters - if param.is_rest_param() { - todo!("Rest parameter"); - } + // Push the environment first so that it will be used by default parameters + context.push_environment(local_env.clone()); - let value = match args.get(i).cloned() { - None => JsValue::undefined(), - Some(value) => value, - }; + // Add argument bindings to the function environment + for (i, param) in code.params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param() { + todo!("Rest parameter"); + } - Function::add_arguments_to_environment(param, value, &local_env, context); - } + let value = match args.get(i).cloned() { + None => JsValue::undefined(), + Some(value) => value, + }; + + Function::add_arguments_to_environment(param, value, &local_env, context); + } - context.vm.push_frame(CallFrame { - prev: None, - code, - this: this.clone(), - pc: 0, - fp: context.vm.stack.len(), - exit_on_return, - environment: local_env, - }); + context.vm.push_frame(CallFrame { + prev: None, + code, + this: this.clone(), + pc: 0, + fp: context.vm.stack.len(), + exit_on_return, + environment: local_env, + }); - let result = context.run(); + let result = context.run(); - context.pop_environment(); + context.pop_environment(); - result + result + } + } } pub fn call( @@ -451,88 +485,105 @@ impl JsObject { // let mut has_parameter_expressions = false; if !self.is_constructor() { - return context.throw_type_error("not a constructable function"); + return context.throw_type_error("not a constructor function"); } - let (code, environment) = { - let mut object = self.borrow_mut(); - let function = object.as_function_mut().unwrap(); + let body = { + let object = self.borrow(); + let function = object.as_function().unwrap(); match function { - Function::Native { function, .. } => return function(this_target, args, context), + Function::Native { function, .. } => FunctionBody::Native { + function: *function, + }, Function::Closure { function, captures, .. - } => return (function)(this_target, args, captures, context), - Function::VmOrdinary { code, environment } => (code.clone(), environment.clone()), + } => FunctionBody::Closure { + function: function.clone(), + captures: captures.clone(), + }, + Function::VmOrdinary { code, environment } => FunctionBody::Ordinary { + code: code.clone(), + environment: environment.clone(), + }, Function::Ordinary { .. } => unreachable!(), } }; - let this: JsValue = { - // If the prototype of the constructor is not an object, then use the default object - // prototype as prototype for the new object - // see - // see - let prototype = get_prototype_from_constructor( - this_target, - StandardObjects::object_object, - context, - )?; - JsObject::from_proto_and_data(prototype, ObjectData::ordinary()).into() - }; - let lexical_this_mode = code.this_mode == ThisMode::Lexical; - - // Create a new Function environment whose parent is set to the scope of the function declaration (self.environment) - // - let local_env = FunctionEnvironmentRecord::new( - this_function_object, - Some(this.clone()), - Some(environment), - // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records - if lexical_this_mode { - BindingStatus::Lexical - } else { - BindingStatus::Uninitialized - }, - JsValue::undefined(), - context, - )?; - // Turn local_env into Environment so it can be cloned - let local_env: Environment = local_env.into(); + match body { + FunctionBody::Native { function, .. } => function(this_target, args, context), + FunctionBody::Closure { function, captures } => { + (function)(this_target, args, captures, context) + } + FunctionBody::Ordinary { code, environment } => { + let this: JsValue = { + // If the prototype of the constructor is not an object, then use the default object + // prototype as prototype for the new object + // see + // see + let prototype = get_prototype_from_constructor( + this_target, + StandardObjects::object_object, + context, + )?; + JsObject::from_proto_and_data(prototype, ObjectData::ordinary()).into() + }; + let lexical_this_mode = code.this_mode == ThisMode::Lexical; + + // Create a new Function environment whose parent is set to the scope of the function declaration (self.environment) + // + let local_env = FunctionEnvironmentRecord::new( + this_function_object, + Some(this.clone()), + Some(environment), + // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records + if lexical_this_mode { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + JsValue::undefined(), + context, + )?; + + // Turn local_env into Environment so it can be cloned + let local_env: Environment = local_env.into(); - // Push the environment first so that it will be used by default parameters - context.push_environment(local_env.clone()); + // Push the environment first so that it will be used by default parameters + context.push_environment(local_env.clone()); - // Add argument bindings to the function environment - for (i, param) in code.params.iter().enumerate() { - // Rest Parameters - if param.is_rest_param() { - todo!("Rest parameter"); - } + // Add argument bindings to the function environment + for (i, param) in code.params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param() { + todo!("Rest parameter"); + } - let value = match args.get(i).cloned() { - None => JsValue::undefined(), - Some(value) => value, - }; + let value = match args.get(i).cloned() { + None => JsValue::undefined(), + Some(value) => value, + }; - Function::add_arguments_to_environment(param, value, &local_env, context); - } + Function::add_arguments_to_environment(param, value, &local_env, context); + } - context.vm.push_frame(CallFrame { - prev: None, - code, - this, - pc: 0, - fp: context.vm.stack.len(), - exit_on_return, - environment: local_env, - }); + context.vm.push_frame(CallFrame { + prev: None, + code, + this, + pc: 0, + fp: context.vm.stack.len(), + exit_on_return, + environment: local_env, + }); - let _result = context.run(); + let _result = context.run(); - context.pop_environment(); + context.pop_environment(); - context.get_this_binding() + context.get_this_binding() + } + } } pub fn construct(