From c5a4be02e6c6077cb04982ba130c7d61ea59475b Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Wed, 15 Sep 2021 17:08:50 +0200 Subject: [PATCH] Refactor `EnvironmentRecordTrait` functions (#1569) --- boa/src/builtins/function/mod.rs | 4 +- .../declarative_environment_record.rs | 151 +++++-- .../environment/environment_record_trait.rs | 26 +- .../function_environment_record.rs | 95 +++-- .../environment/global_environment_record.rs | 400 ++++++++++++++---- boa/src/environment/lexical_environment.rs | 9 +- .../environment/object_environment_record.rs | 204 ++++++--- boa/src/object/gcobject.rs | 13 +- .../ast/node/declaration/function_decl/mod.rs | 10 +- boa/src/syntax/ast/node/declaration/mod.rs | 24 +- .../ast/node/iteration/for_in_loop/mod.rs | 34 +- .../ast/node/iteration/for_of_loop/mod.rs | 34 +- .../syntax/ast/node/operator/assign/mod.rs | 10 +- .../syntax/ast/node/operator/bin_op/mod.rs | 2 +- boa/src/syntax/ast/node/try_node/mod.rs | 6 +- boa/src/value/mod.rs | 26 -- boa/src/vm/mod.rs | 28 +- 17 files changed, 756 insertions(+), 320 deletions(-) diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 6b8695dfd4..a4ea906bb2 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -159,7 +159,7 @@ impl Function { // Create binding local_env // Function parameters can share names in JavaScript... - .create_mutable_binding(param.name().to_owned(), false, true, context) + .create_mutable_binding(param.name(), false, true, context) .expect("Failed to create binding for rest param"); // Set Binding to value @@ -178,7 +178,7 @@ impl Function { ) { // Create binding local_env - .create_mutable_binding(param.name().to_owned(), false, true, context) + .create_mutable_binding(param.name(), false, true, context) .expect("Failed to create binding"); // Set Binding to value diff --git a/boa/src/environment/declarative_environment_record.rs b/boa/src/environment/declarative_environment_record.rs index e8337de512..c5419d777a 100644 --- a/boa/src/environment/declarative_environment_record.rs +++ b/boa/src/environment/declarative_environment_record.rs @@ -49,27 +49,44 @@ impl DeclarativeEnvironmentRecord { } impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { - fn has_binding(&self, name: &str) -> bool { - self.env_rec.borrow().contains_key(name) + /// `9.1.1.1.1 HasBinding ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n + fn has_binding(&self, name: &str, _context: &mut Context) -> JsResult { + // 1. If envRec has a binding for the name that is the value of N, return true. + // 2. Return false. + Ok(self.env_rec.borrow().contains_key(name)) } + /// `9.1.1.1.2 CreateMutableBinding ( N, D )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-createmutablebinding-n-d fn create_mutable_binding( &self, - name: String, + name: &str, deletion: bool, allow_name_reuse: bool, _context: &mut Context, ) -> JsResult<()> { + // 1. Assert: envRec does not already have a binding for N. if !allow_name_reuse { assert!( - !self.env_rec.borrow().contains_key(name.as_str()), + !self.env_rec.borrow().contains_key(name), "Identifier {} has already been declared", name ); } + // 2. Create a mutable binding in envRec for N and record that it is uninitialized. + // If D is true, record that the newly created binding may be deleted by a subsequent DeleteBinding call. self.env_rec.borrow_mut().insert( - name.into_boxed_str(), + name.into(), DeclarativeEnvironmentRecordBinding { value: None, can_delete: deletion, @@ -77,23 +94,34 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { strict: false, }, ); + + // 3. Return NormalCompletion(empty). Ok(()) } + /// `9.1.1.1.3 CreateImmutableBinding ( N, S )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-createimmutablebinding-n-s fn create_immutable_binding( &self, - name: String, + name: &str, strict: bool, _context: &mut Context, ) -> JsResult<()> { + // 1. Assert: envRec does not already have a binding for N. assert!( - !self.env_rec.borrow().contains_key(name.as_str()), + !self.env_rec.borrow().contains_key(name), "Identifier {} has already been declared", name ); + // 2. Create an immutable binding in envRec for N and record that it is uninitialized. + // If S is true, record that the newly created binding is a strict binding. self.env_rec.borrow_mut().insert( - name.into_boxed_str(), + name.into(), DeclarativeEnvironmentRecordBinding { value: None, can_delete: true, @@ -101,9 +129,17 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { strict, }, ); + + // 3. Return NormalCompletion(empty). Ok(()) } + /// `9.1.1.1.4 InitializeBinding ( N, V )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-initializebinding-n-v fn initialize_binding( &self, name: &str, @@ -112,13 +148,25 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { ) -> JsResult<()> { if let Some(ref mut record) = self.env_rec.borrow_mut().get_mut(name) { if record.value.is_none() { + // 2. Set the bound value for N in envRec to V. + // 3. Record that the binding for N in envRec has been initialized. record.value = Some(value); + + // 4. Return NormalCompletion(empty). return Ok(()); } } + + // 1. Assert: envRec must have an uninitialized binding for N. panic!("record must have binding for {}", name); } + /// `9.1.1.1.5 SetMutableBinding ( N, V, S )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s #[allow(clippy::else_if_without_else)] fn set_mutable_binding( &self, @@ -127,49 +175,70 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { mut strict: bool, context: &mut Context, ) -> JsResult<()> { + // 1. If envRec does not have a binding for N, then if self.env_rec.borrow().get(name).is_none() { + // a. If S is true, throw a ReferenceError exception. if strict { return Err(context.construct_reference_error(format!("{} not found", name))); } - self.create_mutable_binding(name.to_owned(), true, false, context)?; + // b. Perform envRec.CreateMutableBinding(N, true). + self.create_mutable_binding(name, true, false, context)?; + // c. Perform envRec.InitializeBinding(N, V). self.initialize_binding(name, value, context)?; + + // d. Return NormalCompletion(empty). return Ok(()); } - let (record_strict, record_has_no_value, record_mutable) = { + let (binding_strict, binding_value_is_none, binding_mutable) = { let env_rec = self.env_rec.borrow(); - let record = env_rec.get(name).unwrap(); - (record.strict, record.value.is_none(), record.mutable) + let binding = env_rec.get(name).unwrap(); + (binding.strict, binding.value.is_none(), binding.mutable) }; - if record_strict { - strict = true + + // 2. If the binding for N in envRec is a strict binding, set S to true. + if binding_strict { + strict = true; } - if record_has_no_value { + + // 3. If the binding for N in envRec has not yet been initialized, throw a ReferenceError exception. + if binding_value_is_none { return Err( context.construct_reference_error(format!("{} has not been initialized", name)) ); - } - if record_mutable { + // 4. Else if the binding for N in envRec is a mutable binding, change its bound value to V. + } else if binding_mutable { let mut env_rec = self.env_rec.borrow_mut(); - let record = env_rec.get_mut(name).unwrap(); - record.value = Some(value); + let binding = env_rec.get_mut(name).unwrap(); + binding.value = Some(value); + // 5. Else, + // a. Assert: This is an attempt to change the value of an immutable binding. + // b. If S is true, throw a TypeError exception. } else if strict { - return Err(context.construct_reference_error(format!( - "Cannot mutate an immutable binding {}", - name - ))); + return Err(context + .construct_type_error(format!("Cannot mutate an immutable binding {}", name))); } + // 6. Return NormalCompletion(empty). Ok(()) } + /// `9.1.1.1.6 GetBindingValue ( N, S )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-getbindingvalue-n-s fn get_binding_value( &self, name: &str, _strict: bool, context: &mut Context, ) -> JsResult { + // 1. Assert: envRec has a binding for N. + // 2. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception. + // 3. Return the value currently bound to N in envRec. if let Some(binding) = self.env_rec.borrow().get(name) { if let Some(ref val) = binding.value { Ok(val.clone()) @@ -181,21 +250,38 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { } } - fn delete_binding(&self, name: &str) -> bool { + /// `9.1.1.1.7 DeleteBinding ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-deletebinding-n + fn delete_binding(&self, name: &str, _context: &mut Context) -> JsResult { + // 1. Assert: envRec has a binding for the name that is the value of N. + // 2. If the binding for N in envRec cannot be deleted, return false. + // 3. Remove the binding for N from envRec. + // 4. Return true. match self.env_rec.borrow().get(name) { Some(binding) => { if binding.can_delete { self.env_rec.borrow_mut().remove(name); - true + Ok(true) } else { - false + Ok(false) } } None => panic!("env_rec has no binding for {}", name), } } + /// `9.1.1.1.8 HasThisBinding ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-hasthisbinding fn has_this_binding(&self) -> bool { + // 1. Return false. false } @@ -203,10 +289,23 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { Ok(JsValue::undefined()) } + /// `9.1.1.1.9 HasSuperBinding ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-hassuperbinding fn has_super_binding(&self) -> bool { + // 1. Return false. false } + /// `9.1.1.1.10 WithBaseObject ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-withbaseobject fn with_base_object(&self) -> Option { None } diff --git a/boa/src/environment/environment_record_trait.rs b/boa/src/environment/environment_record_trait.rs index 7c0e788cbc..7a05e63fc2 100644 --- a/boa/src/environment/environment_record_trait.rs +++ b/boa/src/environment/environment_record_trait.rs @@ -24,7 +24,7 @@ use std::fmt::Debug; pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { /// Determine if an Environment Record has a binding for the String value N. /// Return true if it does and false if it does not. - fn has_binding(&self, name: &str) -> bool; + fn has_binding(&self, name: &str, context: &mut Context) -> JsResult; /// Create a new but uninitialized mutable binding in an Environment Record. The String value N is the text of the bound name. /// If the Boolean argument deletion is true the binding may be subsequently deleted. @@ -35,7 +35,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { /// paraments with the same name. fn create_mutable_binding( &self, - name: String, + name: &str, deletion: bool, allow_name_reuse: bool, context: &mut Context, @@ -47,7 +47,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { /// regardless of the strict mode setting of operations that reference that binding. fn create_immutable_binding( &self, - name: String, + name: &str, strict: bool, context: &mut Context, ) -> JsResult<()>; @@ -85,7 +85,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { /// The String value name is the text of the bound name. /// If a binding for name exists, remove the binding and return true. /// If the binding exists but cannot be removed return false. If the binding does not exist return true. - fn delete_binding(&self, name: &str) -> bool; + fn delete_binding(&self, name: &str, context: &mut Context) -> JsResult; /// Determine if an Environment Record establishes a this binding. /// Return true if it does and false if it does not. @@ -129,7 +129,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { /// Create mutable binding while handling outer environments fn recursive_create_mutable_binding( &self, - name: String, + name: &str, deletion: bool, scope: VariableScope, context: &mut Context, @@ -146,7 +146,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { /// Create immutable binding while handling outer environments fn recursive_create_immutable_binding( &self, - name: String, + name: &str, deletion: bool, scope: VariableScope, context: &mut Context, @@ -168,7 +168,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { strict: bool, context: &mut Context, ) -> JsResult<()> { - if self.has_binding(name) { + if self.has_binding(name, context)? { self.set_mutable_binding(name, value, strict, context) } else { self.get_outer_environment_ref() @@ -184,7 +184,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { value: JsValue, context: &mut Context, ) -> JsResult<()> { - if self.has_binding(name) { + if self.has_binding(name, context)? { self.initialize_binding(name, value, context) } else { self.get_outer_environment_ref() @@ -194,17 +194,17 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { } /// Check if a binding exists in current or any outer environment - fn recursive_has_binding(&self, name: &str) -> bool { - self.has_binding(name) + fn recursive_has_binding(&self, name: &str, context: &mut Context) -> JsResult { + Ok(self.has_binding(name, context)? || match self.get_outer_environment_ref() { - Some(outer) => outer.recursive_has_binding(name), + Some(outer) => outer.recursive_has_binding(name, context)?, None => false, - } + }) } /// Retrieve binding from current or any outer environment fn recursive_get_binding_value(&self, name: &str, context: &mut Context) -> JsResult { - if self.has_binding(name) { + if self.has_binding(name, context)? { self.get_binding_value(name, false, context) } else { match self.get_outer_environment_ref() { diff --git a/boa/src/environment/function_environment_record.rs b/boa/src/environment/function_environment_record.rs index c37d0691ec..68ebef144e 100644 --- a/boa/src/environment/function_environment_record.rs +++ b/boa/src/environment/function_environment_record.rs @@ -64,7 +64,8 @@ impl FunctionEnvironmentRecord { outer: Option, binding_status: BindingStatus, new_target: JsValue, - ) -> FunctionEnvironmentRecord { + context: &mut Context, + ) -> JsResult { let mut func_env = FunctionEnvironmentRecord { declarative_record: DeclarativeEnvironmentRecord::new(outer), // the outer environment will come from Environment set as a private property of F - https://tc39.es/ecma262/#sec-ecmascript-function-objects function: f, @@ -75,36 +76,56 @@ impl FunctionEnvironmentRecord { }; // If a `this` value has been passed, bind it to the environment if let Some(v) = this { - func_env.bind_this_value(v).unwrap(); + func_env.bind_this_value(v, context)?; } - func_env + Ok(func_env) } - pub fn bind_this_value(&mut self, value: JsValue) -> JsResult { + /// `9.1.1.3.1 BindThisValue ( V )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-bindthisvalue + pub fn bind_this_value(&mut self, value: JsValue, context: &mut Context) -> JsResult { match self.this_binding_status { - // You can not bind an arrow function, their `this` value comes from the lexical scope above + // 1. Assert: envRec.[[ThisBindingStatus]] is not lexical. BindingStatus::Lexical => { panic!("Cannot bind to an arrow function!"); } - // You can not bind a function twice + // 2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception. BindingStatus::Initialized => { - todo!(); - // context.throw_reference_error("Cannot bind to an initialised function!") + context.throw_reference_error("Cannot bind to an initialized function!") } BindingStatus::Uninitialized => { + // 3. Set envRec.[[ThisValue]] to V. self.this_value = value.clone(); + // 4. Set envRec.[[ThisBindingStatus]] to initialized. self.this_binding_status = BindingStatus::Initialized; + // 5. Return V. Ok(value) } } } + /// `9.1.1.3.5 GetSuperBase ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-getsuperbase pub fn get_super_base(&self) -> JsValue { + // 1. Let home be envRec.[[FunctionObject]].[[HomeObject]]. let home = &self.home_object; + + // 2. If home has the value undefined, return undefined. if home.is_undefined() { JsValue::undefined() } else { + // 3. Assert: Type(home) is Object. assert!(home.is_object()); + + // 4. Return ? home.[[GetPrototypeOf]](). home.as_object() .expect("home_object must be an Object") .prototype_instance() @@ -113,13 +134,13 @@ impl FunctionEnvironmentRecord { } impl EnvironmentRecordTrait for FunctionEnvironmentRecord { - fn has_binding(&self, name: &str) -> bool { - self.declarative_record.has_binding(name) + fn has_binding(&self, name: &str, context: &mut Context) -> JsResult { + self.declarative_record.has_binding(name, context) } fn create_mutable_binding( &self, - name: String, + name: &str, deletion: bool, allow_name_reuse: bool, context: &mut Context, @@ -130,7 +151,7 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord { fn create_immutable_binding( &self, - name: String, + name: &str, strict: bool, context: &mut Context, ) -> JsResult<()> { @@ -169,34 +190,58 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord { .get_binding_value(name, strict, context) } - fn delete_binding(&self, name: &str) -> bool { - self.declarative_record.delete_binding(name) + fn delete_binding(&self, name: &str, context: &mut Context) -> JsResult { + self.declarative_record.delete_binding(name, context) } + /// `9.1.1.3.2 HasThisBinding ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding fn has_this_binding(&self) -> bool { + // 1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true. !matches!(self.this_binding_status, BindingStatus::Lexical) } + /// `9.1.1.3.3 HasSuperBinding ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding + fn has_super_binding(&self) -> bool { + // 1. If envRec.[[ThisBindingStatus]] is lexical, return false. + // 2. If envRec.[[FunctionObject]].[[HomeObject]] has the value undefined, return false; otherwise, return true. + if let BindingStatus::Lexical = self.this_binding_status { + false + } else { + !self.home_object.is_undefined() + } + } + + /// `9.1.1.3.4 GetThisBinding ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding fn get_this_binding(&self, context: &mut Context) -> JsResult { match self.this_binding_status { + // 1. Assert: envRec.[[ThisBindingStatus]] is not lexical. BindingStatus::Lexical => { panic!("There is no this for a lexical function record"); } + // 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception. BindingStatus::Uninitialized => { - context.throw_reference_error("Uninitialised binding for this function") + context.throw_reference_error("Uninitialized binding for this function") } + // 3. Return envRec.[[ThisValue]]. BindingStatus::Initialized => Ok(self.this_value.clone()), } } - fn has_super_binding(&self) -> bool { - if let BindingStatus::Lexical = self.this_binding_status { - false - } else { - !self.home_object.is_undefined() - } - } - fn with_base_object(&self) -> Option { None } @@ -215,7 +260,7 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord { fn recursive_create_mutable_binding( &self, - name: String, + name: &str, deletion: bool, _scope: VariableScope, context: &mut Context, @@ -225,7 +270,7 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord { fn recursive_create_immutable_binding( &self, - name: String, + name: &str, deletion: bool, _scope: VariableScope, context: &mut Context, diff --git a/boa/src/environment/global_environment_record.rs b/boa/src/environment/global_environment_record.rs index f390811078..df0432b3cd 100644 --- a/boa/src/environment/global_environment_record.rs +++ b/boa/src/environment/global_environment_record.rs @@ -33,7 +33,7 @@ pub struct GlobalEnvironmentRecord { impl GlobalEnvironmentRecord { pub fn new(global: JsObject, this_value: JsObject) -> GlobalEnvironmentRecord { let obj_rec = ObjectEnvironmentRecord { - bindings: global.into(), + bindings: global, outer_env: None, /// Object Environment Records created for with statements (13.11) /// can provide their binding object as an implicit this value for use in function calls. @@ -53,156 +53,326 @@ impl GlobalEnvironmentRecord { } } + /// `9.1.1.4.12 HasVarDeclaration ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-hasvardeclaration pub fn has_var_declaration(&self, name: &str) -> bool { + // 1. Let varDeclaredNames be envRec.[[VarNames]]. + // 2. If varDeclaredNames contains N, return true. + // 3. Return false. self.var_names.borrow().contains(name) } - pub fn has_lexical_declaration(&self, name: &str) -> bool { - self.declarative_record.has_binding(name) + /// `9.1.1.4.13 HasLexicalDeclaration ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-haslexicaldeclaration + pub fn has_lexical_declaration(&self, name: &str, context: &mut Context) -> JsResult { + // 1. Let DclRec be envRec.[[DeclarativeRecord]]. + // 2. Return DclRec.HasBinding(N). + self.declarative_record.has_binding(name, context) } - pub fn has_restricted_global_property(&self, name: &str) -> bool { + /// `9.1.1.4.14 HasRestrictedGlobalProperty ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty + pub fn has_restricted_global_property( + &self, + name: &str, + context: &mut Context, + ) -> JsResult { + // 1. Let ObjRec be envRec.[[ObjectRecord]]. + // 2. Let globalObject be ObjRec.[[BindingObject]]. let global_object = &self.object_record.bindings; - let existing_prop = global_object.get_property(name); - match existing_prop { - Some(desc) => { - if desc.expect_configurable() { - return false; - } - true - } - None => false, + + // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). + let existing_prop = global_object.__get_own_property__(&name.into(), context)?; + + if let Some(existing_prop) = existing_prop { + // 5. If existingProp.[[Configurable]] is true, return false. + // 6. Return true. + Ok(!existing_prop.expect_configurable()) + } else { + // 4. If existingProp is undefined, return false. + Ok(false) } } - pub fn can_declare_global_var(&self, name: &str) -> bool { + /// `9.1.1.4.15 CanDeclareGlobalVar ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar + pub fn can_declare_global_var(&self, name: &str, context: &mut Context) -> JsResult { + // 1. Let ObjRec be envRec.[[ObjectRecord]]. + // 2. Let globalObject be ObjRec.[[BindingObject]]. let global_object = &self.object_record.bindings; - if global_object.has_field(name) { - true - } else { - global_object.is_extensible() + + // 3. Let hasProperty be ? HasOwnProperty(globalObject, N). + let has_property = global_object.has_own_property(name, context)?; + + // 4. If hasProperty is true, return true. + if has_property { + return Ok(true); } + + // 5. Return ? IsExtensible(globalObject). + global_object.is_extensible(context) } - pub fn can_declare_global_function(&self, name: &str) -> bool { + /// `9.1.1.4.16 CanDeclareGlobalFunction ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction + pub fn can_declare_global_function(&self, name: &str, context: &mut Context) -> JsResult { + // 1. Let ObjRec be envRec.[[ObjectRecord]]. + // 2. Let globalObject be ObjRec.[[BindingObject]]. let global_object = &self.object_record.bindings; - let existing_prop = global_object.get_property(name); - match existing_prop { - Some(prop) => { - prop.expect_configurable() - || prop - .enumerable() - .zip(prop.writable()) - .map_or(false, |(a, b)| a && b) + + // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). + let existing_prop = global_object.__get_own_property__(&name.into(), context)?; + + if let Some(existing_prop) = existing_prop { + // 5. If existingProp.[[Configurable]] is true, return true. + // 6. If IsDataDescriptor(existingProp) is true and existingProp has attribute values { [[Writable]]: true, [[Enumerable]]: true }, return true. + if existing_prop.expect_configurable() + || matches!( + ( + existing_prop.is_data_descriptor(), + existing_prop.writable(), + existing_prop.enumerable(), + ), + (true, Some(true), Some(true)) + ) + { + Ok(true) + } else { + // 7. Return false. + Ok(false) } - None => global_object.is_extensible(), + } else { + // 4. If existingProp is undefined, return ? IsExtensible(globalObject). + global_object.is_extensible(context) } } + /// `9.1.1.4.17 CreateGlobalVarBinding ( N, D )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding pub fn create_global_var_binding( &mut self, - name: String, + name: &str, deletion: bool, context: &mut Context, ) -> JsResult<()> { - let obj_rec = &mut self.object_record; - let global_object = &obj_rec.bindings; - let has_property = global_object.has_field(name.as_str()); - let extensible = global_object.is_extensible(); + // 1. Let ObjRec be envRec.[[ObjectRecord]]. + // 2. Let globalObject be ObjRec.[[BindingObject]]. + let global_object = &self.object_record.bindings; + + // 3. Let hasProperty be ? HasOwnProperty(globalObject, N). + let has_property = global_object.has_own_property(name, context)?; + // 4. Let extensible be ? IsExtensible(globalObject). + let extensible = global_object.is_extensible(context)?; + + // 5. If hasProperty is false and extensible is true, then if !has_property && extensible { - obj_rec.create_mutable_binding(name.clone(), deletion, false, context)?; - obj_rec.initialize_binding(&name, JsValue::undefined(), context)?; + // a. Perform ? ObjRec.CreateMutableBinding(N, D). + self.object_record + .create_mutable_binding(name, deletion, false, context)?; + // b. Perform ? ObjRec.InitializeBinding(N, undefined). + self.object_record + .initialize_binding(name, JsValue::undefined(), context)?; } + // 6. Let varDeclaredNames be envRec.[[VarNames]]. let mut var_declared_names = self.var_names.borrow_mut(); - if !var_declared_names.contains(name.as_str()) { - var_declared_names.insert(name.into_boxed_str()); + // 7. If varDeclaredNames does not contain N, then + if !var_declared_names.contains(name) { + // a. Append N to varDeclaredNames. + var_declared_names.insert(name.into()); } + + // 8. Return NormalCompletion(empty). Ok(()) } - pub fn create_global_function_binding(&mut self, name: &str, value: JsValue, deletion: bool) { - let global_object = &mut self.object_record.bindings; - let existing_prop = global_object.get_property(name); - // TODO: change to desc.is_undefined() - let desc = match existing_prop { - Some(desc) if desc.expect_configurable() => PropertyDescriptor::builder().value(value), - Some(_) => PropertyDescriptor::builder() - .value(value) + /// `9.1.1.4.18 CreateGlobalFunctionBinding ( N, V, D )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding + pub fn create_global_function_binding( + &mut self, + name: &str, + value: JsValue, + deletion: bool, + context: &mut Context, + ) -> JsResult<()> { + // 1. Let ObjRec be envRec.[[ObjectRecord]]. + // 2. Let globalObject be ObjRec.[[BindingObject]]. + let global_object = &self.object_record.bindings; + + // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). + let existing_prop = global_object.__get_own_property__(&name.into(), context)?; + + // 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then + let desc = if existing_prop + .map(|f| f.expect_configurable()) + .unwrap_or(true) + { + // a. Let desc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }. + PropertyDescriptor::builder() + .value(value.clone()) .writable(true) .enumerable(true) - .configurable(deletion), - None => PropertyDescriptor::builder().value(value), + .configurable(deletion) + .build() + // 5. Else, + } else { + // a. Let desc be the PropertyDescriptor { [[Value]]: V }. + PropertyDescriptor::builder().value(value.clone()).build() }; - // TODO: fix spec by adding Set and append name to varDeclaredNames - global_object - .as_object() - .expect("global object") - .insert(name, desc); + // 6. Perform ? DefinePropertyOrThrow(globalObject, N, desc). + global_object.define_property_or_throw(name, desc, context)?; + // 7. Perform ? Set(globalObject, N, V, false). + global_object.set(name, value, false, context)?; + + // 8. Let varDeclaredNames be envRec.[[VarNames]]. + // 9. If varDeclaredNames does not contain N, then + if !self.var_names.borrow().contains(name) { + // a. Append N to varDeclaredNames. + self.var_names.borrow_mut().insert(name.into()); + } + + // 10. Return NormalCompletion(empty). + Ok(()) } } impl EnvironmentRecordTrait for GlobalEnvironmentRecord { - fn has_binding(&self, name: &str) -> bool { - if self.declarative_record.has_binding(name) { - return true; + /// `9.1.1.4.1 HasBinding ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-hasbinding-n + fn has_binding(&self, name: &str, context: &mut Context) -> JsResult { + // 1. Let DclRec be envRec.[[DeclarativeRecord]]. + // 2. If DclRec.HasBinding(N) is true, return true. + if self.declarative_record.has_binding(name, context)? { + return Ok(true); } - self.object_record.has_binding(name) + + // 3. Let ObjRec be envRec.[[ObjectRecord]]. + // 4. Return ? ObjRec.HasBinding(N). + self.object_record.has_binding(name, context) } + /// `9.1.1.4.2 CreateMutableBinding ( N, D )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-createmutablebinding-n-d fn create_mutable_binding( &self, - name: String, + name: &str, deletion: bool, allow_name_reuse: bool, context: &mut Context, ) -> JsResult<()> { - if !allow_name_reuse && self.declarative_record.has_binding(&name) { + // 1. Let DclRec be envRec.[[DeclarativeRecord]]. + // 2. If DclRec.HasBinding(N) is true, throw a TypeError exception. + if !allow_name_reuse && self.declarative_record.has_binding(name, context)? { return Err( context.construct_type_error(format!("Binding already exists for {}", name)) ); } + // 3. Return DclRec.CreateMutableBinding(N, D). self.declarative_record .create_mutable_binding(name, deletion, allow_name_reuse, context) } + /// `9.1.1.4.3 CreateImmutableBinding ( N, S )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-createimmutablebinding-n-s fn create_immutable_binding( &self, - name: String, + name: &str, strict: bool, context: &mut Context, ) -> JsResult<()> { - if self.declarative_record.has_binding(&name) { + // 1. Let DclRec be envRec.[[DeclarativeRecord]]. + // 2. If DclRec.HasBinding(N) is true, throw a TypeError exception. + if self.declarative_record.has_binding(name, context)? { return Err( context.construct_type_error(format!("Binding already exists for {}", name)) ); } + // 3. Return DclRec.CreateImmutableBinding(N, S). self.declarative_record .create_immutable_binding(name, strict, context) } + /// `9.1.1.4.4 InitializeBinding ( N, V )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-initializebinding-n-v fn initialize_binding( &self, name: &str, value: JsValue, context: &mut Context, ) -> JsResult<()> { - if self.declarative_record.has_binding(name) { + // 1. Let DclRec be envRec.[[DeclarativeRecord]]. + // 2. If DclRec.HasBinding(N) is true, then + if self.declarative_record.has_binding(name, context)? { + // a. Return DclRec.InitializeBinding(N, V). return self .declarative_record .initialize_binding(name, value, context); } + // 3. Assert: If the binding exists, it must be in the object Environment Record. assert!( - self.object_record.has_binding(name), + self.object_record.has_binding(name, context)?, "Binding must be in object_record" ); + + // 4. Let ObjRec be envRec.[[ObjectRecord]]. + // 5. Return ? ObjRec.InitializeBinding(N, V). self.object_record.initialize_binding(name, value, context) } + /// `9.1.1.4.5 SetMutableBinding ( N, V, S )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-setmutablebinding-n-v-s fn set_mutable_binding( &self, name: &str, @@ -210,64 +380,130 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord { strict: bool, context: &mut Context, ) -> JsResult<()> { - if self.declarative_record.has_binding(name) { + // 1. Let DclRec be envRec.[[DeclarativeRecord]]. + // 2. If DclRec.HasBinding(N) is true, then + if self.declarative_record.has_binding(name, context)? { + // a. Return DclRec.SetMutableBinding(N, V, S). return self .declarative_record .set_mutable_binding(name, value, strict, context); } + + // 3. Let ObjRec be envRec.[[ObjectRecord]]. + // 4. Return ? ObjRec.SetMutableBinding(N, V, S). self.object_record .set_mutable_binding(name, value, strict, context) } + /// `9.1.1.4.6 GetBindingValue ( N, S )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-getbindingvalue-n-s fn get_binding_value( &self, name: &str, strict: bool, context: &mut Context, ) -> JsResult { - if self.declarative_record.has_binding(name) { + // 1. Let DclRec be envRec.[[DeclarativeRecord]]. + // 2. If DclRec.HasBinding(N) is true, then + if self.declarative_record.has_binding(name, context)? { + // a. Return DclRec.GetBindingValue(N, S). return self .declarative_record .get_binding_value(name, strict, context); } + + // 3. Let ObjRec be envRec.[[ObjectRecord]]. + // 4. Return ? ObjRec.GetBindingValue(N, S). self.object_record.get_binding_value(name, strict, context) } - fn delete_binding(&self, name: &str) -> bool { - if self.declarative_record.has_binding(name) { - return self.declarative_record.delete_binding(name); + /// `9.1.1.4.7 DeleteBinding ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-deletebinding-n + fn delete_binding(&self, name: &str, context: &mut Context) -> JsResult { + // 1. Let DclRec be envRec.[[DeclarativeRecord]]. + // 2. If DclRec.HasBinding(N) is true, then + if self.declarative_record.has_binding(name, context)? { + // a. Return DclRec.DeleteBinding(N). + return self.declarative_record.delete_binding(name, context); } - let global: &JsValue = &self.object_record.bindings; - if global.has_field(name) { - let status = self.object_record.delete_binding(name); + // 3. Let ObjRec be envRec.[[ObjectRecord]]. + // 4. Let globalObject be ObjRec.[[BindingObject]]. + let global_object = &self.object_record.bindings; + + // 5. Let existingProp be ? HasOwnProperty(globalObject, N). + // 6. If existingProp is true, then + if global_object.has_own_property(name, context)? { + // a. Let status be ? ObjRec.DeleteBinding(N). + let status = self.object_record.delete_binding(name, context)?; + + // b. If status is true, then if status { - let mut var_names = self.var_names.borrow_mut(); - if var_names.contains(name) { - var_names.remove(name); - return status; - } + // i. Let varNames be envRec.[[VarNames]]. + // ii. If N is an element of varNames, remove that element from the varNames. + self.var_names.borrow_mut().remove(name); } + + // c. Return status. + return Ok(status); } - true + + // 7. Return true. + Ok(true) } + /// `9.1.1.4.8 HasThisBinding ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-hasthisbinding fn has_this_binding(&self) -> bool { + // 1. Return true. true } - fn get_this_binding(&self, _context: &mut Context) -> JsResult { - Ok(self.global_this_binding.clone().into()) - } - + /// `9.1.1.4.9 HasSuperBinding ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-hassuperbinding fn has_super_binding(&self) -> bool { + // 1. Return false. false } + /// `9.1.1.4.10 WithBaseObject ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-withbaseobject fn with_base_object(&self) -> Option { + // 1. Return undefined. None } + /// `9.1.1.4.11 GetThisBinding ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-getthisbinding + fn get_this_binding(&self, _context: &mut Context) -> JsResult { + // 1. Return envRec.[[GlobalThisValue]]. + Ok(self.global_this_binding.clone().into()) + } + fn get_outer_environment(&self) -> Option { None } @@ -287,7 +523,7 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord { fn recursive_create_mutable_binding( &self, - name: String, + name: &str, deletion: bool, _scope: VariableScope, context: &mut Context, @@ -297,7 +533,7 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord { fn recursive_create_immutable_binding( &self, - name: String, + name: &str, deletion: bool, _scope: VariableScope, context: &mut Context, diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 69aa271ff8..e011e019b1 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -95,7 +95,7 @@ impl Context { pub(crate) fn create_mutable_binding( &mut self, - name: String, + name: &str, deletion: bool, scope: VariableScope, ) -> JsResult<()> { @@ -105,7 +105,7 @@ impl Context { pub(crate) fn create_immutable_binding( &mut self, - name: String, + name: &str, deletion: bool, scope: VariableScope, ) -> JsResult<()> { @@ -139,8 +139,9 @@ impl Context { .clone() } - pub(crate) fn has_binding(&mut self, name: &str) -> bool { - self.get_current_environment().recursive_has_binding(name) + pub(crate) fn has_binding(&mut self, name: &str) -> JsResult { + self.get_current_environment() + .recursive_has_binding(name, self) } pub(crate) fn get_binding_value(&mut self, name: &str) -> JsResult { diff --git a/boa/src/environment/object_environment_record.rs b/boa/src/environment/object_environment_record.rs index 99be2a5121..40ccd06b17 100644 --- a/boa/src/environment/object_environment_record.rs +++ b/boa/src/environment/object_environment_record.rs @@ -16,19 +16,19 @@ use crate::{ gc::{Finalize, Trace}, object::JsObject, property::PropertyDescriptor, + symbol::WellKnownSymbols, Context, JsResult, JsValue, }; #[derive(Debug, Trace, Finalize, Clone)] pub struct ObjectEnvironmentRecord { - // TODO: bindings should be an object. - pub bindings: JsValue, + pub bindings: JsObject, pub with_environment: bool, pub outer_env: Option, } impl ObjectEnvironmentRecord { - pub fn new(object: JsValue, environment: Option) -> ObjectEnvironmentRecord { + pub fn new(object: JsObject, environment: Option) -> ObjectEnvironmentRecord { ObjectEnvironmentRecord { bindings: object, outer_env: environment, @@ -43,124 +43,208 @@ impl ObjectEnvironmentRecord { } impl EnvironmentRecordTrait for ObjectEnvironmentRecord { - fn has_binding(&self, name: &str) -> bool { - if self.bindings.has_field(name) { - if self.with_environment { - // TODO: implement unscopables + /// `9.1.1.2.1 HasBinding ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n + fn has_binding(&self, name: &str, context: &mut Context) -> JsResult { + // 1. Let bindingObject be envRec.[[BindingObject]]. + // 2. Let foundBinding be ? HasProperty(bindingObject, N). + // 3. If foundBinding is false, return false. + if !self.bindings.has_property(name, context)? { + return Ok(false); + } + + // 4. If envRec.[[IsWithEnvironment]] is false, return true. + if !self.with_environment { + return Ok(true); + } + + // 5. Let unscopables be ? Get(bindingObject, @@unscopables). + // 6. If Type(unscopables) is Object, then + if let Some(unscopables) = self + .bindings + .get(WellKnownSymbols::unscopables(), context)? + .as_object() + { + // a. Let blocked be ! ToBoolean(? Get(unscopables, N)). + // b. If blocked is true, return false. + if unscopables.get(name, context)?.to_boolean() { + return Ok(false); } - true - } else { - false } + + // 7. Return true. + Ok(true) } + /// `9.1.1.2.2 CreateMutableBinding ( N, D )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-createmutablebinding-n-d fn create_mutable_binding( &self, - name: String, + name: &str, deletion: bool, _allow_name_reuse: bool, - _context: &mut Context, + context: &mut Context, ) -> JsResult<()> { - // TODO: could save time here and not bother generating a new undefined object, - // only for it to be replace with the real value later. We could just add the name to a Vector instead - let bindings = &self.bindings; - let prop = PropertyDescriptor::builder() - .value(JsValue::undefined()) - .writable(true) - .enumerable(true) - .configurable(deletion); - - bindings.set_property(name, prop); + // 1. Let bindingObject be envRec.[[BindingObject]]. + // 2. Return ? DefinePropertyOrThrow(bindingObject, N, PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }). + self.bindings.define_property_or_throw( + name, + PropertyDescriptor::builder() + .value(JsValue::undefined()) + .writable(true) + .enumerable(true) + .configurable(deletion), + context, + )?; Ok(()) } + /// `9.1.1.2.3 CreateImmutableBinding ( N, S )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-createimmutablebinding-n-s fn create_immutable_binding( &self, - _name: String, + _name: &str, _strict: bool, _context: &mut Context, ) -> JsResult<()> { Ok(()) } + /// `9.1.1.2.4 InitializeBinding ( N, V )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-initializebinding-n-v fn initialize_binding( &self, name: &str, value: JsValue, context: &mut Context, ) -> JsResult<()> { - // We should never need to check if a binding has been created, - // As all calls to create_mutable_binding are followed by initialized binding - // The below is just a check. - debug_assert!(self.has_binding(name)); + // 1. Return ? envRec.SetMutableBinding(N, V, false). self.set_mutable_binding(name, value, false, context) } + /// `9.1.1.2.5 SetMutableBinding ( N, V, S )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-setmutablebinding-n-v-s fn set_mutable_binding( &self, name: &str, value: JsValue, strict: bool, - _context: &mut Context, + context: &mut Context, ) -> JsResult<()> { - debug_assert!(value.is_object() || value.is_function()); - let property = PropertyDescriptor::builder() - .value(value) - .enumerable(true) - .configurable(strict); - self.bindings - .as_object() - .expect("binding object") - .insert(name, property); + // 1. Let bindingObject be envRec.[[BindingObject]]. + // 2. Let stillExists be ? HasProperty(bindingObject, N). + let still_exists = self.bindings.has_property(name, context)?; + + // 3. If stillExists is false and S is true, throw a ReferenceError exception. + if !still_exists && strict { + return Err(context.construct_reference_error("Binding already exists")); + } + + // 4. Return ? Set(bindingObject, N, V, S). + self.bindings.set(name, value, strict, context)?; Ok(()) } + /// `9.1.1.2.6 GetBindingValue ( N, S )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-getbindingvalue-n-s fn get_binding_value( &self, name: &str, strict: bool, context: &mut Context, ) -> JsResult { - if self.bindings.has_field(name) { - Ok(self - .bindings - .get_property(name) - .as_ref() - .and_then(|prop| prop.value()) - .cloned() - .unwrap_or_default()) - } else if strict { - context.throw_reference_error(format!("{} has no binding", name)) - } else { - Ok(JsValue::undefined()) + // 1. Let bindingObject be envRec.[[BindingObject]]. + // 2. Let value be ? HasProperty(bindingObject, N). + // 3. If value is false, then + if !self.bindings.__has_property__(&name.into(), context)? { + // a. If S is false, return the value undefined; otherwise throw a ReferenceError exception. + if !strict { + return Ok(JsValue::undefined()); + } else { + return context.throw_reference_error(format!("{} has no binding", name)); + } } + + // 4. Return ? Get(bindingObject, N). + self.bindings.get(name, context) } - fn delete_binding(&self, name: &str) -> bool { - self.bindings.remove_property(name); - true + /// `9.1.1.2.7 DeleteBinding ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-deletebinding-n + fn delete_binding(&self, name: &str, context: &mut Context) -> JsResult { + // 1. Let bindingObject be envRec.[[BindingObject]]. + // 2. Return ? bindingObject.[[Delete]](N). + self.bindings.__delete__(&name.into(), context) } + /// `9.1.1.2.8 HasThisBinding ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hasthisbinding fn has_this_binding(&self) -> bool { + // 1. Return false. false } - fn get_this_binding(&self, _context: &mut Context) -> JsResult { - Ok(JsValue::undefined()) - } - + /// `9.1.1.2.9 HasSuperBinding ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hassuperbinding fn has_super_binding(&self) -> bool { + // 1. Return false. false } + /// `9.1.1.2.10 WithBaseObject ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hassuperbinding fn with_base_object(&self) -> Option { - // Object Environment Records return undefined as their - // WithBaseObject unless their withEnvironment flag is true. + // 1. If envRec.[[IsWithEnvironment]] is true, return envRec.[[BindingObject]]. + // 2. Otherwise, return undefined. if self.with_environment { - return Some(self.bindings.as_object().unwrap()); + Some(self.bindings.clone()) + } else { + None } + } - None + fn get_this_binding(&self, _context: &mut Context) -> JsResult { + Ok(JsValue::undefined()) } fn get_outer_environment_ref(&self) -> Option<&Environment> { diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index 38ea1d5b78..8a2e567559 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -199,7 +199,8 @@ impl JsObject { BindingStatus::Uninitialized }, JsValue::undefined(), - ); + context, + )?; let mut arguments_in_parameter_names = false; @@ -224,12 +225,7 @@ impl JsObject { { // Add arguments object let arguments_obj = create_unmapped_arguments_object(args, context)?; - local_env.create_mutable_binding( - "arguments".to_string(), - false, - true, - context, - )?; + local_env.create_mutable_binding("arguments", false, true, context)?; local_env.initialize_binding("arguments", arguments_obj, context)?; } @@ -280,7 +276,8 @@ impl JsObject { BindingStatus::Uninitialized }, JsValue::undefined(), - ); + context, + )?; context.push_environment(second_env); } 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 1d2fe9fc7c..e19840abda 100644 --- a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -95,14 +95,10 @@ impl Executable for FunctionDecl { FunctionFlags::CONSTRUCTABLE, )?; - if context.has_binding(self.name()) { - context.set_mutable_binding(self.name(), val, true)?; + if context.has_binding(self.name())? { + context.set_mutable_binding(self.name(), val, context.strict())?; } else { - context.create_mutable_binding( - self.name().to_owned(), - false, - VariableScope::Function, - )?; + context.create_mutable_binding(self.name(), false, VariableScope::Function)?; context.initialize_binding(self.name(), val)?; } diff --git a/boa/src/syntax/ast/node/declaration/mod.rs b/boa/src/syntax/ast/node/declaration/mod.rs index 4765602320..84c59f83bd 100644 --- a/boa/src/syntax/ast/node/declaration/mod.rs +++ b/boa/src/syntax/ast/node/declaration/mod.rs @@ -104,26 +104,26 @@ impl Executable for DeclarationList { match &decl { Declaration::Identifier { ident, init } => { - if self.is_var() && context.has_binding(ident.as_ref()) { + if self.is_var() && context.has_binding(ident.as_ref())? { if init.is_some() { - context.set_mutable_binding(ident.as_ref(), val, true)?; + context.set_mutable_binding(ident.as_ref(), val, context.strict())?; } continue; } match &self { Const(_) => context.create_immutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?, Let(_) => context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?, Var(_) => context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Function, )?, @@ -133,26 +133,30 @@ impl Executable for DeclarationList { } Declaration::Pattern(p) => { for (ident, value) in p.run(None, context)? { - if self.is_var() && context.has_binding(ident.as_ref()) { + if self.is_var() && context.has_binding(ident.as_ref())? { if !value.is_undefined() { - context.set_mutable_binding(ident.as_ref(), value, true)?; + context.set_mutable_binding( + ident.as_ref(), + value, + context.strict(), + )?; } continue; } match &self { Const(_) => context.create_immutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?, Let(_) => context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?, Var(_) => context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Function, )?, diff --git a/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs index bf08306227..98933acb67 100644 --- a/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs @@ -112,12 +112,16 @@ impl Executable for ForInLoop { match self.variable() { Node::Identifier(ref name) => { - if context.has_binding(name.as_ref()) { + if context.has_binding(name.as_ref())? { // Binding already exists - context.set_mutable_binding(name.as_ref(), next_result, true)?; + context.set_mutable_binding( + name.as_ref(), + next_result.clone(), + context.strict(), + )?; } else { context.create_mutable_binding( - name.as_ref().to_owned(), + name.as_ref(), true, VariableScope::Function, )?; @@ -132,15 +136,15 @@ impl Executable for ForInLoop { match &var { Declaration::Identifier { ident, .. } => { - if context.has_binding(ident.as_ref()) { + if context.has_binding(ident.as_ref())? { context.set_mutable_binding( ident.as_ref(), next_result, - true, + context.strict(), )?; } else { context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Function, )?; @@ -149,11 +153,15 @@ impl Executable for ForInLoop { } Declaration::Pattern(p) => { for (ident, value) in p.run(Some(next_result), context)? { - if context.has_binding(ident.as_ref()) { - context.set_mutable_binding(ident.as_ref(), value, true)?; + if context.has_binding(ident.as_ref())? { + context.set_mutable_binding( + ident.as_ref(), + value, + context.strict(), + )?; } else { context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Function, )?; @@ -178,7 +186,7 @@ impl Executable for ForInLoop { match &var { Declaration::Identifier { ident, .. } => { context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?; @@ -187,7 +195,7 @@ impl Executable for ForInLoop { Declaration::Pattern(p) => { for (ident, value) in p.run(Some(next_result), context)? { context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?; @@ -211,7 +219,7 @@ impl Executable for ForInLoop { match &var { Declaration::Identifier { ident, .. } => { context.create_immutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?; @@ -220,7 +228,7 @@ impl Executable for ForInLoop { Declaration::Pattern(p) => { for (ident, value) in p.run(Some(next_result), context)? { context.create_immutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?; diff --git a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs index 1cfc3fd492..5ecd4c6dbf 100644 --- a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs @@ -100,12 +100,16 @@ impl Executable for ForOfLoop { match self.variable() { Node::Identifier(ref name) => { - if context.has_binding(name.as_ref()) { + if context.has_binding(name.as_ref())? { // Binding already exists - context.set_mutable_binding(name.as_ref(), next_result, true)?; + context.set_mutable_binding( + name.as_ref(), + next_result.clone(), + context.strict(), + )?; } else { context.create_mutable_binding( - name.as_ref().to_owned(), + name.as_ref(), true, VariableScope::Function, )?; @@ -120,15 +124,15 @@ impl Executable for ForOfLoop { match &var { Declaration::Identifier { ident, .. } => { - if context.has_binding(ident.as_ref()) { + if context.has_binding(ident.as_ref())? { context.set_mutable_binding( ident.as_ref(), next_result, - true, + context.strict(), )?; } else { context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Function, )?; @@ -137,11 +141,15 @@ impl Executable for ForOfLoop { } Declaration::Pattern(p) => { for (ident, value) in p.run(Some(next_result), context)? { - if context.has_binding(ident.as_ref()) { - context.set_mutable_binding(ident.as_ref(), value, true)?; + if context.has_binding(ident.as_ref())? { + context.set_mutable_binding( + ident.as_ref(), + value, + context.strict(), + )?; } else { context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Function, )?; @@ -166,7 +174,7 @@ impl Executable for ForOfLoop { match &var { Declaration::Identifier { ident, .. } => { context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?; @@ -175,7 +183,7 @@ impl Executable for ForOfLoop { Declaration::Pattern(p) => { for (ident, value) in p.run(Some(next_result), context)? { context.create_mutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?; @@ -199,7 +207,7 @@ impl Executable for ForOfLoop { match &var { Declaration::Identifier { ident, .. } => { context.create_immutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?; @@ -208,7 +216,7 @@ impl Executable for ForOfLoop { Declaration::Pattern(p) => { for (ident, value) in p.run(Some(next_result), context)? { context.create_immutable_binding( - ident.to_string(), + ident.as_ref(), false, VariableScope::Block, )?; diff --git a/boa/src/syntax/ast/node/operator/assign/mod.rs b/boa/src/syntax/ast/node/operator/assign/mod.rs index 0f993de17e..d2af1b6fc1 100644 --- a/boa/src/syntax/ast/node/operator/assign/mod.rs +++ b/boa/src/syntax/ast/node/operator/assign/mod.rs @@ -58,15 +58,11 @@ impl Executable for Assign { let val = self.rhs().run(context)?; match self.lhs() { Node::Identifier(ref name) => { - if context.has_binding(name.as_ref()) { + if context.has_binding(name.as_ref())? { // Binding already exists - context.set_mutable_binding(name.as_ref(), val.clone(), true)?; + context.set_mutable_binding(name.as_ref(), val.clone(), context.strict())?; } else { - context.create_mutable_binding( - name.as_ref().to_owned(), - true, - VariableScope::Function, - )?; + context.create_mutable_binding(name.as_ref(), true, VariableScope::Function)?; context.initialize_binding(name.as_ref(), val.clone())?; } } diff --git a/boa/src/syntax/ast/node/operator/bin_op/mod.rs b/boa/src/syntax/ast/node/operator/bin_op/mod.rs index 3ab7995ae9..7531017d0a 100644 --- a/boa/src/syntax/ast/node/operator/bin_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/bin_op/mod.rs @@ -203,7 +203,7 @@ impl Executable for BinOp { let v_a = context.get_binding_value(name.as_ref())?; let value = Self::run_assign(op, v_a, self.rhs(), context)?; - context.set_mutable_binding(name.as_ref(), value.clone(), true)?; + context.set_mutable_binding(name.as_ref(), value.clone(), context.strict())?; Ok(value) } Node::GetConstField(ref get_const_field) => { diff --git a/boa/src/syntax/ast/node/try_node/mod.rs b/boa/src/syntax/ast/node/try_node/mod.rs index 0bb5486a4a..6dac6a95e4 100644 --- a/boa/src/syntax/ast/node/try_node/mod.rs +++ b/boa/src/syntax/ast/node/try_node/mod.rs @@ -105,11 +105,7 @@ impl Executable for Try { context.push_environment(DeclarativeEnvironmentRecord::new(Some(env))); if let Some(param) = catch.parameter() { - context.create_mutable_binding( - param.to_owned(), - false, - VariableScope::Block, - )?; + context.create_mutable_binding(param, false, VariableScope::Block)?; context.initialize_binding(param, err)?; } } diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index 6ec0a6c0e9..a75a64b246 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -154,19 +154,6 @@ impl JsValue { } } - /// This will tell us if we can exten an object or not, not properly implemented yet - /// - /// For now always returns true. - /// - /// For scalar types it should be false, for objects check the private field for extensibilaty. - /// By default true. - /// - /// would turn `extensible` to `false` - /// would also turn `extensible` to `false` - pub(crate) fn is_extensible(&self) -> bool { - true - } - /// Returns true if the value is an object #[inline] pub fn is_object(&self) -> bool { @@ -365,19 +352,6 @@ impl JsValue { } } - /// Check to see if the Value has the field, mainly used by environment records. - #[inline] - pub(crate) fn has_field(&self, key: K) -> bool - where - K: Into, - { - let _timer = BoaProfiler::global().start_event("Value::has_field", "value"); - // todo: call `__has_property__` instead of directly getting from object - self.as_object() - .map(|object| object.borrow().properties().contains_key(&key.into())) - .unwrap_or(false) - } - /// Set the field in the value /// /// Similar to `7.3.4 Set ( O, P, V, Throw )`, but returns the value instead of a boolean. diff --git a/boa/src/vm/mod.rs b/boa/src/vm/mod.rs index 889a69021f..d9f2e263d3 100644 --- a/boa/src/vm/mod.rs +++ b/boa/src/vm/mod.rs @@ -259,28 +259,22 @@ impl<'a> Vm<'a> { let index = self.read::(); let name = &self.code.names[index as usize]; - self.context.create_mutable_binding( - name.to_string(), - false, - VariableScope::Function, - )?; + self.context + .create_mutable_binding(name, false, VariableScope::Function)?; } Opcode::DefLet => { let index = self.read::(); let name = &self.code.names[index as usize]; - self.context.create_mutable_binding( - name.to_string(), - false, - VariableScope::Block, - )?; + self.context + .create_mutable_binding(name, false, VariableScope::Block)?; } Opcode::DefConst => { let index = self.read::(); let name = &self.code.names[index as usize]; self.context.create_immutable_binding( - name.to_string(), + name.as_ref(), false, VariableScope::Block, )?; @@ -304,15 +298,13 @@ impl<'a> Vm<'a> { let value = self.pop(); let name = &self.code.names[index as usize]; - if self.context.has_binding(name) { + if self.context.has_binding(name)? { // Binding already exists - self.context.set_mutable_binding(name, value, true)?; + self.context + .set_mutable_binding(name, value, self.context.strict())?; } else { - self.context.create_mutable_binding( - name.to_string(), - true, - VariableScope::Function, - )?; + self.context + .create_mutable_binding(name, true, VariableScope::Function)?; self.context.initialize_binding(name, value)?; } }