From 8b32522ffa46d861eeb13069bd9c311e0df556d4 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Wed, 15 Apr 2020 19:21:10 +0100 Subject: [PATCH 1/6] implement this --- .../declarative_environment_record.rs | 4 +++ .../environment/environment_record_trait.rs | 3 ++ .../function_environment_record.rs | 30 +++++++++---------- .../environment/global_environment_record.rs | 8 ++--- boa/src/environment/lexical_environment.rs | 5 ++++ .../environment/object_environment_record.rs | 4 +++ boa/src/exec/mod.rs | 4 +++ 7 files changed, 39 insertions(+), 19 deletions(-) diff --git a/boa/src/environment/declarative_environment_record.rs b/boa/src/environment/declarative_environment_record.rs index 5b36143d8fb..f8ce882f971 100644 --- a/boa/src/environment/declarative_environment_record.rs +++ b/boa/src/environment/declarative_environment_record.rs @@ -149,6 +149,10 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { false } + fn get_this_binding(&self) -> Value { + Gc::new(ValueData::Undefined) + } + fn has_super_binding(&self) -> bool { false } diff --git a/boa/src/environment/environment_record_trait.rs b/boa/src/environment/environment_record_trait.rs index f2166289e1b..297ecb2215d 100644 --- a/boa/src/environment/environment_record_trait.rs +++ b/boa/src/environment/environment_record_trait.rs @@ -60,6 +60,9 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { /// Return true if it does and false if it does not. fn has_this_binding(&self) -> bool; + /// Return the `this` binding from the environment + fn get_this_binding(&self) -> Value; + /// Determine if an Environment Record establishes a super method binding. /// Return true if it does and false if it does not. fn has_super_binding(&self) -> bool; diff --git a/boa/src/environment/function_environment_record.rs b/boa/src/environment/function_environment_record.rs index e7027372f6d..71dcd4faf6c 100644 --- a/boa/src/environment/function_environment_record.rs +++ b/boa/src/environment/function_environment_record.rs @@ -74,21 +74,6 @@ impl FunctionEnvironmentRecord { } } } - - pub fn get_this_binding(&self) -> Value { - match self.this_binding_status { - BindingStatus::Lexical => { - // TODO: change this when error handling comes into play - panic!("There is no this for a lexical function record"); - } - BindingStatus::Uninitialized => { - // TODO: change this when error handling comes into play - panic!("Reference Error: Unitialised binding for this function"); - } - - BindingStatus::Initialized => self.this_value.clone(), - } - } } impl EnvironmentRecordTrait for FunctionEnvironmentRecord { @@ -115,6 +100,21 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord { ); } + fn get_this_binding(&self) -> Value { + match self.this_binding_status { + BindingStatus::Lexical => { + // TODO: change this when error handling comes into play + panic!("There is no this for a lexical function record"); + } + BindingStatus::Uninitialized => { + // TODO: change this when error handling comes into play + panic!("Reference Error: Unitialised binding for this function"); + } + + BindingStatus::Initialized => self.this_value.clone(), + } + } + fn create_immutable_binding(&mut self, name: String, strict: bool) -> bool { if self.env_rec.contains_key(&name) { // TODO: change this when error handling comes into play diff --git a/boa/src/environment/global_environment_record.rs b/boa/src/environment/global_environment_record.rs index 079c69a9293..7f79566c2e1 100644 --- a/boa/src/environment/global_environment_record.rs +++ b/boa/src/environment/global_environment_record.rs @@ -28,10 +28,6 @@ pub struct GlobalEnvironmentRecord { } impl GlobalEnvironmentRecord { - pub fn get_this_binding(&self) -> Value { - self.global_this_binding.clone() - } - pub fn has_var_declaration(&self, name: &str) -> bool { self.var_names.contains(name) } @@ -96,6 +92,10 @@ impl GlobalEnvironmentRecord { } impl EnvironmentRecordTrait for GlobalEnvironmentRecord { + fn get_this_binding(&self) -> Value { + self.global_this_binding.clone() + } + fn has_binding(&self, name: &str) -> bool { if self.declarative_record.has_binding(name) { return true; diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 487820f4aaf..21b181bee2a 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -113,6 +113,11 @@ impl LexicalEnvironment { .get_global_object() } + pub fn get_this_binding(&self) -> Value { + let env = self.environment_stack.get(0).expect("").borrow(); + env.get_this_binding() + } + pub fn create_mutable_binding(&mut self, name: String, deletion: bool, scope: VariableScope) { match scope { VariableScope::Block => self diff --git a/boa/src/environment/object_environment_record.rs b/boa/src/environment/object_environment_record.rs index 43b9f8349ec..6c7f73ee307 100644 --- a/boa/src/environment/object_environment_record.rs +++ b/boa/src/environment/object_environment_record.rs @@ -87,6 +87,10 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { false } + fn get_this_binding(&self) -> Value { + Gc::new(ValueData::Undefined) + } + fn has_super_binding(&self) -> bool { false } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index f5e4deb83aa..7725a8d2d59 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -659,6 +659,10 @@ impl Executor for Interpreter { // TODO: for now we can do nothing but return the value as-is self.run(node) } + Node::This => { + // Will either return `this` binding or undefined + Ok(self.realm.environment.get_this_binding()) + } ref i => unimplemented!("{}", i), } } From a4c3871010932e33dc5d128adc720d73a99ee8e3 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Wed, 13 May 2020 15:24:14 +0100 Subject: [PATCH 2/6] * remove construct/call from Object instead set func * get_this_binding() was wrong, fixed * BindingStatus is now properly set * `this` now works on dynamic functions --- .vscode/tasks.json | 2 +- boa/src/builtins/function/mod.rs | 18 +++++--- boa/src/builtins/mod.rs | 12 ++---- boa/src/builtins/object/mod.rs | 41 +++++++------------ boa/src/builtins/value/mod.rs | 2 +- .../declarative_environment_record.rs | 2 +- boa/src/environment/lexical_environment.rs | 12 +++--- .../environment/object_environment_record.rs | 2 +- boa/src/exec/mod.rs | 20 +++------ 9 files changed, 44 insertions(+), 67 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d35f8352665..49d85082624 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,7 @@ "type": "process", "label": "Cargo Run", "command": "cargo", - "args": ["run", "./tests/js/test.js"], + "args": ["run", "--bin", "boa", "./tests/js/test.js"], "problemMatcher": ["$rustc"], "group": { "kind": "build", diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index e0c7a346bc4..33d30a56293 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -18,7 +18,10 @@ use crate::{ property::Property, value::{ResultValue, Value}, }, - environment::lexical_environment::{new_function_environment, Environment}, + environment::{ + function_environment_record::BindingStatus, + lexical_environment::{new_function_environment, Environment}, + }, exec::Executor, syntax::ast::node::{FormalParameter, Node}, Interpreter, @@ -162,8 +165,9 @@ impl Function { // let local_env = new_function_environment( this.clone(), - this_obj.clone(), + None, Some(self.environment.as_ref().unwrap().clone()), + BindingStatus::Uninitialized, ); // Add argument bindings to the function environment @@ -203,6 +207,7 @@ impl Function { } } + /// pub fn construct( &self, this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) @@ -222,8 +227,9 @@ impl Function { // let local_env = new_function_environment( this.clone(), - this_obj.clone(), + Some(this_obj.clone()), Some(self.environment.as_ref().unwrap().clone()), + BindingStatus::Initialized, ); // Add argument bindings to the function environment @@ -250,14 +256,14 @@ impl Function { interpreter.realm.environment.push(local_env); // Call body should be set before reaching here - let result = match &self.body { + let _ = match &self.body { FunctionBody::Ordinary(ref body) => interpreter.run(body), _ => panic!("Ordinary function should not have BuiltIn Function body"), }; // local_env gets dropped here, its no longer needed - interpreter.realm.environment.pop(); - result + let binding = interpreter.realm.environment.get_this_binding(); + Ok(binding) } } } diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index b1aaec67dbd..62fe41bfaee 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -11,7 +11,7 @@ macro_rules! make_builtin_fn { ); let mut new_func = crate::builtins::object::Object::function(); - new_func.set_call(func); + new_func.set_func(func); let new_func_obj = Value::from(new_func); new_func_obj.set_field_slice("length", Value::from($l)); $p.set_field_slice($name, new_func_obj); @@ -39,7 +39,7 @@ macro_rules! make_constructor_fn { // Create the function object and point its instance prototype to Function.prototype let mut constructor_obj = Object::function(); - constructor_obj.set_construct(constructor_fn); + constructor_obj.set_func(constructor_fn); constructor_obj.set_internal_slot("__proto__", func_prototype); let constructor_val = Value::from(constructor_obj); @@ -51,11 +51,6 @@ macro_rules! make_constructor_fn { constructor_val }}; ($construct_body:ident, $call_body:ident, $global:ident, $proto:ident) => {{ - // Create the native functions - let construct_fn = crate::builtins::function::Function::create_builtin( - vec![], - crate::builtins::function::FunctionBody::BuiltIn($construct_body), - ); let call_fn = crate::builtins::function::Function::create_builtin( vec![], crate::builtins::function::FunctionBody::BuiltIn($call_body), @@ -68,8 +63,7 @@ macro_rules! make_constructor_fn { // Create the function object and point its instance prototype to Function.prototype let mut constructor_obj = Object::function(); - constructor_obj.set_construct(construct_fn); - constructor_obj.set_call(call_fn); + constructor_obj.set_func(call_fn); constructor_obj.set_internal_slot("__proto__", func_prototype); let constructor_val = Value::from(constructor_obj); diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index d7b6ed6f087..35d6816a238 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -46,7 +46,7 @@ pub static INSTANCE_PROTOTYPE: &str = "__proto__"; pub struct Object { /// The type of the object. pub kind: ObjectKind, - /// Intfiernal Slots + /// Internal Slots pub internal_slots: FxHashMap, /// Properties pub properties: FxHashMap, @@ -54,10 +54,8 @@ pub struct Object { pub sym_properties: FxHashMap, /// Some rust object that stores internal state pub state: Option, - /// [[Call]] - pub call: Option, - /// [[Construct]] - pub construct: Option, + /// Function + pub func: Option, } impl Debug for Object { @@ -65,8 +63,7 @@ impl Debug for Object { writeln!(f, "{{")?; writeln!(f, "\tkind: {}", self.kind)?; writeln!(f, "\tstate: {:?}", self.state)?; - writeln!(f, "\tcall: {:?}", self.call)?; - writeln!(f, "\tconstruct: {:?}", self.construct)?; + writeln!(f, "\tfunc: {:?}", self.func)?; writeln!(f, "\tproperties: {{")?; for (key, _) in self.properties.iter() { writeln!(f, "\t\t{}", key)?; @@ -342,8 +339,7 @@ impl Object { properties: FxHashMap::default(), sym_properties: FxHashMap::default(), state: None, - call: None, - construct: None, + func: None, }; object.set_internal_slot("extensible", Value::from(true)); @@ -358,8 +354,7 @@ impl Object { properties: FxHashMap::default(), sym_properties: FxHashMap::default(), state: None, - call: None, - construct: None, + func: None, }; object.set_internal_slot("extensible", Value::from(true)); @@ -382,14 +377,9 @@ impl Object { obj } - /// Set [[Call]] - pub fn set_call(&mut self, val: Function) { - self.call = Some(val); - } - - /// set [[Construct]] - pub fn set_construct(&mut self, val: Function) { - self.construct = Some(val); + /// Set the function this object wraps + pub fn set_func(&mut self, val: Function) { + self.func = Some(val); } /// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument. @@ -400,8 +390,7 @@ impl Object { properties: FxHashMap::default(), sym_properties: FxHashMap::default(), state: None, - call: None, - construct: None, + func: None, }; obj.internal_slots @@ -417,8 +406,7 @@ impl Object { properties: FxHashMap::default(), sym_properties: FxHashMap::default(), state: None, - call: None, - construct: None, + func: None, }; obj.internal_slots @@ -434,8 +422,7 @@ impl Object { properties: FxHashMap::default(), sym_properties: FxHashMap::default(), state: None, - call: None, - construct: None, + func: None, }; obj.internal_slots @@ -466,7 +453,7 @@ impl Object { /// /// [spec]: https://tc39.es/ecma262/#sec-iscallable pub fn is_callable(&self) -> bool { - self.call.is_some() + self.func.is_some() } /// It determines if Object is a function object with a [[Construct]] internal method. @@ -476,7 +463,7 @@ impl Object { /// /// [spec]: https://tc39.es/ecma262/#sec-isconstructor pub fn is_constructor(&self) -> bool { - self.construct.is_some() + self.func.is_some() } } diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 87249d29d08..72fdac257a2 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -625,7 +625,7 @@ impl ValueData { // Get Length let length = native_func.params.len(); // Set [[Call]] internal slot - new_func.set_call(native_func); + new_func.set_func(native_func); // Wrap Object in GC'd Value let new_func_val = Value::from(new_func); // Set length to parameters diff --git a/boa/src/environment/declarative_environment_record.rs b/boa/src/environment/declarative_environment_record.rs index f8ce882f971..1b02c1c7a13 100644 --- a/boa/src/environment/declarative_environment_record.rs +++ b/boa/src/environment/declarative_environment_record.rs @@ -150,7 +150,7 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { } fn get_this_binding(&self) -> Value { - Gc::new(ValueData::Undefined) + Value::undefined() } fn has_super_binding(&self) -> bool { diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 21b181bee2a..158d50b48eb 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -114,7 +114,7 @@ impl LexicalEnvironment { } pub fn get_this_binding(&self) -> Value { - let env = self.environment_stack.get(0).expect("").borrow(); + let env = self.environment_stack.back().expect("").borrow(); env.get_this_binding() } @@ -231,18 +231,18 @@ pub fn new_declarative_environment(env: Option) -> Environment { pub fn new_function_environment( f: Value, - new_target: Value, + this: Option, outer: Option, + binding_status: BindingStatus, ) -> Environment { - debug_assert!(new_target.is_object() || new_target.is_undefined()); Gc::new(GcCell::new(Box::new(FunctionEnvironmentRecord { env_rec: FxHashMap::default(), function: f, - this_binding_status: BindingStatus::Uninitialized, // hardcoding to unitialized for now until short functions are properly supported + this_binding_status: binding_status, home_object: Value::undefined(), - new_target, + new_target: Value::undefined(), outer_env: outer, // this will come from Environment set as a private property of F - https://tc39.es/ecma262/#sec-ecmascript-function-objects - this_value: Value::undefined(), // TODO: this_value should start as an Option as its not always there to begin with + this_value: this.unwrap_or(Value::undefined()), }))) } diff --git a/boa/src/environment/object_environment_record.rs b/boa/src/environment/object_environment_record.rs index 6c7f73ee307..add288fb75f 100644 --- a/boa/src/environment/object_environment_record.rs +++ b/boa/src/environment/object_environment_record.rs @@ -88,7 +88,7 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { } fn get_this_binding(&self) -> Value { - Gc::new(ValueData::Undefined) + Value::undefined() } fn has_super_binding(&self) -> bool { diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 7725a8d2d59..fbe05f375dd 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -291,7 +291,7 @@ impl Executor for Interpreter { ); let mut new_func = Object::function(); - new_func.set_call(func); + new_func.set_func(func); let val = Value::from(new_func); val.set_field_slice("length", Value::from(args.len())); @@ -326,7 +326,7 @@ impl Executor for Interpreter { ); let mut new_func = Object::function(); - new_func.set_call(func); + new_func.set_func(func); let val = Value::from(new_func); val.set_field_slice("length", Value::from(args.len())); @@ -354,7 +354,7 @@ impl Executor for Interpreter { ); let mut new_func = Object::function(); - new_func.set_call(func); + new_func.set_func(func); let val = Value::from(new_func); val.set_field_slice("length", Value::from(args.len())); @@ -517,7 +517,7 @@ impl Executor for Interpreter { match *(func_object.borrow()).deref() { ValueData::Object(ref o) => (*o.deref().clone().borrow_mut()) - .construct + .func .as_ref() .unwrap() .construct(&mut func_object.clone(), &v_args, self, &mut this), @@ -630,13 +630,6 @@ impl Executor for Interpreter { })) } Node::StatementList(ref list) => { - { - let env = &mut self.realm.environment; - env.push(new_declarative_environment(Some( - env.get_current_environment_ref().clone(), - ))); - } - let mut obj = Value::null(); for (i, item) in list.iter().enumerate() { let val = self.run(item)?; @@ -650,9 +643,6 @@ impl Executor for Interpreter { } } - // pop the block env - let _ = self.realm.environment.pop(); - Ok(obj) } Node::Spread(ref node) => { @@ -684,7 +674,7 @@ impl Interpreter { // All functions should be objects, and eventually will be. // During this transition call will support both native functions and function objects match (*f).deref() { - ValueData::Object(ref obj) => match (*obj).deref().borrow().call { + ValueData::Object(ref obj) => match (*obj).deref().borrow().func { Some(ref func) => func.call(&mut f.clone(), arguments_list, self, this), None => panic!("Expected function"), }, From 7be031dc7112f9c9bf412e4a08248d5ff76b2255 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Wed, 13 May 2020 16:03:56 +0100 Subject: [PATCH 3/6] * Migrates all builtins to use a single constructor/call fucntion to match the spec --- boa/src/builtins/array/mod.rs | 5 +- boa/src/builtins/boolean/mod.rs | 16 +++---- boa/src/builtins/error.rs | 5 +- boa/src/builtins/function/mod.rs | 37 ++++++++++++++- boa/src/builtins/mod.rs | 54 ---------------------- boa/src/builtins/number/mod.rs | 15 ++++-- boa/src/builtins/object/mod.rs | 3 +- boa/src/builtins/regexp/mod.rs | 5 +- boa/src/builtins/string/mod.rs | 21 +++------ boa/src/builtins/symbol/mod.rs | 3 +- boa/src/environment/lexical_environment.rs | 2 +- 11 files changed, 71 insertions(+), 95 deletions(-) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 110e7547d26..cfe51739c2d 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -12,9 +12,10 @@ #[cfg(test)] mod tests; +use super::function::make_constructor_fn; use crate::{ builtins::{ - object::{Object, ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{ResultValue, Value, ValueData}, }, @@ -977,7 +978,7 @@ pub fn create(global: &Value) -> Value { make_builtin_fn!(slice, named "slice", with length 2, of prototype); make_builtin_fn!(some, named "some", with length 2, of prototype); - let array = make_constructor_fn!(make_array, make_array, global, prototype); + let array = make_constructor_fn(make_array, global, prototype); // Static Methods make_builtin_fn!(is_array, named "isArray", with length 1, of array); diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 4e92e11be3d..58956cacda2 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -12,16 +12,19 @@ #[cfg(test)] mod tests; +use super::function::make_constructor_fn; use crate::{ builtins::{ - object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, + object::{internal_methods_trait::ObjectInternalMethods, ObjectKind}, value::{ResultValue, Value, ValueData}, }, exec::Interpreter, }; use std::{borrow::Borrow, ops::Deref}; -/// Create a new boolean object - [[Construct]] +/// `[[Construct]]` Create a new boolean object +/// +/// `[[Call]]` Creates a new boolean primitive pub fn construct_boolean(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { this.set_kind(ObjectKind::Boolean); @@ -32,13 +35,6 @@ pub fn construct_boolean(this: &mut Value, args: &[Value], _: &mut Interpreter) this.set_internal_slot("BooleanData", to_boolean(&Value::from(false))); } - // no need to return `this` as its passed by reference - Ok(this.clone()) -} - -/// Return a boolean literal [[Call]] -pub fn call_boolean(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - // Get the argument, if any match args.get(0) { Some(ref value) => Ok(to_boolean(value)), None => Ok(to_boolean(&Value::from(false))), @@ -108,7 +104,7 @@ pub fn create(global: &Value) -> Value { make_builtin_fn!(to_string, named "toString", of prototype); make_builtin_fn!(value_of, named "valueOf", of prototype); - make_constructor_fn!(construct_boolean, call_boolean, global, prototype) + make_constructor_fn(construct_boolean, global, prototype) } /// Initialise the `Boolean` object on the global object. diff --git a/boa/src/builtins/error.rs b/boa/src/builtins/error.rs index 1ca7d5f7e6c..35f4e91c44c 100644 --- a/boa/src/builtins/error.rs +++ b/boa/src/builtins/error.rs @@ -10,9 +10,10 @@ //! [spec]: https://tc39.es/ecma262/#sec-error-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error +use super::function::make_constructor_fn; use crate::{ builtins::{ - object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, + object::ObjectKind, value::{ResultValue, Value}, }, exec::Interpreter, @@ -58,7 +59,7 @@ pub fn create(global: &Value) -> Value { prototype.set_field_slice("message", Value::from("")); prototype.set_field_slice("name", Value::from("Error")); make_builtin_fn!(to_string, named "toString", of prototype); - make_constructor_fn!(make_error, global, prototype) + make_constructor_fn(make_error, global, prototype) } /// Initialise the global object with the `Error` object. diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 33d30a56293..75cf71322fb 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -217,7 +217,10 @@ impl Function { ) -> ResultValue { match self.kind { FunctionKind::BuiltIn => match &self.body { - FunctionBody::BuiltIn(func) => func(this_obj, args_list, interpreter), + FunctionBody::BuiltIn(func) => { + func(this_obj, args_list, interpreter).unwrap(); + Ok(this_obj.clone()) + } FunctionBody::Ordinary(_) => { panic!("Builtin function should not have Ordinary Function body") } @@ -369,7 +372,37 @@ pub fn make_function(this: &mut Value, _: &[Value], _: &mut Interpreter) -> Resu pub fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); - make_constructor_fn!(make_function, make_function, global, prototype) + make_constructor_fn(make_function, global, prototype) +} + +/// Creates a new constructor function +/// +/// This utility function handling linking the new Constructor to the prototype. +/// So far this is only used by internal functions +pub fn make_constructor_fn(body: NativeFunctionData, global: &Value, proto: Value) -> Value { + // Create the native function + let constructor_fn = crate::builtins::function::Function::create_builtin( + vec![], + crate::builtins::function::FunctionBody::BuiltIn(body), + ); + + // Get reference to Function.prototype + let func_prototype = global + .get_field_slice("Function") + .get_field_slice(PROTOTYPE); + + // Create the function object and point its instance prototype to Function.prototype + let mut constructor_obj = Object::function(); + constructor_obj.set_func(constructor_fn); + + constructor_obj.set_internal_slot("__proto__", func_prototype); + let constructor_val = Value::from(constructor_obj); + + // Set proto.constructor -> constructor_obj + proto.set_field_slice("constructor", constructor_val.clone()); + constructor_val.set_field_slice(PROTOTYPE, proto); + + constructor_val } /// Initialise the `Function` object on the global object. diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 62fe41bfaee..532b74c8b87 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -21,60 +21,6 @@ macro_rules! make_builtin_fn { }; } -/// Macro to create a new constructor function -/// -/// Either (construct_body, global, prototype) -macro_rules! make_constructor_fn { - ($body:ident, $global:ident, $proto:ident) => {{ - // Create the native function - let constructor_fn = crate::builtins::function::Function::create_builtin( - vec![], - crate::builtins::function::FunctionBody::BuiltIn($body), - ); - - // Get reference to Function.prototype - let func_prototype = $global - .get_field_slice("Function") - .get_field_slice(PROTOTYPE); - - // Create the function object and point its instance prototype to Function.prototype - let mut constructor_obj = Object::function(); - constructor_obj.set_func(constructor_fn); - - constructor_obj.set_internal_slot("__proto__", func_prototype); - let constructor_val = Value::from(constructor_obj); - - // Set proto.constructor -> constructor_obj - $proto.set_field_slice("constructor", constructor_val.clone()); - constructor_val.set_field_slice(PROTOTYPE, $proto); - - constructor_val - }}; - ($construct_body:ident, $call_body:ident, $global:ident, $proto:ident) => {{ - let call_fn = crate::builtins::function::Function::create_builtin( - vec![], - crate::builtins::function::FunctionBody::BuiltIn($call_body), - ); - - // Get reference to Function.prototype - let func_prototype = $global - .get_field_slice("Function") - .get_field_slice(PROTOTYPE); - - // Create the function object and point its instance prototype to Function.prototype - let mut constructor_obj = Object::function(); - constructor_obj.set_func(call_fn); - constructor_obj.set_internal_slot("__proto__", func_prototype); - let constructor_val = Value::from(constructor_obj); - - // Set proto.constructor -> constructor_obj - $proto.set_field_slice("constructor", constructor_val.clone()); - constructor_val.set_field_slice(PROTOTYPE, $proto); - - constructor_val - }}; -} - pub mod array; pub mod boolean; pub mod console; diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index d9e864ece54..66a07e164e1 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -16,9 +16,10 @@ #[cfg(test)] mod tests; +use super::{function::make_constructor_fn, object::ObjectKind}; use crate::{ builtins::{ - object::{internal_methods_trait::ObjectInternalMethods, Object, PROTOTYPE}, + object::internal_methods_trait::ObjectInternalMethods, value::{ResultValue, Value, ValueData}, }, exec::Interpreter, @@ -57,14 +58,18 @@ fn num_to_exponential(n: f64) -> String { } } -/// Create a new number `[[Construct]]` +/// `[[Construct]]` - Creates a Number instance +/// +/// `[[Call]]` - Creates a number primitive pub fn make_number(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { let data = match args.get(0) { Some(ref value) => to_number(value), None => to_number(&Value::from(0)), }; - this.set_internal_slot("NumberData", data); - Ok(this.clone()) + this.set_kind(ObjectKind::Number); + this.set_internal_slot("NumberData", data.clone()); + + Ok(data) } /// `Number()` function. @@ -360,7 +365,7 @@ pub fn create(global: &Value) -> Value { make_builtin_fn!(to_string, named "toString", with length 1, of prototype); make_builtin_fn!(value_of, named "valueOf", of prototype); - make_constructor_fn!(make_number, call_number, global, prototype) + make_constructor_fn(make_number, global, prototype) } /// Initialise the `Number` object on the global object. diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 35d6816a238..af79f71dda3 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -29,6 +29,7 @@ use std::{ ops::Deref, }; +use super::function::make_constructor_fn; pub use internal_methods_trait::ObjectInternalMethods; pub use internal_state::{InternalState, InternalStateCell}; @@ -594,7 +595,7 @@ pub fn create(global: &Value) -> Value { make_builtin_fn!(has_own_property, named "hasOwnProperty", of prototype); make_builtin_fn!(to_string, named "toString", of prototype); - let object = make_constructor_fn!(make_object, make_object, global, prototype); + let object = make_constructor_fn(make_object, global, prototype); object.set_field_slice("length", Value::from(1)); make_builtin_fn!(set_prototype_of, named "setPrototypeOf", with length 2, of object); diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index b2cbecc2610..521ebf8e51b 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -13,9 +13,10 @@ use std::ops::Deref; use regex::Regex; +use super::function::make_constructor_fn; use crate::{ builtins::{ - object::{InternalState, Object, ObjectInternalMethods, ObjectKind, PROTOTYPE}, + object::{InternalState, ObjectKind}, property::Property, value::{ResultValue, Value, ValueData}, }, @@ -475,7 +476,7 @@ pub fn create(global: &Value) -> Value { make_builtin_fn!(get_sticky, named "sticky", of prototype); make_builtin_fn!(get_unicode, named "unicode", of prototype); - make_constructor_fn!(make_regexp, make_regexp, global, prototype) + make_constructor_fn(make_regexp, global, prototype) } /// Initialise the `RegExp` object on the global object. diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 7ece2e74c30..7e2e2e59a7e 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -12,9 +12,10 @@ #[cfg(test)] mod tests; +use super::function::make_constructor_fn; use crate::{ builtins::{ - object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, + object::{Object, ObjectKind}, property::Property, regexp::{make_regexp, match_all as regexp_match_all, r#match as regexp_match}, value::{ResultValue, Value, ValueData}, @@ -28,14 +29,10 @@ use std::{ ops::Deref, }; -/// Create new string [[Construct]] -// This gets called when a new String() is created, it's called by exec:346 +/// [[Construct]] - Creates a new instance `this` +/// +/// [[Call]] - Returns a new native `string` pub fn make_string(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - // If we're constructing a string, we should set the initial length - // To do this we need to convert the string back to a Rust String, then get the .len() - // let a: String = from_value(args.get(0).expect("failed to get argument for String method").clone()).unwrap(); - // this.set_field_slice("length", to_value(a.len() as i32)); - // This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe // to its Javascript Identifier (global constructor method name) this.set_kind(ObjectKind::String); @@ -45,13 +42,7 @@ pub fn make_string(this: &mut Value, args: &[Value], _: &mut Interpreter) -> Res .expect("failed to get StringData for make_string()") .clone(), ); - Ok(this.clone()) -} -/// Call new string [[Call]] -/// -/// More information: [ECMAScript reference](https://tc39.es/ecma262/#sec-string-constructor-string-value) -pub fn call_string(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let arg = match args.get(0) { Some(v) => v.clone(), None => Value::undefined(), @@ -1029,7 +1020,7 @@ pub fn create(global: &Value) -> Value { make_builtin_fn!(match_all, named "matchAll", with length 1, of prototype); make_builtin_fn!(replace, named "replace", with length 2, of prototype); - make_constructor_fn!(make_string, call_string, global, prototype) + make_constructor_fn(make_string, global, prototype) } /// Initialise the `String` object on the global object. diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 161bde9f44c..16cd4c0e17c 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -18,6 +18,7 @@ #[cfg(test)] mod tests; +use super::function::make_constructor_fn; use crate::{ builtins::{ object::{ @@ -92,7 +93,7 @@ pub fn create(global: &Value) -> Value { // Create prototype object let prototype = Value::new_object(Some(global)); make_builtin_fn!(to_string, named "toString", of prototype); - make_constructor_fn!(call_symbol, call_symbol, global, prototype) + make_constructor_fn(call_symbol, global, prototype) } /// Initialise the `Symbol` object on the global object. diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 158d50b48eb..3c7d15faea0 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -242,7 +242,7 @@ pub fn new_function_environment( home_object: Value::undefined(), new_target: Value::undefined(), outer_env: outer, // this will come from Environment set as a private property of F - https://tc39.es/ecma262/#sec-ecmascript-function-objects - this_value: this.unwrap_or(Value::undefined()), + this_value: this.unwrap_or_else(Value::undefined), }))) } From a3c9a301c6df70c5615d0b4b908a753a5b030780 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Wed, 13 May 2020 16:42:08 +0100 Subject: [PATCH 4/6] Ensure new object has an existing prototype --- boa/src/exec/mod.rs | 24 +++++++++++++++++------- boa/src/exec/tests.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index fbe05f375dd..edbe7bb7e4c 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -275,13 +275,21 @@ impl Executor for Interpreter { // Node::FunctionDecl(ref name, ref args, ref expr) => { // Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype - // let proto = &self - // .realm - // .environment - // .get_global_object() - // .expect("Could not get the global object") - // .get_field_slice("Object") - // .get_field_slice("Prototype"); + let function_prototype = &self + .realm + .environment + .get_global_object() + .expect("Could not get the global object") + .get_field_slice("Function") + .get_field_slice("Prototype"); + + // Every new function has a prototype property pre-made + let global_val = &self + .realm + .environment + .get_global_object() + .expect("Could not get the global object"); + let proto = Value::new_object(Some(global_val)); let func = FunctionObject::create_ordinary( args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value @@ -293,6 +301,8 @@ impl Executor for Interpreter { let mut new_func = Object::function(); new_func.set_func(func); let val = Value::from(new_func); + val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone()); + val.set_field_slice(PROTOTYPE, proto); val.set_field_slice("length", Value::from(args.len())); // Set the name and assign it in the current environment diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 7f3b6f78125..45fa718d32b 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -500,6 +500,7 @@ fn unary_delete() { #[cfg(test)] mod in_operator { use super::*; + use crate::{builtins::object::INSTANCE_PROTOTYPE, forward_val}; #[test] fn propery_in_object() { let p_in_o = r#" @@ -564,4 +565,37 @@ mod in_operator { "#; exec(scenario); } + + #[test] + fn should_set_this_value() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + let scenario = r#" + function Foo() { + this.a = "a"; + this.b = "b"; + } + + var bar = new Foo(); + "#; + forward(&mut engine, scenario); + assert_eq!(forward(&mut engine, "bar.a"), "a"); + assert_eq!(forward(&mut engine, "bar.b"), "b"); + } + + #[test] + fn new_instance_should_point_to_prototype() { + // A new instance should point to a prototype object created with the constructor function + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + let scenario = r#" + function Foo() {} + var bar = new Foo(); + "#; + forward(&mut engine, scenario); + let a = forward_val(&mut engine, "bar").unwrap(); + assert!(a.get_internal_slot(INSTANCE_PROTOTYPE).is_object(), true); + } } From 3d14b01ffbc1d8521a258cc6f36da36ec5c4ddd7 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Wed, 13 May 2020 17:12:38 +0100 Subject: [PATCH 5/6] create_function utility --- boa/src/exec/mod.rs | 119 +++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 74 deletions(-) diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index edbe7bb7e4c..1dcc72e6658 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -18,7 +18,7 @@ use crate::{ realm::Realm, syntax::ast::{ constant::Const, - node::{MethodDefinitionKind, Node, PropertyDefinition}, + node::{FormalParameter, MethodDefinitionKind, Node, PropertyDefinition}, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, }, }; @@ -274,38 +274,9 @@ impl Executor for Interpreter { } // Node::FunctionDecl(ref name, ref args, ref expr) => { - // Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype - let function_prototype = &self - .realm - .environment - .get_global_object() - .expect("Could not get the global object") - .get_field_slice("Function") - .get_field_slice("Prototype"); - - // Every new function has a prototype property pre-made - let global_val = &self - .realm - .environment - .get_global_object() - .expect("Could not get the global object"); - let proto = Value::new_object(Some(global_val)); - - let func = FunctionObject::create_ordinary( - args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value - self.realm.environment.get_current_environment().clone(), - FunctionBody::Ordinary(*expr.clone()), - ThisMode::NonLexical, - ); - - let mut new_func = Object::function(); - new_func.set_func(func); - let val = Value::from(new_func); - val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone()); - val.set_field_slice(PROTOTYPE, proto); - val.set_field_slice("length", Value::from(args.len())); - + let val = self.create_function(args, expr, ThisMode::NonLexical); // Set the name and assign it in the current environment + val.set_field_slice("name", Value::from(name.clone())); self.realm.environment.create_mutable_binding( name.clone(), @@ -319,26 +290,7 @@ impl Executor for Interpreter { } // Node::FunctionExpr(ref name, ref args, ref expr) => { - // Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype - // let proto = &self - // .realm - // .environment - // .get_global_object() - // .expect("Could not get the global object") - // .get_field_slice("Object") - // .get_field_slice("Prototype"); - - let func = FunctionObject::create_ordinary( - args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value - self.realm.environment.get_current_environment().clone(), - FunctionBody::Ordinary(*expr.clone()), - ThisMode::NonLexical, - ); - - let mut new_func = Object::function(); - new_func.set_func(func); - let val = Value::from(new_func); - val.set_field_slice("length", Value::from(args.len())); + let val = self.create_function(args, expr, ThisMode::NonLexical); if let Some(name) = name { val.set_field_slice("name", Value::from(name.clone())); @@ -347,28 +299,7 @@ impl Executor for Interpreter { Ok(val) } Node::ArrowFunctionDecl(ref args, ref expr) => { - // Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype - // let proto = &self - // .realm - // .environment - // .get_global_object() - // .expect("Could not get the global object") - // .get_field_slice("Object") - // .get_field_slice("Prototype"); - - let func = FunctionObject::create_ordinary( - args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value - self.realm.environment.get_current_environment().clone(), - FunctionBody::Ordinary(*expr.clone()), - ThisMode::Lexical, - ); - - let mut new_func = Object::function(); - new_func.set_func(func); - let val = Value::from(new_func); - val.set_field_slice("length", Value::from(args.len())); - - Ok(val) + Ok(self.create_function(args, expr, ThisMode::Lexical)) } Node::BinOp(BinOp::Num(ref op), ref a, ref b) => { let v_a = self.run(a)?; @@ -674,6 +605,46 @@ impl Interpreter { &self.realm } + /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions + pub(crate) fn create_function( + &mut self, + args: &Box<[FormalParameter]>, + expr: &Node, + this_mode: ThisMode, + ) -> Value { + let function_prototype = &self + .realm + .environment + .get_global_object() + .expect("Could not get the global object") + .get_field_slice("Function") + .get_field_slice("Prototype"); + + // Every new function has a prototype property pre-made + let global_val = &self + .realm + .environment + .get_global_object() + .expect("Could not get the global object"); + let proto = Value::new_object(Some(global_val)); + + let func = FunctionObject::create_ordinary( + args.clone(), + self.realm.environment.get_current_environment().clone(), + FunctionBody::Ordinary(expr.clone()), + this_mode, + ); + + let mut new_func = Object::function(); + new_func.set_func(func); + let val = Value::from(new_func); + val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone()); + val.set_field_slice(PROTOTYPE, proto); + val.set_field_slice("length", Value::from(args.len())); + + val + } + /// https://tc39.es/ecma262/#sec-call pub(crate) fn call( &mut self, From 7c7f9574d3691f99981472a65627ba6c8afe52dd Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Thu, 14 May 2020 19:08:05 +0100 Subject: [PATCH 6/6] needing to clone before passing through --- boa/src/exec/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 1dcc72e6658..6950a028bc0 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -274,7 +274,7 @@ impl Executor for Interpreter { } // Node::FunctionDecl(ref name, ref args, ref expr) => { - let val = self.create_function(args, expr, ThisMode::NonLexical); + let val = self.create_function(args.clone(), expr, ThisMode::NonLexical); // Set the name and assign it in the current environment val.set_field_slice("name", Value::from(name.clone())); @@ -290,7 +290,7 @@ impl Executor for Interpreter { } // Node::FunctionExpr(ref name, ref args, ref expr) => { - let val = self.create_function(args, expr, ThisMode::NonLexical); + let val = self.create_function(args.clone(), expr, ThisMode::NonLexical); if let Some(name) = name { val.set_field_slice("name", Value::from(name.clone())); @@ -299,7 +299,7 @@ impl Executor for Interpreter { Ok(val) } Node::ArrowFunctionDecl(ref args, ref expr) => { - Ok(self.create_function(args, expr, ThisMode::Lexical)) + Ok(self.create_function(args.clone(), expr, ThisMode::Lexical)) } Node::BinOp(BinOp::Num(ref op), ref a, ref b) => { let v_a = self.run(a)?; @@ -608,7 +608,7 @@ impl Interpreter { /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions pub(crate) fn create_function( &mut self, - args: &Box<[FormalParameter]>, + args: Box<[FormalParameter]>, expr: &Node, this_mode: ThisMode, ) -> Value {