Browse Source

Refactor `EnvironmentRecordTrait` functions (#1569)

pull/1578/head
raskad 3 years ago committed by GitHub
parent
commit
c5a4be02e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      boa/src/builtins/function/mod.rs
  2. 151
      boa/src/environment/declarative_environment_record.rs
  3. 26
      boa/src/environment/environment_record_trait.rs
  4. 95
      boa/src/environment/function_environment_record.rs
  5. 410
      boa/src/environment/global_environment_record.rs
  6. 9
      boa/src/environment/lexical_environment.rs
  7. 196
      boa/src/environment/object_environment_record.rs
  8. 13
      boa/src/object/gcobject.rs
  9. 10
      boa/src/syntax/ast/node/declaration/function_decl/mod.rs
  10. 24
      boa/src/syntax/ast/node/declaration/mod.rs
  11. 34
      boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs
  12. 34
      boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs
  13. 10
      boa/src/syntax/ast/node/operator/assign/mod.rs
  14. 2
      boa/src/syntax/ast/node/operator/bin_op/mod.rs
  15. 6
      boa/src/syntax/ast/node/try_node/mod.rs
  16. 26
      boa/src/value/mod.rs
  17. 28
      boa/src/vm/mod.rs

4
boa/src/builtins/function/mod.rs

@ -159,7 +159,7 @@ impl Function {
// Create binding // Create binding
local_env local_env
// Function parameters can share names in JavaScript... // 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"); .expect("Failed to create binding for rest param");
// Set Binding to value // Set Binding to value
@ -178,7 +178,7 @@ impl Function {
) { ) {
// Create binding // Create binding
local_env 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"); .expect("Failed to create binding");
// Set Binding to value // Set Binding to value

151
boa/src/environment/declarative_environment_record.rs

@ -49,27 +49,44 @@ impl DeclarativeEnvironmentRecord {
} }
impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord {
fn has_binding(&self, name: &str) -> bool { /// `9.1.1.1.1 HasBinding ( N )`
self.env_rec.borrow().contains_key(name) ///
/// 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<bool> {
// 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( fn create_mutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
allow_name_reuse: bool, allow_name_reuse: bool,
_context: &mut Context, _context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
// 1. Assert: envRec does not already have a binding for N.
if !allow_name_reuse { if !allow_name_reuse {
assert!( assert!(
!self.env_rec.borrow().contains_key(name.as_str()), !self.env_rec.borrow().contains_key(name),
"Identifier {} has already been declared", "Identifier {} has already been declared",
name 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( self.env_rec.borrow_mut().insert(
name.into_boxed_str(), name.into(),
DeclarativeEnvironmentRecordBinding { DeclarativeEnvironmentRecordBinding {
value: None, value: None,
can_delete: deletion, can_delete: deletion,
@ -77,23 +94,34 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord {
strict: false, strict: false,
}, },
); );
// 3. Return NormalCompletion(empty).
Ok(()) 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( fn create_immutable_binding(
&self, &self,
name: String, name: &str,
strict: bool, strict: bool,
_context: &mut Context, _context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
// 1. Assert: envRec does not already have a binding for N.
assert!( assert!(
!self.env_rec.borrow().contains_key(name.as_str()), !self.env_rec.borrow().contains_key(name),
"Identifier {} has already been declared", "Identifier {} has already been declared",
name 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( self.env_rec.borrow_mut().insert(
name.into_boxed_str(), name.into(),
DeclarativeEnvironmentRecordBinding { DeclarativeEnvironmentRecordBinding {
value: None, value: None,
can_delete: true, can_delete: true,
@ -101,9 +129,17 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord {
strict, strict,
}, },
); );
// 3. Return NormalCompletion(empty).
Ok(()) 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( fn initialize_binding(
&self, &self,
name: &str, name: &str,
@ -112,13 +148,25 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord {
) -> JsResult<()> { ) -> JsResult<()> {
if let Some(ref mut record) = self.env_rec.borrow_mut().get_mut(name) { if let Some(ref mut record) = self.env_rec.borrow_mut().get_mut(name) {
if record.value.is_none() { 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); record.value = Some(value);
// 4. Return NormalCompletion(empty).
return Ok(()); return Ok(());
} }
} }
// 1. Assert: envRec must have an uninitialized binding for N.
panic!("record must have binding for {}", name); 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)] #[allow(clippy::else_if_without_else)]
fn set_mutable_binding( fn set_mutable_binding(
&self, &self,
@ -127,49 +175,70 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord {
mut strict: bool, mut strict: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
// 1. If envRec does not have a binding for N, then
if self.env_rec.borrow().get(name).is_none() { if self.env_rec.borrow().get(name).is_none() {
// a. If S is true, throw a ReferenceError exception.
if strict { if strict {
return Err(context.construct_reference_error(format!("{} not found", name))); 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)?; self.initialize_binding(name, value, context)?;
// d. Return NormalCompletion(empty).
return Ok(()); 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 env_rec = self.env_rec.borrow();
let record = env_rec.get(name).unwrap(); let binding = env_rec.get(name).unwrap();
(record.strict, record.value.is_none(), record.mutable) (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( return Err(
context.construct_reference_error(format!("{} has not been initialized", name)) context.construct_reference_error(format!("{} has not been initialized", name))
); );
} // 4. Else if the binding for N in envRec is a mutable binding, change its bound value to V.
if record_mutable { } else if binding_mutable {
let mut env_rec = self.env_rec.borrow_mut(); let mut env_rec = self.env_rec.borrow_mut();
let record = env_rec.get_mut(name).unwrap(); let binding = env_rec.get_mut(name).unwrap();
record.value = Some(value); 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 { } else if strict {
return Err(context.construct_reference_error(format!( return Err(context
"Cannot mutate an immutable binding {}", .construct_type_error(format!("Cannot mutate an immutable binding {}", name)));
name
)));
} }
// 6. Return NormalCompletion(empty).
Ok(()) 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( fn get_binding_value(
&self, &self,
name: &str, name: &str,
_strict: bool, _strict: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 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(binding) = self.env_rec.borrow().get(name) {
if let Some(ref val) = binding.value { if let Some(ref val) = binding.value {
Ok(val.clone()) 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<bool> {
// 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) { match self.env_rec.borrow().get(name) {
Some(binding) => { Some(binding) => {
if binding.can_delete { if binding.can_delete {
self.env_rec.borrow_mut().remove(name); self.env_rec.borrow_mut().remove(name);
true Ok(true)
} else { } else {
false Ok(false)
} }
} }
None => panic!("env_rec has no binding for {}", name), 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 { fn has_this_binding(&self) -> bool {
// 1. Return false.
false false
} }
@ -203,10 +289,23 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord {
Ok(JsValue::undefined()) 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 { fn has_super_binding(&self) -> bool {
// 1. Return false.
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<JsObject> { fn with_base_object(&self) -> Option<JsObject> {
None None
} }

26
boa/src/environment/environment_record_trait.rs

@ -24,7 +24,7 @@ use std::fmt::Debug;
pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { pub trait EnvironmentRecordTrait: Debug + Trace + Finalize {
/// Determine if an Environment Record has a binding for the String value N. /// Determine if an Environment Record has a binding for the String value N.
/// Return true if it does and false if it does not. /// 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<bool>;
/// Create a new but uninitialized mutable binding in an Environment Record. The String value N is the text of the bound name. /// 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. /// 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. /// paraments with the same name.
fn create_mutable_binding( fn create_mutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
allow_name_reuse: bool, allow_name_reuse: bool,
context: &mut Context, 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. /// regardless of the strict mode setting of operations that reference that binding.
fn create_immutable_binding( fn create_immutable_binding(
&self, &self,
name: String, name: &str,
strict: bool, strict: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<()>; ) -> JsResult<()>;
@ -85,7 +85,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize {
/// The String value name is the text of the bound name. /// The String value name is the text of the bound name.
/// If a binding for name exists, remove the binding and return true. /// 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. /// 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<bool>;
/// Determine if an Environment Record establishes a this binding. /// Determine if an Environment Record establishes a this binding.
/// Return true if it does and false if it does not. /// 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 /// Create mutable binding while handling outer environments
fn recursive_create_mutable_binding( fn recursive_create_mutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
scope: VariableScope, scope: VariableScope,
context: &mut Context, context: &mut Context,
@ -146,7 +146,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize {
/// Create immutable binding while handling outer environments /// Create immutable binding while handling outer environments
fn recursive_create_immutable_binding( fn recursive_create_immutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
scope: VariableScope, scope: VariableScope,
context: &mut Context, context: &mut Context,
@ -168,7 +168,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize {
strict: bool, strict: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
if self.has_binding(name) { if self.has_binding(name, context)? {
self.set_mutable_binding(name, value, strict, context) self.set_mutable_binding(name, value, strict, context)
} else { } else {
self.get_outer_environment_ref() self.get_outer_environment_ref()
@ -184,7 +184,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize {
value: JsValue, value: JsValue,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
if self.has_binding(name) { if self.has_binding(name, context)? {
self.initialize_binding(name, value, context) self.initialize_binding(name, value, context)
} else { } else {
self.get_outer_environment_ref() 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 /// Check if a binding exists in current or any outer environment
fn recursive_has_binding(&self, name: &str) -> bool { fn recursive_has_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
self.has_binding(name) Ok(self.has_binding(name, context)?
|| match self.get_outer_environment_ref() { || match self.get_outer_environment_ref() {
Some(outer) => outer.recursive_has_binding(name), Some(outer) => outer.recursive_has_binding(name, context)?,
None => false, None => false,
} })
} }
/// Retrieve binding from current or any outer environment /// Retrieve binding from current or any outer environment
fn recursive_get_binding_value(&self, name: &str, context: &mut Context) -> JsResult<JsValue> { fn recursive_get_binding_value(&self, name: &str, context: &mut Context) -> JsResult<JsValue> {
if self.has_binding(name) { if self.has_binding(name, context)? {
self.get_binding_value(name, false, context) self.get_binding_value(name, false, context)
} else { } else {
match self.get_outer_environment_ref() { match self.get_outer_environment_ref() {

95
boa/src/environment/function_environment_record.rs

@ -64,7 +64,8 @@ impl FunctionEnvironmentRecord {
outer: Option<Environment>, outer: Option<Environment>,
binding_status: BindingStatus, binding_status: BindingStatus,
new_target: JsValue, new_target: JsValue,
) -> FunctionEnvironmentRecord { context: &mut Context,
) -> JsResult<FunctionEnvironmentRecord> {
let mut func_env = FunctionEnvironmentRecord { 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 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, function: f,
@ -75,36 +76,56 @@ impl FunctionEnvironmentRecord {
}; };
// If a `this` value has been passed, bind it to the environment // If a `this` value has been passed, bind it to the environment
if let Some(v) = this { 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<JsValue> { /// `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<JsValue> {
match self.this_binding_status { 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 => { BindingStatus::Lexical => {
panic!("Cannot bind to an arrow function!"); 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 => { BindingStatus::Initialized => {
todo!(); context.throw_reference_error("Cannot bind to an initialized function!")
// context.throw_reference_error("Cannot bind to an initialised function!")
} }
BindingStatus::Uninitialized => { BindingStatus::Uninitialized => {
// 3. Set envRec.[[ThisValue]] to V.
self.this_value = value.clone(); self.this_value = value.clone();
// 4. Set envRec.[[ThisBindingStatus]] to initialized.
self.this_binding_status = BindingStatus::Initialized; self.this_binding_status = BindingStatus::Initialized;
// 5. Return V.
Ok(value) 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 { pub fn get_super_base(&self) -> JsValue {
// 1. Let home be envRec.[[FunctionObject]].[[HomeObject]].
let home = &self.home_object; let home = &self.home_object;
// 2. If home has the value undefined, return undefined.
if home.is_undefined() { if home.is_undefined() {
JsValue::undefined() JsValue::undefined()
} else { } else {
// 3. Assert: Type(home) is Object.
assert!(home.is_object()); assert!(home.is_object());
// 4. Return ? home.[[GetPrototypeOf]]().
home.as_object() home.as_object()
.expect("home_object must be an Object") .expect("home_object must be an Object")
.prototype_instance() .prototype_instance()
@ -113,13 +134,13 @@ impl FunctionEnvironmentRecord {
} }
impl EnvironmentRecordTrait for FunctionEnvironmentRecord { impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
fn has_binding(&self, name: &str) -> bool { fn has_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
self.declarative_record.has_binding(name) self.declarative_record.has_binding(name, context)
} }
fn create_mutable_binding( fn create_mutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
allow_name_reuse: bool, allow_name_reuse: bool,
context: &mut Context, context: &mut Context,
@ -130,7 +151,7 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
fn create_immutable_binding( fn create_immutable_binding(
&self, &self,
name: String, name: &str,
strict: bool, strict: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
@ -169,34 +190,58 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
.get_binding_value(name, strict, context) .get_binding_value(name, strict, context)
} }
fn delete_binding(&self, name: &str) -> bool { fn delete_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
self.declarative_record.delete_binding(name) 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 { fn has_this_binding(&self) -> bool {
// 1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true.
!matches!(self.this_binding_status, BindingStatus::Lexical) !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<JsValue> { fn get_this_binding(&self, context: &mut Context) -> JsResult<JsValue> {
match self.this_binding_status { match self.this_binding_status {
// 1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
BindingStatus::Lexical => { BindingStatus::Lexical => {
panic!("There is no this for a lexical function record"); panic!("There is no this for a lexical function record");
} }
// 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception.
BindingStatus::Uninitialized => { 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()), 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<JsObject> { fn with_base_object(&self) -> Option<JsObject> {
None None
} }
@ -215,7 +260,7 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
fn recursive_create_mutable_binding( fn recursive_create_mutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
_scope: VariableScope, _scope: VariableScope,
context: &mut Context, context: &mut Context,
@ -225,7 +270,7 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
fn recursive_create_immutable_binding( fn recursive_create_immutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
_scope: VariableScope, _scope: VariableScope,
context: &mut Context, context: &mut Context,

410
boa/src/environment/global_environment_record.rs

@ -33,7 +33,7 @@ pub struct GlobalEnvironmentRecord {
impl GlobalEnvironmentRecord { impl GlobalEnvironmentRecord {
pub fn new(global: JsObject, this_value: JsObject) -> GlobalEnvironmentRecord { pub fn new(global: JsObject, this_value: JsObject) -> GlobalEnvironmentRecord {
let obj_rec = ObjectEnvironmentRecord { let obj_rec = ObjectEnvironmentRecord {
bindings: global.into(), bindings: global,
outer_env: None, outer_env: None,
/// Object Environment Records created for with statements (13.11) /// Object Environment Records created for with statements (13.11)
/// can provide their binding object as an implicit this value for use in function calls. /// 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 { 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) self.var_names.borrow().contains(name)
} }
pub fn has_lexical_declaration(&self, name: &str) -> bool { /// `9.1.1.4.13 HasLexicalDeclaration ( N )`
self.declarative_record.has_binding(name) ///
} /// More information:
/// - [ECMAScript reference][spec]
pub fn has_restricted_global_property(&self, name: &str) -> bool { ///
/// [spec]: https://tc39.es/ecma262/#sec-haslexicaldeclaration
pub fn has_lexical_declaration(&self, name: &str, context: &mut Context) -> JsResult<bool> {
// 1. Let DclRec be envRec.[[DeclarativeRecord]].
// 2. Return DclRec.HasBinding(N).
self.declarative_record.has_binding(name, context)
}
/// `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<bool> {
// 1. Let ObjRec be envRec.[[ObjectRecord]].
// 2. Let globalObject be ObjRec.[[BindingObject]].
let global_object = &self.object_record.bindings; let global_object = &self.object_record.bindings;
let existing_prop = global_object.get_property(name);
match existing_prop { // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
Some(desc) => { let existing_prop = global_object.__get_own_property__(&name.into(), context)?;
if desc.expect_configurable() {
return false; if let Some(existing_prop) = existing_prop {
} // 5. If existingProp.[[Configurable]] is true, return false.
true // 6. Return true.
} Ok(!existing_prop.expect_configurable())
None => false, } 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<bool> {
// 1. Let ObjRec be envRec.[[ObjectRecord]].
// 2. Let globalObject be ObjRec.[[BindingObject]].
let global_object = &self.object_record.bindings; let global_object = &self.object_record.bindings;
if global_object.has_field(name) {
true // 3. Let hasProperty be ? HasOwnProperty(globalObject, N).
} else { let has_property = global_object.has_own_property(name, context)?;
global_object.is_extensible()
// 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<bool> {
// 1. Let ObjRec be envRec.[[ObjectRecord]].
// 2. Let globalObject be ObjRec.[[BindingObject]].
let global_object = &self.object_record.bindings; let global_object = &self.object_record.bindings;
let existing_prop = global_object.get_property(name);
match existing_prop { // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
Some(prop) => { let existing_prop = global_object.__get_own_property__(&name.into(), context)?;
prop.expect_configurable()
|| prop if let Some(existing_prop) = existing_prop {
.enumerable() // 5. If existingProp.[[Configurable]] is true, return true.
.zip(prop.writable()) // 6. If IsDataDescriptor(existingProp) is true and existingProp has attribute values { [[Writable]]: true, [[Enumerable]]: true }, return true.
.map_or(false, |(a, b)| a && b) 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( pub fn create_global_var_binding(
&mut self, &mut self,
name: String, name: &str,
deletion: bool, deletion: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
let obj_rec = &mut self.object_record; // 1. Let ObjRec be envRec.[[ObjectRecord]].
let global_object = &obj_rec.bindings; // 2. Let globalObject be ObjRec.[[BindingObject]].
let has_property = global_object.has_field(name.as_str()); let global_object = &self.object_record.bindings;
let extensible = global_object.is_extensible();
// 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 { if !has_property && extensible {
obj_rec.create_mutable_binding(name.clone(), deletion, false, context)?; // a. Perform ? ObjRec.CreateMutableBinding(N, D).
obj_rec.initialize_binding(&name, JsValue::undefined(), context)?; 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(); let mut var_declared_names = self.var_names.borrow_mut();
if !var_declared_names.contains(name.as_str()) { // 7. If varDeclaredNames does not contain N, then
var_declared_names.insert(name.into_boxed_str()); if !var_declared_names.contains(name) {
// a. Append N to varDeclaredNames.
var_declared_names.insert(name.into());
} }
// 8. Return NormalCompletion(empty).
Ok(()) Ok(())
} }
pub fn create_global_function_binding(&mut self, name: &str, value: JsValue, deletion: bool) { /// `9.1.1.4.18 CreateGlobalFunctionBinding ( N, V, D )`
let global_object = &mut self.object_record.bindings; ///
let existing_prop = global_object.get_property(name); /// More information:
// TODO: change to desc.is_undefined() /// - [ECMAScript reference][spec]
let desc = match existing_prop { ///
Some(desc) if desc.expect_configurable() => PropertyDescriptor::builder().value(value), /// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
Some(_) => PropertyDescriptor::builder() pub fn create_global_function_binding(
.value(value) &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) .writable(true)
.enumerable(true) .enumerable(true)
.configurable(deletion), .configurable(deletion)
None => PropertyDescriptor::builder().value(value), .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 // 6. Perform ? DefinePropertyOrThrow(globalObject, N, desc).
global_object global_object.define_property_or_throw(name, desc, context)?;
.as_object() // 7. Perform ? Set(globalObject, N, V, false).
.expect("global object") global_object.set(name, value, false, context)?;
.insert(name, desc);
}
}
impl EnvironmentRecordTrait for GlobalEnvironmentRecord { // 8. Let varDeclaredNames be envRec.[[VarNames]].
fn has_binding(&self, name: &str) -> bool { // 9. If varDeclaredNames does not contain N, then
if self.declarative_record.has_binding(name) { if !self.var_names.borrow().contains(name) {
return true; // a. Append N to varDeclaredNames.
self.var_names.borrow_mut().insert(name.into());
} }
self.object_record.has_binding(name)
// 10. Return NormalCompletion(empty).
Ok(())
} }
}
impl EnvironmentRecordTrait for GlobalEnvironmentRecord {
/// `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<bool> {
// 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);
}
// 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( fn create_mutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
allow_name_reuse: bool, allow_name_reuse: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> 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( return Err(
context.construct_type_error(format!("Binding already exists for {}", name)) context.construct_type_error(format!("Binding already exists for {}", name))
); );
} }
// 3. Return DclRec.CreateMutableBinding(N, D).
self.declarative_record self.declarative_record
.create_mutable_binding(name, deletion, allow_name_reuse, context) .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( fn create_immutable_binding(
&self, &self,
name: String, name: &str,
strict: bool, strict: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> 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( return Err(
context.construct_type_error(format!("Binding already exists for {}", name)) context.construct_type_error(format!("Binding already exists for {}", name))
); );
} }
// 3. Return DclRec.CreateImmutableBinding(N, S).
self.declarative_record self.declarative_record
.create_immutable_binding(name, strict, context) .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( fn initialize_binding(
&self, &self,
name: &str, name: &str,
value: JsValue, value: JsValue,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> 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 return self
.declarative_record .declarative_record
.initialize_binding(name, value, context); .initialize_binding(name, value, context);
} }
// 3. Assert: If the binding exists, it must be in the object Environment Record.
assert!( assert!(
self.object_record.has_binding(name), self.object_record.has_binding(name, context)?,
"Binding must be in object_record" "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) 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( fn set_mutable_binding(
&self, &self,
name: &str, name: &str,
@ -210,64 +380,130 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord {
strict: bool, strict: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> 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 return self
.declarative_record .declarative_record
.set_mutable_binding(name, value, strict, context); .set_mutable_binding(name, value, strict, context);
} }
// 3. Let ObjRec be envRec.[[ObjectRecord]].
// 4. Return ? ObjRec.SetMutableBinding(N, V, S).
self.object_record self.object_record
.set_mutable_binding(name, value, strict, context) .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( fn get_binding_value(
&self, &self,
name: &str, name: &str,
strict: bool, strict: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
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 return self
.declarative_record .declarative_record
.get_binding_value(name, strict, context); .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) self.object_record.get_binding_value(name, strict, context)
} }
fn delete_binding(&self, name: &str) -> bool { /// `9.1.1.4.7 DeleteBinding ( N )`
if self.declarative_record.has_binding(name) { ///
return self.declarative_record.delete_binding(name); /// 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<bool> {
// 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; // 3. Let ObjRec be envRec.[[ObjectRecord]].
if global.has_field(name) { // 4. Let globalObject be ObjRec.[[BindingObject]].
let status = self.object_record.delete_binding(name); 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 { if status {
let mut var_names = self.var_names.borrow_mut(); // i. Let varNames be envRec.[[VarNames]].
if var_names.contains(name) { // ii. If N is an element of varNames, remove that element from the varNames.
var_names.remove(name); self.var_names.borrow_mut().remove(name);
return status;
}
} }
// 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 { fn has_this_binding(&self) -> bool {
// 1. Return true.
true true
} }
fn get_this_binding(&self, _context: &mut Context) -> JsResult<JsValue> { /// `9.1.1.4.9 HasSuperBinding ( )`
Ok(self.global_this_binding.clone().into()) ///
} /// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-hassuperbinding
fn has_super_binding(&self) -> bool { fn has_super_binding(&self) -> bool {
// 1. Return false.
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<JsObject> { fn with_base_object(&self) -> Option<JsObject> {
// 1. Return undefined.
None 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<JsValue> {
// 1. Return envRec.[[GlobalThisValue]].
Ok(self.global_this_binding.clone().into())
}
fn get_outer_environment(&self) -> Option<Environment> { fn get_outer_environment(&self) -> Option<Environment> {
None None
} }
@ -287,7 +523,7 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord {
fn recursive_create_mutable_binding( fn recursive_create_mutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
_scope: VariableScope, _scope: VariableScope,
context: &mut Context, context: &mut Context,
@ -297,7 +533,7 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord {
fn recursive_create_immutable_binding( fn recursive_create_immutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
_scope: VariableScope, _scope: VariableScope,
context: &mut Context, context: &mut Context,

9
boa/src/environment/lexical_environment.rs

@ -95,7 +95,7 @@ impl Context {
pub(crate) fn create_mutable_binding( pub(crate) fn create_mutable_binding(
&mut self, &mut self,
name: String, name: &str,
deletion: bool, deletion: bool,
scope: VariableScope, scope: VariableScope,
) -> JsResult<()> { ) -> JsResult<()> {
@ -105,7 +105,7 @@ impl Context {
pub(crate) fn create_immutable_binding( pub(crate) fn create_immutable_binding(
&mut self, &mut self,
name: String, name: &str,
deletion: bool, deletion: bool,
scope: VariableScope, scope: VariableScope,
) -> JsResult<()> { ) -> JsResult<()> {
@ -139,8 +139,9 @@ impl Context {
.clone() .clone()
} }
pub(crate) fn has_binding(&mut self, name: &str) -> bool { pub(crate) fn has_binding(&mut self, name: &str) -> JsResult<bool> {
self.get_current_environment().recursive_has_binding(name) self.get_current_environment()
.recursive_has_binding(name, self)
} }
pub(crate) fn get_binding_value(&mut self, name: &str) -> JsResult<JsValue> { pub(crate) fn get_binding_value(&mut self, name: &str) -> JsResult<JsValue> {

196
boa/src/environment/object_environment_record.rs

@ -16,19 +16,19 @@ use crate::{
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::JsObject, object::JsObject,
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
#[derive(Debug, Trace, Finalize, Clone)] #[derive(Debug, Trace, Finalize, Clone)]
pub struct ObjectEnvironmentRecord { pub struct ObjectEnvironmentRecord {
// TODO: bindings should be an object. pub bindings: JsObject,
pub bindings: JsValue,
pub with_environment: bool, pub with_environment: bool,
pub outer_env: Option<Environment>, pub outer_env: Option<Environment>,
} }
impl ObjectEnvironmentRecord { impl ObjectEnvironmentRecord {
pub fn new(object: JsValue, environment: Option<Environment>) -> ObjectEnvironmentRecord { pub fn new(object: JsObject, environment: Option<Environment>) -> ObjectEnvironmentRecord {
ObjectEnvironmentRecord { ObjectEnvironmentRecord {
bindings: object, bindings: object,
outer_env: environment, outer_env: environment,
@ -43,124 +43,208 @@ impl ObjectEnvironmentRecord {
} }
impl EnvironmentRecordTrait for ObjectEnvironmentRecord { impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
fn has_binding(&self, name: &str) -> bool { /// `9.1.1.2.1 HasBinding ( N )`
if self.bindings.has_field(name) { ///
if self.with_environment { /// More information:
// TODO: implement unscopables /// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n
fn has_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
// 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( fn create_mutable_binding(
&self, &self,
name: String, name: &str,
deletion: bool, deletion: bool,
_allow_name_reuse: bool, _allow_name_reuse: bool,
_context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
// TODO: could save time here and not bother generating a new undefined object, // 1. Let bindingObject be envRec.[[BindingObject]].
// only for it to be replace with the real value later. We could just add the name to a Vector instead // 2. Return ? DefinePropertyOrThrow(bindingObject, N, PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }).
let bindings = &self.bindings; self.bindings.define_property_or_throw(
let prop = PropertyDescriptor::builder() name,
PropertyDescriptor::builder()
.value(JsValue::undefined()) .value(JsValue::undefined())
.writable(true) .writable(true)
.enumerable(true) .enumerable(true)
.configurable(deletion); .configurable(deletion),
context,
bindings.set_property(name, prop); )?;
Ok(()) 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( fn create_immutable_binding(
&self, &self,
_name: String, _name: &str,
_strict: bool, _strict: bool,
_context: &mut Context, _context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
Ok(()) 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( fn initialize_binding(
&self, &self,
name: &str, name: &str,
value: JsValue, value: JsValue,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
// We should never need to check if a binding has been created, // 1. Return ? envRec.SetMutableBinding(N, V, false).
// As all calls to create_mutable_binding are followed by initialized binding
// The below is just a check.
debug_assert!(self.has_binding(name));
self.set_mutable_binding(name, value, false, context) 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( fn set_mutable_binding(
&self, &self,
name: &str, name: &str,
value: JsValue, value: JsValue,
strict: bool, strict: bool,
_context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
debug_assert!(value.is_object() || value.is_function()); // 1. Let bindingObject be envRec.[[BindingObject]].
let property = PropertyDescriptor::builder() // 2. Let stillExists be ? HasProperty(bindingObject, N).
.value(value) let still_exists = self.bindings.has_property(name, context)?;
.enumerable(true)
.configurable(strict); // 3. If stillExists is false and S is true, throw a ReferenceError exception.
self.bindings if !still_exists && strict {
.as_object() return Err(context.construct_reference_error("Binding already exists"));
.expect("binding object") }
.insert(name, property);
// 4. Return ? Set(bindingObject, N, V, S).
self.bindings.set(name, value, strict, context)?;
Ok(()) 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( fn get_binding_value(
&self, &self,
name: &str, name: &str,
strict: bool, strict: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
if self.bindings.has_field(name) { // 1. Let bindingObject be envRec.[[BindingObject]].
Ok(self // 2. Let value be ? HasProperty(bindingObject, N).
.bindings // 3. If value is false, then
.get_property(name) if !self.bindings.__has_property__(&name.into(), context)? {
.as_ref() // a. If S is false, return the value undefined; otherwise throw a ReferenceError exception.
.and_then(|prop| prop.value()) if !strict {
.cloned() return Ok(JsValue::undefined());
.unwrap_or_default())
} else if strict {
context.throw_reference_error(format!("{} has no binding", name))
} else { } else {
Ok(JsValue::undefined()) return context.throw_reference_error(format!("{} has no binding", name));
} }
} }
fn delete_binding(&self, name: &str) -> bool { // 4. Return ? Get(bindingObject, N).
self.bindings.remove_property(name); self.bindings.get(name, context)
true
} }
fn has_this_binding(&self) -> bool { /// `9.1.1.2.7 DeleteBinding ( N )`
false ///
/// 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<bool> {
// 1. Let bindingObject be envRec.[[BindingObject]].
// 2. Return ? bindingObject.[[Delete]](N).
self.bindings.__delete__(&name.into(), context)
} }
fn get_this_binding(&self, _context: &mut Context) -> JsResult<JsValue> { /// `9.1.1.2.8 HasThisBinding ( )`
Ok(JsValue::undefined()) ///
/// 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
} }
/// `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 { fn has_super_binding(&self) -> bool {
// 1. Return false.
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<JsObject> { fn with_base_object(&self) -> Option<JsObject> {
// Object Environment Records return undefined as their // 1. If envRec.[[IsWithEnvironment]] is true, return envRec.[[BindingObject]].
// WithBaseObject unless their withEnvironment flag is true. // 2. Otherwise, return undefined.
if self.with_environment { 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<JsValue> {
Ok(JsValue::undefined())
} }
fn get_outer_environment_ref(&self) -> Option<&Environment> { fn get_outer_environment_ref(&self) -> Option<&Environment> {

13
boa/src/object/gcobject.rs

@ -199,7 +199,8 @@ impl JsObject {
BindingStatus::Uninitialized BindingStatus::Uninitialized
}, },
JsValue::undefined(), JsValue::undefined(),
); context,
)?;
let mut arguments_in_parameter_names = false; let mut arguments_in_parameter_names = false;
@ -224,12 +225,7 @@ impl JsObject {
{ {
// Add arguments object // Add arguments object
let arguments_obj = create_unmapped_arguments_object(args, context)?; let arguments_obj = create_unmapped_arguments_object(args, context)?;
local_env.create_mutable_binding( local_env.create_mutable_binding("arguments", false, true, context)?;
"arguments".to_string(),
false,
true,
context,
)?;
local_env.initialize_binding("arguments", arguments_obj, context)?; local_env.initialize_binding("arguments", arguments_obj, context)?;
} }
@ -280,7 +276,8 @@ impl JsObject {
BindingStatus::Uninitialized BindingStatus::Uninitialized
}, },
JsValue::undefined(), JsValue::undefined(),
); context,
)?;
context.push_environment(second_env); context.push_environment(second_env);
} }

10
boa/src/syntax/ast/node/declaration/function_decl/mod.rs

@ -95,14 +95,10 @@ impl Executable for FunctionDecl {
FunctionFlags::CONSTRUCTABLE, FunctionFlags::CONSTRUCTABLE,
)?; )?;
if context.has_binding(self.name()) { if context.has_binding(self.name())? {
context.set_mutable_binding(self.name(), val, true)?; context.set_mutable_binding(self.name(), val, context.strict())?;
} else { } else {
context.create_mutable_binding( context.create_mutable_binding(self.name(), false, VariableScope::Function)?;
self.name().to_owned(),
false,
VariableScope::Function,
)?;
context.initialize_binding(self.name(), val)?; context.initialize_binding(self.name(), val)?;
} }

24
boa/src/syntax/ast/node/declaration/mod.rs

@ -104,26 +104,26 @@ impl Executable for DeclarationList {
match &decl { match &decl {
Declaration::Identifier { ident, init } => { 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() { if init.is_some() {
context.set_mutable_binding(ident.as_ref(), val, true)?; context.set_mutable_binding(ident.as_ref(), val, context.strict())?;
} }
continue; continue;
} }
match &self { match &self {
Const(_) => context.create_immutable_binding( Const(_) => context.create_immutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?, )?,
Let(_) => context.create_mutable_binding( Let(_) => context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?, )?,
Var(_) => context.create_mutable_binding( Var(_) => context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Function, VariableScope::Function,
)?, )?,
@ -133,26 +133,30 @@ impl Executable for DeclarationList {
} }
Declaration::Pattern(p) => { Declaration::Pattern(p) => {
for (ident, value) in p.run(None, context)? { 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() { if !value.is_undefined() {
context.set_mutable_binding(ident.as_ref(), value, true)?; context.set_mutable_binding(
ident.as_ref(),
value,
context.strict(),
)?;
} }
continue; continue;
} }
match &self { match &self {
Const(_) => context.create_immutable_binding( Const(_) => context.create_immutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?, )?,
Let(_) => context.create_mutable_binding( Let(_) => context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?, )?,
Var(_) => context.create_mutable_binding( Var(_) => context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Function, VariableScope::Function,
)?, )?,

34
boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs

@ -112,12 +112,16 @@ impl Executable for ForInLoop {
match self.variable() { match self.variable() {
Node::Identifier(ref name) => { Node::Identifier(ref name) => {
if context.has_binding(name.as_ref()) { if context.has_binding(name.as_ref())? {
// Binding already exists // 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 { } else {
context.create_mutable_binding( context.create_mutable_binding(
name.as_ref().to_owned(), name.as_ref(),
true, true,
VariableScope::Function, VariableScope::Function,
)?; )?;
@ -132,15 +136,15 @@ impl Executable for ForInLoop {
match &var { match &var {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
if context.has_binding(ident.as_ref()) { if context.has_binding(ident.as_ref())? {
context.set_mutable_binding( context.set_mutable_binding(
ident.as_ref(), ident.as_ref(),
next_result, next_result,
true, context.strict(),
)?; )?;
} else { } else {
context.create_mutable_binding( context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Function, VariableScope::Function,
)?; )?;
@ -149,11 +153,15 @@ impl Executable for ForInLoop {
} }
Declaration::Pattern(p) => { Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? { for (ident, value) in p.run(Some(next_result), context)? {
if context.has_binding(ident.as_ref()) { if context.has_binding(ident.as_ref())? {
context.set_mutable_binding(ident.as_ref(), value, true)?; context.set_mutable_binding(
ident.as_ref(),
value,
context.strict(),
)?;
} else { } else {
context.create_mutable_binding( context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Function, VariableScope::Function,
)?; )?;
@ -178,7 +186,7 @@ impl Executable for ForInLoop {
match &var { match &var {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
context.create_mutable_binding( context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?; )?;
@ -187,7 +195,7 @@ impl Executable for ForInLoop {
Declaration::Pattern(p) => { Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? { for (ident, value) in p.run(Some(next_result), context)? {
context.create_mutable_binding( context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?; )?;
@ -211,7 +219,7 @@ impl Executable for ForInLoop {
match &var { match &var {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
context.create_immutable_binding( context.create_immutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?; )?;
@ -220,7 +228,7 @@ impl Executable for ForInLoop {
Declaration::Pattern(p) => { Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? { for (ident, value) in p.run(Some(next_result), context)? {
context.create_immutable_binding( context.create_immutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?; )?;

34
boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs

@ -100,12 +100,16 @@ impl Executable for ForOfLoop {
match self.variable() { match self.variable() {
Node::Identifier(ref name) => { Node::Identifier(ref name) => {
if context.has_binding(name.as_ref()) { if context.has_binding(name.as_ref())? {
// Binding already exists // 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 { } else {
context.create_mutable_binding( context.create_mutable_binding(
name.as_ref().to_owned(), name.as_ref(),
true, true,
VariableScope::Function, VariableScope::Function,
)?; )?;
@ -120,15 +124,15 @@ impl Executable for ForOfLoop {
match &var { match &var {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
if context.has_binding(ident.as_ref()) { if context.has_binding(ident.as_ref())? {
context.set_mutable_binding( context.set_mutable_binding(
ident.as_ref(), ident.as_ref(),
next_result, next_result,
true, context.strict(),
)?; )?;
} else { } else {
context.create_mutable_binding( context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Function, VariableScope::Function,
)?; )?;
@ -137,11 +141,15 @@ impl Executable for ForOfLoop {
} }
Declaration::Pattern(p) => { Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? { for (ident, value) in p.run(Some(next_result), context)? {
if context.has_binding(ident.as_ref()) { if context.has_binding(ident.as_ref())? {
context.set_mutable_binding(ident.as_ref(), value, true)?; context.set_mutable_binding(
ident.as_ref(),
value,
context.strict(),
)?;
} else { } else {
context.create_mutable_binding( context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Function, VariableScope::Function,
)?; )?;
@ -166,7 +174,7 @@ impl Executable for ForOfLoop {
match &var { match &var {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
context.create_mutable_binding( context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?; )?;
@ -175,7 +183,7 @@ impl Executable for ForOfLoop {
Declaration::Pattern(p) => { Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? { for (ident, value) in p.run(Some(next_result), context)? {
context.create_mutable_binding( context.create_mutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?; )?;
@ -199,7 +207,7 @@ impl Executable for ForOfLoop {
match &var { match &var {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
context.create_immutable_binding( context.create_immutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?; )?;
@ -208,7 +216,7 @@ impl Executable for ForOfLoop {
Declaration::Pattern(p) => { Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? { for (ident, value) in p.run(Some(next_result), context)? {
context.create_immutable_binding( context.create_immutable_binding(
ident.to_string(), ident.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?; )?;

10
boa/src/syntax/ast/node/operator/assign/mod.rs

@ -58,15 +58,11 @@ impl Executable for Assign {
let val = self.rhs().run(context)?; let val = self.rhs().run(context)?;
match self.lhs() { match self.lhs() {
Node::Identifier(ref name) => { Node::Identifier(ref name) => {
if context.has_binding(name.as_ref()) { if context.has_binding(name.as_ref())? {
// Binding already exists // 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 { } else {
context.create_mutable_binding( context.create_mutable_binding(name.as_ref(), true, VariableScope::Function)?;
name.as_ref().to_owned(),
true,
VariableScope::Function,
)?;
context.initialize_binding(name.as_ref(), val.clone())?; context.initialize_binding(name.as_ref(), val.clone())?;
} }
} }

2
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 v_a = context.get_binding_value(name.as_ref())?;
let value = Self::run_assign(op, v_a, self.rhs(), context)?; 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) Ok(value)
} }
Node::GetConstField(ref get_const_field) => { Node::GetConstField(ref get_const_field) => {

6
boa/src/syntax/ast/node/try_node/mod.rs

@ -105,11 +105,7 @@ impl Executable for Try {
context.push_environment(DeclarativeEnvironmentRecord::new(Some(env))); context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
if let Some(param) = catch.parameter() { if let Some(param) = catch.parameter() {
context.create_mutable_binding( context.create_mutable_binding(param, false, VariableScope::Block)?;
param.to_owned(),
false,
VariableScope::Block,
)?;
context.initialize_binding(param, err)?; context.initialize_binding(param, err)?;
} }
} }

26
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.
///
/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal> would turn `extensible` to `false`
/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze> would also turn `extensible` to `false`
pub(crate) fn is_extensible(&self) -> bool {
true
}
/// Returns true if the value is an object /// Returns true if the value is an object
#[inline] #[inline]
pub fn is_object(&self) -> bool { 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<K>(&self, key: K) -> bool
where
K: Into<PropertyKey>,
{
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 /// Set the field in the value
/// ///
/// Similar to `7.3.4 Set ( O, P, V, Throw )`, but returns the value instead of a boolean. /// Similar to `7.3.4 Set ( O, P, V, Throw )`, but returns the value instead of a boolean.

28
boa/src/vm/mod.rs

@ -259,28 +259,22 @@ impl<'a> Vm<'a> {
let index = self.read::<u32>(); let index = self.read::<u32>();
let name = &self.code.names[index as usize]; let name = &self.code.names[index as usize];
self.context.create_mutable_binding( self.context
name.to_string(), .create_mutable_binding(name, false, VariableScope::Function)?;
false,
VariableScope::Function,
)?;
} }
Opcode::DefLet => { Opcode::DefLet => {
let index = self.read::<u32>(); let index = self.read::<u32>();
let name = &self.code.names[index as usize]; let name = &self.code.names[index as usize];
self.context.create_mutable_binding( self.context
name.to_string(), .create_mutable_binding(name, false, VariableScope::Block)?;
false,
VariableScope::Block,
)?;
} }
Opcode::DefConst => { Opcode::DefConst => {
let index = self.read::<u32>(); let index = self.read::<u32>();
let name = &self.code.names[index as usize]; let name = &self.code.names[index as usize];
self.context.create_immutable_binding( self.context.create_immutable_binding(
name.to_string(), name.as_ref(),
false, false,
VariableScope::Block, VariableScope::Block,
)?; )?;
@ -304,15 +298,13 @@ impl<'a> Vm<'a> {
let value = self.pop(); let value = self.pop();
let name = &self.code.names[index as usize]; let name = &self.code.names[index as usize];
if self.context.has_binding(name) { if self.context.has_binding(name)? {
// Binding already exists // Binding already exists
self.context.set_mutable_binding(name, value, true)?; self.context
.set_mutable_binding(name, value, self.context.strict())?;
} else { } else {
self.context.create_mutable_binding( self.context
name.to_string(), .create_mutable_binding(name, true, VariableScope::Function)?;
true,
VariableScope::Function,
)?;
self.context.initialize_binding(name, value)?; self.context.initialize_binding(name, value)?;
} }
} }

Loading…
Cancel
Save