diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 4cf03d37901..d7dcca86e08 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -16,7 +16,7 @@ use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ error::RangeError, - object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{same_value_zero, ResultValue, Value, ValueData}, }, @@ -42,7 +42,7 @@ impl Array { .get_global_object() .expect("Could not get global object"), )); - array.set_kind(ObjectKind::Array); + array.set_data(ObjectData::Array); array.borrow().set_internal_slot( INSTANCE_PROTOTYPE, interpreter @@ -117,7 +117,7 @@ impl Array { this.set_internal_slot(INSTANCE_PROTOTYPE, prototype); // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Array); + this.set_data(ObjectData::Array); // add our arguments in let mut length = args.len() as i32; @@ -176,7 +176,7 @@ impl Array { // 1. ValueData::Object(ref obj) => { // 2. - if (*obj).deref().borrow().kind == ObjectKind::Array { + if let ObjectData::Array = (*obj).deref().borrow().data { return Ok(value_true); } Ok(value_false) @@ -1008,7 +1008,7 @@ impl Array { let prototype = Value::new_object(None); let length = Property::default().value(Value::from(0)); - prototype.set_property_slice("length", length); + prototype.set_property("length", length); make_builtin_fn(Self::concat, "concat", &prototype, 1); make_builtin_fn(Self::push, "push", &prototype, 1); diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index f01b4573c6a..b2206b8fb7f 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -40,15 +40,11 @@ impl BigInt { /// /// [spec]: https://tc39.es/ecma262/#sec-bigint-objects /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt - pub(crate) fn make_bigint( - _this: &mut Value, - args: &[Value], - ctx: &mut Interpreter, - ) -> ResultValue { + pub(crate) fn make_bigint(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let data = match args.get(0) { Some(ref value) => { if let Some(bigint) = value.to_bigint() { - Value::from(bigint) + bigint } else { return Err(RangeError::run_new( format!( @@ -59,9 +55,9 @@ impl BigInt { )?); } } - None => Value::from(AstBigInt::from(0)), + None => AstBigInt::from(0), }; - Ok(data) + Ok(Value::from(data)) } /// `BigInt.prototype.toString( [radix] )` @@ -119,7 +115,6 @@ impl BigInt { /// Create a new `Number` object pub(crate) fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("BigIntData", Value::from(AstBigInt::from(0))); make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 40b7de056dd..f8895ae2816 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -15,7 +15,7 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{internal_methods_trait::ObjectInternalMethods, ObjectKind}, + object::ObjectData, value::{ResultValue, Value, ValueData}, }, exec::Interpreter, @@ -35,19 +35,11 @@ impl Boolean { args: &[Value], _: &mut Interpreter, ) -> ResultValue { - this.set_kind(ObjectKind::Boolean); - // Get the argument, if any - if let Some(ref value) = args.get(0) { - this.set_internal_slot("BooleanData", Self::to_boolean(value)); - } else { - this.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false))); - } + let data = args.get(0).map(|x| x.to_boolean()).unwrap_or(false); + this.set_data(ObjectData::Boolean(data)); - match args.get(0) { - Some(ref value) => Ok(Self::to_boolean(value)), - None => Ok(Self::to_boolean(&Value::from(false))), - } + Ok(Value::from(data)) } /// The `toString()` method returns a string representing the specified `Boolean` object. @@ -73,22 +65,7 @@ impl Boolean { /// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf pub(crate) fn value_of(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Self::this_boolean_value(this)) - } - - // === Utility Functions === - /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) - /// Creates a new boolean value from the input - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_boolean(value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Object(_) => Value::from(true), - ValueData::String(ref s) if !s.is_empty() => Value::from(true), - ValueData::Rational(n) if n != 0.0 && !n.is_nan() => Value::from(true), - ValueData::Integer(n) if n != 0 => Value::from(true), - ValueData::Boolean(v) => Value::from(v), - _ => Value::from(false), - } + Ok(Value::from(Self::this_boolean_value(this))) } /// An Utility function used to get the internal BooleanData. @@ -97,11 +74,14 @@ impl Boolean { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue - pub(crate) fn this_boolean_value(value: &Value) -> Value { + pub(crate) fn this_boolean_value(value: &Value) -> bool { match *value.deref().borrow() { - ValueData::Boolean(v) => Value::from(v), - ValueData::Object(ref v) => (v).deref().borrow().get_internal_slot("BooleanData"), - _ => Value::from(false), + ValueData::Boolean(v) => v, + ValueData::Object(ref v) => match v.deref().borrow().data { + ObjectData::Boolean(boolean) => boolean, + _ => unreachable!(), + }, + _ => false, } } @@ -110,7 +90,6 @@ impl Boolean { // Create Prototype // https://tc39.es/ecma262/#sec-properties-of-the-boolean-prototype-object let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false))); make_builtin_fn(Self::to_string, "toString", &prototype, 0); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index 328edc50bfb..bea435ea65b 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -13,7 +13,7 @@ use crate::{ builtins::{ function::{make_builtin_fn, make_constructor_fn}, - object::ObjectKind, + object::ObjectData, value::{ResultValue, Value}, }, exec::Interpreter, @@ -47,7 +47,7 @@ impl Error { } // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Error); + this.set_data(ObjectData::Error); Ok(Value::undefined()) } diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 34e4c88ff77..1fc3789219c 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -13,7 +13,7 @@ use crate::{ builtins::{ function::make_builtin_fn, function::make_constructor_fn, - object::ObjectKind, + object::ObjectData, value::{ResultValue, Value}, }, exec::Interpreter, @@ -38,7 +38,7 @@ impl RangeError { } // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Error); + this.set_data(ObjectData::Error); Ok(Value::undefined()) } diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 2886b97051f..5e9a9eb9019 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -14,7 +14,7 @@ use crate::{ builtins::{ array::Array, - object::{Object, ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{ResultValue, Value}, }, @@ -39,7 +39,7 @@ pub enum ConstructorKind { /// Defines how this references are interpreted within the formal parameters and code body of the function. /// /// Arrow functions don't define a `this` and thus are lexical, `function`s do define a this and thus are NonLexical -#[derive(Trace, Finalize, Debug, Clone)] +#[derive(Trace, Finalize, Debug, Clone, PartialEq, PartialOrd, Hash)] pub enum ThisMode { Lexical, NonLexical, @@ -55,12 +55,24 @@ pub enum FunctionBody { impl Debug for FunctionBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::BuiltIn(_) => write!(f, "native code"), + Self::BuiltIn(_) => write!(f, "[native]"), Self::Ordinary(statements) => write!(f, "{:?}", statements), } } } +impl PartialEq for FunctionBody { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::BuiltIn(a), Self::BuiltIn(b)) => std::ptr::eq(a, b), + (Self::Ordinary(a), Self::Ordinary(b)) => a == b, + (_, _) => false, + } + } +} + +impl Eq for FunctionBody {} + /// `Trace` implementation for `FunctionBody`. /// /// This is indeed safe, but we need to mark this as an empty trace because neither @@ -158,19 +170,19 @@ impl Function { /// pub fn call( &self, - this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + this: &mut Value, args_list: &[Value], interpreter: &mut Interpreter, - this_obj: &mut Value, ) -> ResultValue { if self.callable { match self.body { - FunctionBody::BuiltIn(func) => func(this_obj, args_list, interpreter), + FunctionBody::BuiltIn(func) => func(this, args_list, interpreter), FunctionBody::Ordinary(ref body) => { // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) // let local_env = new_function_environment( - this.clone(), + function, None, Some(self.environment.as_ref().unwrap().clone()), BindingStatus::Uninitialized, @@ -216,23 +228,23 @@ impl Function { /// pub fn construct( &self, - this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + this: &mut Value, args_list: &[Value], interpreter: &mut Interpreter, - this_obj: &mut Value, ) -> ResultValue { if self.constructable { match self.body { FunctionBody::BuiltIn(func) => { - func(this_obj, args_list, interpreter).unwrap(); - Ok(this_obj.clone()) + func(this, args_list, interpreter)?; + Ok(this.clone()) } FunctionBody::Ordinary(ref body) => { // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) // let local_env = new_function_environment( - this.clone(), - Some(this_obj.clone()), + function, + Some(this.clone()), Some(self.environment.as_ref().unwrap().clone()), BindingStatus::Initialized, ); @@ -357,7 +369,7 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { .writable(true) .configurable(true); - obj.properties.insert(index.to_string(), prop); + obj.properties_mut().insert(index.to_string(), prop); index += 1; } @@ -368,7 +380,10 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { /// // This gets called when a new Function() is created. pub fn make_function(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - this.set_kind(ObjectKind::Function); + this.set_data(ObjectData::Function(Function::builtin( + Vec::new(), + |_, _, _| Ok(Value::undefined()), + ))); Ok(this.clone()) } @@ -399,8 +414,7 @@ pub fn make_constructor_fn( let func_prototype = global.get_field("Function").get_field(PROTOTYPE); // Create the function object and point its instance prototype to Function.prototype - let mut constructor_obj = Object::function(); - constructor_obj.set_func(constructor_fn); + let mut constructor_obj = Object::function(constructor_fn); constructor_obj.set_internal_slot(INSTANCE_PROTOTYPE, func_prototype); let constructor_val = Value::from(constructor_obj); @@ -414,14 +428,14 @@ pub fn make_constructor_fn( .writable(false) .configurable(false) .enumerable(false); - constructor_val.set_property_slice("length", length); + constructor_val.set_property("length", length); let name = Property::new() .value(Value::from(name)) .writable(false) .configurable(false) .enumerable(false); - constructor_val.set_property_slice("name", name); + constructor_val.set_property("name", name); constructor_val } @@ -433,12 +447,9 @@ pub fn make_builtin_fn(function: NativeFunctionData, name: N, parent: &Value, where N: Into, { - let func = Function::builtin(Vec::new(), function); - - let mut new_func = Object::function(); - new_func.set_func(func); + let func = Object::function(Function::builtin(Vec::new(), function)); - let new_func_obj = Value::from(new_func); + let new_func_obj = Value::from(func); new_func_obj.set_field("length", length); parent.set_field(name.into(), new_func_obj); diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index ad7cd378fd0..cd39283e593 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -15,7 +15,6 @@ use crate::builtins::{ function::make_builtin_fn, - object::ObjectKind, property::Property, value::{ResultValue, Value}, }; @@ -87,7 +86,7 @@ pub fn stringify(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) - .map(|obj| { let object_to_return = Value::new_object(None); for (key, val) in obj - .properties + .properties() .iter() .filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value))) { @@ -104,10 +103,10 @@ pub fn stringify(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) - Ok(Value::from(object_to_return.to_json().to_string())) }) .ok_or_else(Value::undefined)? - } else if replacer_as_object.kind == ObjectKind::Array { + } else if replacer_as_object.is_array() { let mut obj_to_return = - serde_json::Map::with_capacity(replacer_as_object.properties.len() - 1); - let fields = replacer_as_object.properties.keys().filter_map(|key| { + serde_json::Map::with_capacity(replacer_as_object.properties().len() - 1); + let fields = replacer_as_object.properties().keys().filter_map(|key| { if key == "length" { None } else { diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 05cde1dcf13..06e9bddfd83 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -18,18 +18,17 @@ mod tests; use super::{ function::{make_builtin_fn, make_constructor_fn}, - object::ObjectKind, + object::ObjectData, }; use crate::{ builtins::{ - object::internal_methods_trait::ObjectInternalMethods, value::{ResultValue, Value, ValueData}, RangeError, }, exec::Interpreter, }; use num_traits::float::FloatCore; -use std::{borrow::Borrow, f64, ops::Deref}; +use std::{f64, ops::Deref}; const BUF_SIZE: usize = 2200; @@ -41,19 +40,22 @@ impl Number { /// Helper function that converts a Value to a Number. #[allow(clippy::wrong_self_convention)] fn to_number(value: &Value) -> Value { - match *value.deref().borrow() { + match value.data() { ValueData::Boolean(b) => { - if b { + if *b { Value::from(1) } else { Value::from(0) } } ValueData::Symbol(_) | ValueData::Undefined => Value::from(f64::NAN), - ValueData::Integer(i) => Value::from(f64::from(i)), - ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"), + ValueData::Integer(i) => Value::from(f64::from(*i)), + ValueData::Object(ref o) => match (o).deref().borrow().data { + ObjectData::Number(num) => Value::from(num), + _ => unreachable!(), + }, ValueData::Null => Value::from(0), - ValueData::Rational(n) => Value::from(n), + ValueData::Rational(n) => Value::from(*n), ValueData::BigInt(ref bigint) => Value::from(bigint.to_f64()), ValueData::String(ref s) => match s.parse::() { Ok(n) => Value::from(n), @@ -80,13 +82,12 @@ impl Number { _ctx: &mut Interpreter, ) -> ResultValue { let data = match args.get(0) { - Some(ref value) => Self::to_number(value), - None => Self::to_number(&Value::from(0)), + Some(ref value) => Self::to_number(value).to_number(), + None => 0.0, }; - this.set_kind(ObjectKind::Number); - this.set_internal_slot("NumberData", data.clone()); + this.set_data(ObjectData::Number(data)); - Ok(data) + Ok(Value::from(data)) } /// `Number.prototype.toExponential( [fractionDigits] )` @@ -393,7 +394,6 @@ impl Number { /// Create a new `Number` object pub(crate) fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("NumberData", Value::from(0)); make_builtin_fn(Self::to_exponential, "toExponential", &prototype, 1); make_builtin_fn(Self::to_fixed, "toFixed", &prototype, 1); diff --git a/boa/src/builtins/object/internal_methods_trait.rs b/boa/src/builtins/object/internal_methods.rs similarity index 57% rename from boa/src/builtins/object/internal_methods_trait.rs rename to boa/src/builtins/object/internal_methods.rs index d99531bed85..b38708b4973 100644 --- a/boa/src/builtins/object/internal_methods_trait.rs +++ b/boa/src/builtins/object/internal_methods.rs @@ -1,4 +1,4 @@ -//! This module defines the `ObjectInternalMethods` trait. +//! This module defines the object internal methods. //! //! More information: //! - [ECMAScript reference][spec] @@ -6,30 +6,21 @@ //! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots use crate::builtins::{ - object::{Object, INSTANCE_PROTOTYPE}, + object::{Object, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{same_value, Value, ValueData}, }; use std::borrow::Borrow; use std::ops::Deref; -/// Here lies the internal methods for ordinary objects. -/// -/// Most objects make use of these methods, including exotic objects like functions. -/// So thats why this is a trait -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots -pub trait ObjectInternalMethods { - /// Check if has property. +impl Object { + /// Check if object has property. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p - fn has_property(&self, val: &Value) -> bool { + pub fn has_property(&self, val: &Value) -> bool { debug_assert!(Property::is_property_key(val)); let prop = self.get_own_property(val); if prop.value.is_none() { @@ -54,7 +45,8 @@ pub trait ObjectInternalMethods { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible - fn is_extensible(&self) -> bool { + #[inline] + pub fn is_extensible(&self) -> bool { let val = self.get_internal_slot("extensible"); match *val.deref().borrow() { ValueData::Boolean(b) => b, @@ -68,13 +60,14 @@ pub trait ObjectInternalMethods { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions - fn prevent_extensions(&mut self) -> bool { + #[inline] + pub fn prevent_extensions(&mut self) -> bool { self.set_internal_slot("extensible", Value::from(false)); true } /// Delete property. - fn delete(&mut self, prop_key: &Value) -> bool { + pub fn delete(&mut self, prop_key: &Value) -> bool { debug_assert!(Property::is_property_key(prop_key)); let desc = self.get_own_property(prop_key); if desc @@ -94,7 +87,7 @@ pub trait ObjectInternalMethods { } // [[Get]] - fn get(&self, val: &Value) -> Value { + pub fn get(&self, val: &Value) -> Value { debug_assert!(Property::is_property_key(val)); let desc = self.get_own_property(val); if desc.value.clone().is_none() @@ -124,13 +117,13 @@ pub trait ObjectInternalMethods { return Value::undefined(); } - // TODO!!!!! Call getter from here + // TODO: Call getter from here! Value::undefined() } /// [[Set]] /// - fn set(&mut self, field: Value, val: Value) -> bool { + pub fn set(&mut self, field: Value, val: Value) -> bool { // [1] debug_assert!(Property::is_property_key(&field)); @@ -167,7 +160,13 @@ pub trait ObjectInternalMethods { } } - fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { + /// Define an own property. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc + pub fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { let mut current = self.get_own_property(&Value::from(property_key.to_string())); let extensible = self.is_extensible(); @@ -277,25 +276,142 @@ pub trait ObjectInternalMethods { true } - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p - /// The specification returns a Property Descriptor or Undefined. These are 2 separate types and we can't do that here. - fn get_own_property(&self, prop: &Value) -> Property; + /// The specification returns a Property Descriptor or Undefined. + /// + /// These are 2 separate types and we can't do that here. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p + pub fn get_own_property(&self, prop: &Value) -> Property { + debug_assert!(Property::is_property_key(prop)); + // Prop could either be a String or Symbol + match *(*prop) { + ValueData::String(ref st) => { + match self.properties.get(st) { + // If O does not have an own property with key P, return undefined. + // In this case we return a new empty Property + None => Property::default(), + Some(ref v) => { + let mut d = Property::default(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + d.writable = v.writable; + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); + } + d.enumerable = v.enumerable; + d.configurable = v.configurable; + d + } + } + } + ValueData::Symbol(ref sym) => { + let sym_id = (**sym) + .borrow() + .get_internal_slot("SymbolData") + .to_string() + .parse::() + .expect("Could not get Symbol ID"); + match self.symbol_properties().get(&sym_id) { + // If O does not have an own property with key P, return undefined. + // In this case we return a new empty Property + None => Property::default(), + Some(ref v) => { + let mut d = Property::default(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + d.writable = v.writable; + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); + } + d.enumerable = v.enumerable; + d.configurable = v.configurable; + d + } + } + } + _ => Property::default(), + } + } - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v - fn set_prototype_of(&mut self, val: Value) -> bool; + /// `Object.setPropertyOf(obj, prototype)` + /// + /// This method sets the prototype (i.e., the internal `[[Prototype]]` property) + /// of a specified object to another object or `null`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf + pub fn set_prototype_of(&mut self, val: Value) -> bool { + debug_assert!(val.is_object() || val.is_null()); + let current = self.get_internal_slot(PROTOTYPE); + if same_value(¤t, &val, false) { + return true; + } + let extensible = self.get_internal_slot("extensible"); + if extensible.is_null() { + return false; + } + let mut p = val.clone(); + let mut done = false; + while !done { + if p.is_null() { + done = true + } else if same_value(&Value::from(self.clone()), &p, false) { + return false; + } else { + p = p.get_internal_slot(PROTOTYPE); + } + } + self.set_internal_slot(PROTOTYPE, val); + true + } /// Returns either the prototype or null - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof - fn get_prototype_of(&self) -> Value { + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof + #[inline] + pub fn get_prototype_of(&self) -> Value { self.get_internal_slot(INSTANCE_PROTOTYPE) } - /// Utility function to get an immutable internal slot or Null - fn get_internal_slot(&self, name: &str) -> Value; + /// Helper function to get an immutable internal slot or `Null`. + #[inline] + pub fn get_internal_slot(&self, name: &str) -> Value { + match self.internal_slots.get(name) { + Some(v) => v.clone(), + None => Value::null(), + } + } - fn set_internal_slot(&mut self, name: &str, val: Value); + /// Helper function to set an internal slot. + #[inline] + pub fn set_internal_slot(&mut self, name: &str, val: Value) { + self.internal_slots.insert(name.to_string(), val); + } - fn insert_property(&mut self, name: String, p: Property); + /// Helper function for property insertion. + #[inline] + pub fn insert_property(&mut self, name: String, p: Property) { + self.properties.insert(name, p); + } - fn remove_property(&mut self, name: &str); + /// Helper function for property removal. + #[inline] + pub fn remove_property(&mut self, name: &str) { + self.properties.remove(name); + } } diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 912d15203cd..a5b5203aac6 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -17,23 +17,22 @@ use crate::{ builtins::{ function::Function, property::Property, - value::{same_value, ResultValue, Value, ValueData}, + value::{ResultValue, Value, ValueData}, }, exec::Interpreter, + syntax::ast::bigint::BigInt as AstBigInt, }; -use gc::{unsafe_empty_trace, Finalize, Trace}; +use gc::{Finalize, Trace}; use rustc_hash::FxHashMap; use std::{ - borrow::Borrow, - fmt::{self, Debug, Display, Error, Formatter}, + fmt::{Debug, Display, Error, Formatter}, ops::Deref, }; use super::function::{make_builtin_fn, make_constructor_fn}; -pub use internal_methods_trait::ObjectInternalMethods; pub use internal_state::{InternalState, InternalStateCell}; -pub mod internal_methods_trait; +pub mod internal_methods; mod internal_state; /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. @@ -43,319 +42,83 @@ pub static PROTOTYPE: &str = "prototype"; pub static INSTANCE_PROTOTYPE: &str = "__proto__"; /// The internal representation of an JavaScript object. -#[derive(Trace, Finalize, Clone)] +#[derive(Debug, Trace, Finalize, Clone)] pub struct Object { /// The type of the object. - pub kind: ObjectKind, + pub data: ObjectData, /// Internal Slots - pub internal_slots: FxHashMap, + internal_slots: FxHashMap, /// Properties - pub properties: FxHashMap, + properties: FxHashMap, /// Symbol Properties - pub sym_properties: FxHashMap, + symbol_properties: FxHashMap, /// Some rust object that stores internal state - pub state: Option, - /// Function - pub func: Option, + state: Option, } -impl Debug for Object { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln!(f, "{{")?; - writeln!(f, "\tkind: {}", self.kind)?; - writeln!(f, "\tstate: {:?}", self.state)?; - writeln!(f, "\tfunc: {:?}", self.func)?; - writeln!(f, "\tproperties: {{")?; - for (key, _) in self.properties.iter() { - writeln!(f, "\t\t{}", key)?; - } - writeln!(f, "\t }}")?; - write!(f, "}}") - } +/// Defines the different types of objects. +#[derive(Debug, Trace, Finalize, Clone)] +pub enum ObjectData { + Array, + BigInt(AstBigInt), + Boolean(bool), + Function(Function), + String(String), + Number(f64), + Symbol, + Error, + Ordinary, } -impl ObjectInternalMethods for Object { - /// `Object.setPropertyOf(obj, prototype)` - /// - /// This method sets the prototype (i.e., the internal `[[Prototype]]` property) - /// of a specified object to another object or `null`. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf - fn set_prototype_of(&mut self, val: Value) -> bool { - debug_assert!(val.is_object() || val.is_null()); - let current = self.get_internal_slot(PROTOTYPE); - if same_value(¤t, &val, false) { - return true; - } - let extensible = self.get_internal_slot("extensible"); - if extensible.is_null() { - return false; - } - let mut p = val.clone(); - let mut done = false; - while !done { - if p.is_null() { - done = true - } else if same_value(&Value::from(self.clone()), &p, false) { - return false; - } else { - p = p.get_internal_slot(PROTOTYPE); - } - } - self.set_internal_slot(PROTOTYPE, val); - true - } - - /// Helper function for property insertion. - fn insert_property(&mut self, name: String, p: Property) { - self.properties.insert(name, p); - } - - /// Helper function for property removal. - fn remove_property(&mut self, name: &str) { - self.properties.remove(name); - } - - /// Helper function to set an internal slot - fn set_internal_slot(&mut self, name: &str, val: Value) { - self.internal_slots.insert(name.to_string(), val); - } - - /// Helper function to get an immutable internal slot or Null - fn get_internal_slot(&self, name: &str) -> Value { - match self.internal_slots.get(name) { - Some(v) => v.clone(), - None => Value::null(), - } - } - - /// The specification returns a Property Descriptor or Undefined. - /// - /// These are 2 separate types and we can't do that here. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p - fn get_own_property(&self, prop: &Value) -> Property { - debug_assert!(Property::is_property_key(prop)); - // Prop could either be a String or Symbol - match *(*prop) { - ValueData::String(ref st) => { - match self.properties.get(st) { - // If O does not have an own property with key P, return undefined. - // In this case we return a new empty Property - None => Property::default(), - Some(ref v) => { - let mut d = Property::default(); - if v.is_data_descriptor() { - d.value = v.value.clone(); - d.writable = v.writable; - } else { - debug_assert!(v.is_accessor_descriptor()); - d.get = v.get.clone(); - d.set = v.set.clone(); - } - d.enumerable = v.enumerable; - d.configurable = v.configurable; - d - } - } - } - ValueData::Symbol(ref sym) => { - let sym_id = (**sym) - .borrow() - .get_internal_slot("SymbolData") - .to_string() - .parse::() - .expect("Could not get Symbol ID"); - match self.sym_properties.get(&sym_id) { - // If O does not have an own property with key P, return undefined. - // In this case we return a new empty Property - None => Property::default(), - Some(ref v) => { - let mut d = Property::default(); - if v.is_data_descriptor() { - d.value = v.value.clone(); - d.writable = v.writable; - } else { - debug_assert!(v.is_accessor_descriptor()); - d.get = v.get.clone(); - d.set = v.set.clone(); - } - d.enumerable = v.enumerable; - d.configurable = v.configurable; - d - } - } - } - _ => Property::default(), - } - } - - /// Define an own property. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc - #[allow(clippy::option_unwrap_used)] - fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { - let mut current = self.get_own_property(&Value::from(property_key.to_string())); - let extensible = self.is_extensible(); - - // https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor - // There currently isn't a property, lets create a new one - if current.value.is_none() || current.value.as_ref().expect("failed").is_undefined() { - if !extensible { - return false; - } - if desc.value.is_some() && desc.value.clone().unwrap().is_symbol() { - let sym_id = desc - .value - .clone() - .unwrap() - .to_string() - .parse::() - .expect("parsing failed"); - self.sym_properties.insert(sym_id, desc); - } else { - self.properties.insert(property_key, desc); - } - return true; - } - // If every field is absent we don't need to set anything - if desc.is_none() { - return true; - } - - // 4 - if !current.configurable.unwrap_or(false) { - if desc.configurable.is_some() && desc.configurable.unwrap() { - return false; - } - - if desc.enumerable.is_some() - && (desc.enumerable.as_ref().unwrap() != current.enumerable.as_ref().unwrap()) - { - return false; - } - } - - // 5 - if desc.is_generic_descriptor() { - // 6 - } else if current.is_data_descriptor() != desc.is_data_descriptor() { - // a - if !current.configurable.unwrap() { - return false; - } - // b - if current.is_data_descriptor() { - // Convert to accessor - current.value = None; - current.writable = None; - } else { - // c - // convert to data - current.get = None; - current.set = None; - } - - if current.value.is_some() && current.value.clone().unwrap().is_symbol() { - let sym_id = current - .value - .clone() - .unwrap() - .to_string() - .parse::() - .expect("parsing failed"); - self.sym_properties.insert(sym_id, current); - } else { - self.properties.insert(property_key.clone(), current); - } - // 7 - } else if current.is_data_descriptor() && desc.is_data_descriptor() { - // a - if !current.configurable.unwrap() && !current.writable.unwrap() { - if desc.writable.is_some() && desc.writable.unwrap() { - return false; - } - - if desc.value.is_some() - && !same_value( - &desc.value.clone().unwrap(), - ¤t.value.clone().unwrap(), - false, - ) - { - return false; - } - - return true; - } - // 8 - } else { - if !current.configurable.unwrap() { - if desc.set.is_some() - && !same_value( - &desc.set.clone().unwrap(), - ¤t.set.clone().unwrap(), - false, - ) - { - return false; - } - - if desc.get.is_some() - && !same_value( - &desc.get.clone().unwrap(), - ¤t.get.clone().unwrap(), - false, - ) - { - return false; - } +impl Display for ObjectData { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!( + f, + "{}", + match self { + Self::Function(_) => "Function", + Self::Array => "Array", + Self::String(_) => "String", + Self::Symbol => "Symbol", + Self::Error => "Error", + Self::Ordinary => "Ordinary", + Self::Boolean(_) => "Boolean", + Self::Number(_) => "Number", + Self::BigInt(_) => "BigInt", } - - return true; - } - // 9 - self.properties.insert(property_key, desc); - true + ) } } -impl Object { +impl Default for Object { /// Return a new ObjectData struct, with `kind` set to Ordinary - pub fn default() -> Self { + fn default() -> Self { let mut object = Self { - kind: ObjectKind::Ordinary, + data: ObjectData::Ordinary, internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, }; object.set_internal_slot("extensible", Value::from(true)); object } +} + +impl Object { + pub fn new() -> Self { + Default::default() + } /// Return a new ObjectData struct, with `kind` set to Ordinary - pub fn function() -> Self { + pub fn function(function: Function) -> Self { let mut object = Self { - kind: ObjectKind::Function, + data: ObjectData::Function(function), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, }; object.set_internal_slot("extensible", Value::from(true)); @@ -378,73 +141,48 @@ impl Object { obj } - /// Set the function this object wraps - pub fn set_func(&mut self, val: Function) { - self.func = Some(val); - } - /// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument. - fn from_boolean(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::Boolean, + pub fn boolean(value: bool) -> Self { + Self { + data: ObjectData::Boolean(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("BooleanData".to_string(), argument.clone()); - obj + } } /// Return a new `Number` object whose `[[NumberData]]` internal slot is set to argument. - fn from_number(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::Number, + pub fn number(value: f64) -> Self { + Self { + data: ObjectData::Number(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("NumberData".to_string(), argument.clone()); - obj + } } /// Return a new `String` object whose `[[StringData]]` internal slot is set to argument. - fn from_string(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::String, + pub fn string(value: String) -> Self { + Self { + data: ObjectData::String(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("StringData".to_string(), argument.clone()); - obj + } } /// Return a new `BigInt` object whose `[[BigIntData]]` internal slot is set to argument. - fn from_bigint(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::BigInt, + pub fn bigint(value: AstBigInt) -> Self { + Self { + data: ObjectData::BigInt(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("BigIntData".to_string(), argument.clone()); - obj + } } /// Converts the `Value` to an `Object` type. @@ -454,11 +192,12 @@ impl Object { /// /// [spec]: https://tc39.es/ecma262/#sec-toobject pub fn from(value: &Value) -> Result { - match *value.deref().borrow() { - ValueData::Boolean(_) => Ok(Self::from_boolean(value)), - ValueData::Rational(_) => Ok(Self::from_number(value)), - ValueData::String(_) => Ok(Self::from_string(value)), - ValueData::BigInt(_) => Ok(Self::from_bigint(value)), + match *value.data() { + ValueData::Boolean(a) => Ok(Self::boolean(a)), + ValueData::Rational(a) => Ok(Self::number(a)), + ValueData::Integer(a) => Ok(Self::number(f64::from(a))), + ValueData::String(ref a) => Ok(Self::string(a.clone())), + ValueData::BigInt(ref bigint) => Ok(Self::bigint(bigint.clone())), ValueData::Object(ref obj) => Ok((*obj).deref().borrow().clone()), _ => Err(()), } @@ -470,10 +209,11 @@ impl Object { /// - [EcmaScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-iscallable + #[inline] pub fn is_callable(&self) -> bool { - match self.func { - Some(ref function) => function.is_callable(), - None => false, + match self.data { + ObjectData::Function(ref function) => function.is_callable(), + _ => false, } } @@ -483,58 +223,198 @@ impl Object { /// - [EcmaScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-isconstructor + #[inline] pub fn is_constructable(&self) -> bool { - match self.func { - Some(ref function) => function.is_constructable(), - None => false, + match self.data { + ObjectData::Function(ref function) => function.is_constructable(), + _ => false, } } -} -/// Defines the different types of objects. -#[derive(Finalize, Debug, Copy, Clone, Eq, PartialEq)] -pub enum ObjectKind { - Function, - Array, - String, - Symbol, - Error, - Ordinary, - Boolean, - Number, - BigInt, -} + /// Checks if it an `Array` object. + #[inline] + pub fn is_array(&self) -> bool { + match self.data { + ObjectData::Array => true, + _ => false, + } + } -impl Display for ObjectKind { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - write!( - f, - "{}", - match self { - Self::Function => "Function", - Self::Array => "Array", - Self::String => "String", - Self::Symbol => "Symbol", - Self::Error => "Error", - Self::Ordinary => "Ordinary", - Self::Boolean => "Boolean", - Self::Number => "Number", - Self::BigInt => "BigInt", - } - ) + #[inline] + pub fn as_array(&self) -> Option<()> { + match self.data { + ObjectData::Array => Some(()), + _ => None, + } } -} -/// `Trace` implementation for `ObjectKind`. -/// -/// This is indeed safe, but we need to mark this as an empty trace because neither -// `NativeFunctionData` nor Node hold any GC'd objects, but Gc doesn't know that. So we need to -/// signal it manually. `rust-gc` does not have a `Trace` implementation for `fn(_, _, _)`. -/// -/// -/// Waiting on until we can derive Copy -unsafe impl Trace for ObjectKind { - unsafe_empty_trace!(); + /// Checks if it a `String` object. + #[inline] + pub fn is_string(&self) -> bool { + match self.data { + ObjectData::String(_) => true, + _ => false, + } + } + + #[inline] + pub fn as_string(&self) -> Option<&String> { + match self.data { + ObjectData::String(ref string) => Some(string), + _ => None, + } + } + + /// Checks if it a `Function` object. + #[inline] + pub fn is_function(&self) -> bool { + match self.data { + ObjectData::Function(_) => true, + _ => false, + } + } + + #[inline] + pub fn as_function(&self) -> Option<&Function> { + match self.data { + ObjectData::Function(ref function) => Some(function), + _ => None, + } + } + + /// Checks if it a Symbol object. + #[inline] + pub fn is_symbol(&self) -> bool { + match self.data { + ObjectData::Symbol => true, + _ => false, + } + } + + #[inline] + pub fn as_symbol(&self) -> Option<()> { + match self.data { + ObjectData::Symbol => Some(()), + _ => None, + } + } + + /// Checks if it an Error object. + #[inline] + pub fn is_error(&self) -> bool { + match self.data { + ObjectData::Error => true, + _ => false, + } + } + + #[inline] + pub fn as_error(&self) -> Option<()> { + match self.data { + ObjectData::Error => Some(()), + _ => None, + } + } + + /// Checks if it a Boolean object. + #[inline] + pub fn is_boolean(&self) -> bool { + match self.data { + ObjectData::Boolean(_) => true, + _ => false, + } + } + + #[inline] + pub fn as_boolean(&self) -> Option { + match self.data { + ObjectData::Boolean(boolean) => Some(boolean), + _ => None, + } + } + + /// Checks if it a `Number` object. + #[inline] + pub fn is_number(&self) -> bool { + match self.data { + ObjectData::Number(_) => true, + _ => false, + } + } + + #[inline] + pub fn as_number(&self) -> Option { + match self.data { + ObjectData::Number(number) => Some(number), + _ => None, + } + } + + /// Checks if it a `BigInt` object. + #[inline] + pub fn is_bigint(&self) -> bool { + match self.data { + ObjectData::BigInt(_) => true, + _ => false, + } + } + + #[inline] + pub fn as_bigint(&self) -> Option<&AstBigInt> { + match self.data { + ObjectData::BigInt(ref bigint) => Some(bigint), + _ => None, + } + } + + /// Checks if it an ordinary object. + #[inline] + pub fn is_ordinary(&self) -> bool { + match self.data { + ObjectData::Ordinary => true, + _ => false, + } + } + + #[inline] + pub fn internal_slots(&self) -> &FxHashMap { + &self.internal_slots + } + + #[inline] + pub fn internal_slots_mut(&mut self) -> &mut FxHashMap { + &mut self.internal_slots + } + + #[inline] + pub fn properties(&self) -> &FxHashMap { + &self.properties + } + + #[inline] + pub fn properties_mut(&mut self) -> &mut FxHashMap { + &mut self.properties + } + + #[inline] + pub fn symbol_properties(&self) -> &FxHashMap { + &self.symbol_properties + } + + #[inline] + pub fn symbol_properties_mut(&mut self) -> &mut FxHashMap { + &mut self.symbol_properties + } + + #[inline] + pub fn state(&self) -> &Option { + &self.state + } + + #[inline] + pub fn state_mut(&mut self) -> &mut Option { + &mut self.state + } } /// Create a new object. @@ -622,7 +502,6 @@ pub fn create(global: &Value) -> Value { let object = make_constructor_fn("Object", 1, make_object, global, prototype, true); - object.set_field("length", Value::from(1)); make_builtin_fn(set_prototype_of, "setPrototypeOf", &object, 2); make_builtin_fn(get_prototype_of, "getPrototypeOf", &object, 1); make_builtin_fn(define_property, "defineProperty", &object, 3); diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index af82d3bfe80..ece9e880454 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -16,7 +16,7 @@ use regex::Regex; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{InternalState, ObjectKind}, + object::{InternalState, ObjectData}, property::Property, value::{ResultValue, Value, ValueData}, }, @@ -78,7 +78,8 @@ impl RegExp { regex_body = body.into(); } ValueData::Object(ref obj) => { - let slots = &obj.borrow().internal_slots; + let obj = obj.borrow(); + let slots = obj.internal_slots(); if slots.get("RegExpMatcher").is_some() { // first argument is another `RegExp` object, so copy its pattern and flags if let Some(body) = slots.get("OriginalSource") { @@ -159,7 +160,7 @@ impl RegExp { // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Ordinary); + this.set_data(ObjectData::Ordinary); this.set_internal_slot("RegExpMatcher", Value::undefined()); this.set_internal_slot("OriginalSource", Value::from(regex_body)); this.set_internal_slot("OriginalFlags", Value::from(regex_flags)); @@ -353,9 +354,8 @@ impl RegExp { } let result = Value::from(result); - result - .set_property_slice("index", Property::default().value(Value::from(m.start()))); - result.set_property_slice("input", Property::default().value(Value::from(arg_str))); + result.set_property("index", Property::default().value(Value::from(m.start()))); + result.set_property("input", Property::default().value(Value::from(arg_str))); result } else { if regex.use_last_index { @@ -440,11 +440,9 @@ impl RegExp { let match_val = Value::from(match_vec); - match_val.set_property_slice( - "index", - Property::default().value(Value::from(m.start())), - ); - match_val.set_property_slice( + match_val + .set_property("index", Property::default().value(Value::from(m.start()))); + match_val.set_property( "input", Property::default().value(Value::from(arg_str.clone())), ); @@ -462,7 +460,7 @@ impl RegExp { let length = matches.len(); let result = Value::from(matches); result.set_field("length", Value::from(length)); - result.set_kind(ObjectKind::Array); + result.set_data(ObjectData::Array); Ok(result) } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 06ba55460b1..51e9eb90c12 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -15,7 +15,7 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{Object, ObjectKind}, + object::{Object, ObjectData}, property::Property, value::{ResultValue, Value, ValueData}, RegExp, @@ -46,32 +46,20 @@ impl String { ) -> ResultValue { // This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe // to its Javascript Identifier (global constructor method name) - let s = args.get(0).unwrap_or(&Value::string("")).clone(); - let length_str = s.to_string().chars().count(); + let string = args.get(0).map(|x| x.to_string()).unwrap_or_default(); + let length = string.chars().count(); - this.set_field("length", Value::from(length_str as i32)); + this.set_field("length", Value::from(length as i32)); - this.set_kind(ObjectKind::String); - this.set_internal_slot("StringData", s); + this.set_data(ObjectData::String(string.clone())); - let arg = match args.get(0) { - Some(v) => v.clone(), - None => Value::undefined(), - }; - - if arg.is_undefined() { - return Ok("".into()); - } - - Ok(Value::from(arg.to_string())) + Ok(Value::from(string)) } /// Get the string value to a primitive string #[allow(clippy::wrong_self_convention)] pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - // Get String from String Object and send it back as a new value - let primitive_val = this.get_internal_slot("StringData"); - Ok(Value::from(format!("{}", primitive_val))) + Ok(Value::from(this.to_string())) } /// `String.prototype.charAt( index )` @@ -398,10 +386,11 @@ impl String { match value.deref() { ValueData::String(ref body) => body.into(), ValueData::Object(ref obj) => { - let slots = &obj.borrow().internal_slots; - if slots.get("RegExpMatcher").is_some() { + let obj = obj.borrow(); + + if obj.internal_slots().get("RegExpMatcher").is_some() { // first argument is another `RegExp` object, so copy its pattern and flags - if let Some(body) = slots.get("OriginalSource") { + if let Some(body) = obj.internal_slots().get("OriginalSource") { return body.to_string(); } } @@ -1045,7 +1034,8 @@ impl String { let prototype = Value::new_object(Some(global)); let length = Property::default().value(Value::from(0)); - prototype.set_property_slice("length", length); + prototype.set_property("length", length); + make_builtin_fn(Self::char_at, "charAt", &prototype, 1); make_builtin_fn(Self::char_code_at, "charCodeAt", &prototype, 1); make_builtin_fn(Self::to_string, "toString", &prototype, 0); diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index e71a5dc09c3..a3a7156e9e3 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -21,15 +21,11 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{ - internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE, - PROTOTYPE, - }, - value::{ResultValue, Value, ValueData}, + object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, + value::{ResultValue, Value}, }, exec::Interpreter, }; -use gc::{Gc, GcCell}; use rand::random; /// Creates Symbol instances. @@ -48,7 +44,7 @@ pub fn call_symbol(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> Resu // So we start by creating an Object // TODO: Set prototype to Symbol.prototype (by changing to Object::create(), use interpreter to get Symbol.prototype) let mut sym_instance = Object::default(); - sym_instance.kind = ObjectKind::Symbol; + sym_instance.data = ObjectData::Symbol; // Set description which should either be undefined or a string let desc_string = match args.get(0) { @@ -67,9 +63,7 @@ pub fn call_symbol(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> Resu .get_field(PROTOTYPE); sym_instance.set_internal_slot(INSTANCE_PROTOTYPE, proto); - Ok(Value(Gc::new(ValueData::Symbol(Box::new(GcCell::new( - sym_instance, - )))))) + Ok(Value::symbol(sym_instance)) } /// `Symbol.prototype.toString()` diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index b2459cf40f0..5f4a2b4d613 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -144,7 +144,7 @@ where fn from(value: &[T]) -> Self { let mut array = Object::default(); for (i, item) in value.iter().enumerate() { - array.properties.insert( + array.properties_mut().insert( i.to_string(), Property::default().value(item.clone().into()), ); @@ -161,7 +161,7 @@ where let mut array = Object::default(); for (i, item) in value.into_iter().enumerate() { array - .properties + .properties_mut() .insert(i.to_string(), Property::default().value(item.into())); } Value::from(array) diff --git a/boa/src/builtins/value/hash.rs b/boa/src/builtins/value/hash.rs new file mode 100644 index 00000000000..fc0462f0730 --- /dev/null +++ b/boa/src/builtins/value/hash.rs @@ -0,0 +1,52 @@ +use super::*; + +use crate::builtins::Number; +use std::hash::{Hash, Hasher}; + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + same_value_zero(self, other) + } +} + +impl Eq for Value {} + +#[derive(PartialEq, Eq, Hash)] +struct UndefinedHashable; + +#[derive(PartialEq, Eq, Hash)] +struct NullHashable; + +struct RationalHashable(f64); + +impl PartialEq for RationalHashable { + #[inline] + fn eq(&self, other: &Self) -> bool { + Number::same_value(self.0, other.0) + } +} + +impl Eq for RationalHashable {} + +impl Hash for RationalHashable { + #[inline] + fn hash(&self, state: &mut H) { + self.0.to_bits().hash(state); + } +} + +impl Hash for Value { + fn hash(&self, state: &mut H) { + let data = self.data(); + match data { + ValueData::Undefined => UndefinedHashable.hash(state), + ValueData::Null => NullHashable.hash(state), + ValueData::String(ref string) => string.hash(state), + ValueData::Boolean(boolean) => boolean.hash(state), + ValueData::Integer(integer) => integer.hash(state), + ValueData::BigInt(ref bigint) => bigint.hash(state), + ValueData::Rational(rational) => RationalHashable(*rational).hash(state), + ValueData::Symbol(_) | ValueData::Object(_) => std::ptr::hash(data, state), + } + } +} diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index ec682c7b09e..3cddcfa898e 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -7,14 +7,11 @@ mod tests; use crate::builtins::{ function::Function, - object::{ - internal_methods_trait::ObjectInternalMethods, InternalState, InternalStateCell, Object, - ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE, - }, + object::{InternalState, InternalStateCell, Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, }; use crate::syntax::ast::bigint::BigInt; -use gc::{Finalize, Gc, GcCell, GcCellRef, Trace}; +use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace}; use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; use std::{ any::Any, @@ -22,13 +19,15 @@ use std::{ convert::TryFrom, f64::NAN, fmt::{self, Display}, - ops::{Add, BitAnd, BitOr, BitXor, Deref, DerefMut, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, + ops::{Add, BitAnd, BitOr, BitXor, Deref, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, str::FromStr, }; pub mod conversions; +pub mod hash; pub mod operations; pub use conversions::*; +pub use hash::*; pub use operations::*; /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) @@ -37,7 +36,7 @@ pub type ResultValue = Result; /// A Garbage-collected Javascript value as represented in the interpreter. #[derive(Debug, Clone, Trace, Finalize, Default)] -pub struct Value(pub(crate) Gc); +pub struct Value(Gc); impl Value { /// Creates a new `undefined` value. @@ -52,6 +51,12 @@ impl Value { Self(Gc::new(ValueData::Null)) } + /// Creates a new number with `NaN` value. + #[inline] + pub fn nan() -> Self { + Self::number(NAN) + } + /// Creates a new string value. #[inline] pub fn string(value: S) -> Self @@ -106,6 +111,12 @@ impl Value { Self(Gc::new(ValueData::Object(Box::new(GcCell::new(object))))) } + /// Creates a new symbol value. + #[inline] + pub fn symbol(object: Object) -> Self { + Self(Gc::new(ValueData::Symbol(Box::new(GcCell::new(object))))) + } + /// Gets the underlying `ValueData` structure. #[inline] pub fn data(&self) -> &ValueData { @@ -133,12 +144,12 @@ impl Value { } /// Similar to `new_object`, but you can pass a prototype to create from, plus a kind - pub fn new_object_from_prototype(proto: Value, kind: ObjectKind) -> Self { + pub fn new_object_from_prototype(proto: Value, data: ObjectData) -> Self { let mut object = Object::default(); - object.kind = kind; + object.data = data; object - .internal_slots + .internal_slots_mut() .insert(INSTANCE_PROTOTYPE.to_string(), proto); Self::object(object) @@ -197,6 +208,7 @@ impl ValueData { } /// Returns true if the value is an object + #[inline] pub fn is_object(&self) -> bool { match *self { Self::Object(_) => true, @@ -204,7 +216,24 @@ impl ValueData { } } - /// Returns true if the value is a symbol + #[inline] + pub fn as_object(&self) -> Option> { + match *self { + ValueData::Object(ref o) => Some(o.borrow()), + _ => None, + } + } + + #[inline] + pub fn as_object_mut(&self) -> Option> { + match *self { + ValueData::Object(ref o) => Some(o.borrow_mut()), + _ => None, + } + } + + /// Returns true if the value is a symbol. + #[inline] pub fn is_symbol(&self) -> bool { match *self { Self::Symbol(_) => true, @@ -213,17 +242,16 @@ impl ValueData { } /// Returns true if the value is a function + #[inline] pub fn is_function(&self) -> bool { match *self { - Self::Object(ref o) => { - let borrowed_obj = o.borrow(); - borrowed_obj.is_callable() || borrowed_obj.is_constructable() - } + Self::Object(ref o) => o.borrow().is_function(), _ => false, } } /// Returns true if the value is undefined + #[inline] pub fn is_undefined(&self) -> bool { match *self { Self::Undefined => true, @@ -232,6 +260,7 @@ impl ValueData { } /// Returns true if the value is null + #[inline] pub fn is_null(&self) -> bool { match *self { Self::Null => true, @@ -240,6 +269,7 @@ impl ValueData { } /// Returns true if the value is null or undefined + #[inline] pub fn is_null_or_undefined(&self) -> bool { match *self { Self::Null | Self::Undefined => true, @@ -248,6 +278,7 @@ impl ValueData { } /// Returns true if the value is a 64-bit floating-point number + #[inline] pub fn is_double(&self) -> bool { match *self { Self::Rational(_) => true, @@ -256,6 +287,7 @@ impl ValueData { } /// Returns true if the value is integer. + #[inline] #[allow(clippy::float_cmp)] pub fn is_integer(&self) -> bool { // If it can fit in a i32 and the trucated version is @@ -269,7 +301,8 @@ impl ValueData { } } - /// Returns true if the value is a number + /// Returns true if the value is a number. + #[inline] pub fn is_number(&self) -> bool { match self { Self::Rational(_) | Self::Integer(_) => true, @@ -277,7 +310,8 @@ impl ValueData { } } - /// Returns true if the value is a string + /// Returns true if the value is a string. + #[inline] pub fn is_string(&self) -> bool { match *self { Self::String(_) => true, @@ -285,7 +319,8 @@ impl ValueData { } } - /// Returns true if the value is a boolean + /// Returns true if the value is a boolean. + #[inline] pub fn is_boolean(&self) -> bool { match *self { Self::Boolean(_) => true, @@ -293,9 +328,9 @@ impl ValueData { } } - /// Returns true if the value is true + /// Returns true if the value is true. /// - /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) + /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean pub fn is_true(&self) -> bool { match *self { Self::Object(_) => true, @@ -364,8 +399,8 @@ impl ValueData { ValueData::BigInt(b) => Some(b.clone()), ValueData::Object(ref o) => { let object = (o).deref().borrow(); - if object.kind == ObjectKind::BigInt { - object.get_internal_slot("BigIntData").to_bigint() + if let ObjectData::BigInt(ref bigint) = object.data { + Some(bigint.clone()) } else { None } @@ -374,23 +409,28 @@ impl ValueData { } } - pub fn as_object(&self) -> Option> { + /// Creates a new boolean value from the input + pub fn to_boolean(&self) -> bool { match *self { - ValueData::Object(ref o) => Some(o.borrow()), - _ => None, + Self::Undefined | Self::Null => false, + Self::Symbol(_) | Self::Object(_) => true, + Self::String(ref s) if !s.is_empty() => true, + Self::Rational(n) if n != 0.0 && !n.is_nan() => true, + Self::Integer(n) if n != 0 => true, + Self::BigInt(ref n) if *n != 0 => true, + Self::Boolean(v) => v, + _ => false, } } /// Removes a property from a Value object. /// - /// It will return a boolean based on if the value was removed, if there was no value to remove false is returned + /// It will return a boolean based on if the value was removed, if there was no value to remove false is returned. pub fn remove_property(&self, field: &str) -> bool { - let removed = match *self { - Self::Object(ref obj) => obj.borrow_mut().deref_mut().properties.remove(field), - _ => None, - }; - - removed.is_some() + self.as_object_mut() + .map(|mut x| x.properties_mut().remove(field)) + .flatten() + .is_some() } /// Resolve the property in the object. @@ -423,9 +463,9 @@ impl ValueData { _ => return None, }; - match obj.properties.get(field) { + match obj.properties().get(field) { Some(val) => Some(val.clone()), - None => match obj.internal_slots.get(&INSTANCE_PROTOTYPE.to_string()) { + None => match obj.internal_slots().get(INSTANCE_PROTOTYPE) { Some(value) => value.get_property(field), None => None, }, @@ -443,18 +483,13 @@ impl ValueData { writable: Option, configurable: Option, ) { - let obj: Option = match self { - Self::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()), - _ => None, - }; - - if let Some(mut obj_data) = obj { + if let Some(ref mut object) = self.as_object_mut() { // Use value, or walk up the prototype chain - if let Some(ref mut prop) = obj_data.properties.get_mut(field) { - prop.value = value; - prop.enumerable = enumerable; - prop.writable = writable; - prop.configurable = configurable; + if let Some(ref mut property) = object.properties_mut().get_mut(field) { + property.value = value; + property.enumerable = enumerable; + property.writable = writable; + property.configurable = configurable; } } } @@ -475,7 +510,7 @@ impl ValueData { _ => return Value::undefined(), }; - match obj.internal_slots.get(field) { + match obj.internal_slots().get(field) { Some(val) => val.clone(), None => Value::undefined(), } @@ -520,19 +555,17 @@ impl ValueData { /// Check whether an object has an internal state set. pub fn has_internal_state(&self) -> bool { - if let Self::Object(ref obj) = *self { - obj.borrow().state.is_some() - } else { - false + match self.as_object() { + Some(object) => object.state().is_some(), + None => false, } } /// Get the internal state of an object. pub fn get_internal_state(&self) -> Option { - if let Self::Object(ref obj) = *self { - obj.borrow().state.as_ref().cloned() - } else { - None + match self.as_object() { + Some(object) => object.state().clone(), + None => None, } } @@ -546,10 +579,9 @@ impl ValueData { &self, f: F, ) -> R { - if let Self::Object(ref obj) = *self { - let o = obj.borrow(); - let state = o - .state + if let Some(object) = self.as_object() { + let state = object + .state() .as_ref() .expect("no state") .downcast_ref() @@ -570,10 +602,9 @@ impl ValueData { &self, f: F, ) -> R { - if let Self::Object(ref obj) = *self { - let mut o = obj.borrow_mut(); - let state = o - .state + if let Some(mut object) = self.as_object_mut() { + let state = object + .state_mut() .as_mut() .expect("no state") .downcast_mut() @@ -600,7 +631,7 @@ impl ValueData { let val = val.into(); if let Self::Object(ref obj) = *self { - if obj.borrow().kind == ObjectKind::Array { + if obj.borrow().is_array() { if let Ok(num) = field.to_string().parse::() { if num > 0 { let len = i32::from(&self.get_field("length")); @@ -624,52 +655,48 @@ impl ValueData { } /// Set the private field in the value - pub fn set_internal_slot(&self, field: &str, val: Value) -> Value { - if let Self::Object(ref obj) = *self { - obj.borrow_mut() - .internal_slots - .insert(field.to_string(), val.clone()); + pub fn set_internal_slot(&self, field: &str, value: Value) -> Value { + if let Some(mut object) = self.as_object_mut() { + object + .internal_slots_mut() + .insert(field.to_string(), value.clone()); } - val + value } /// Set the kind of an object - pub fn set_kind(&self, kind: ObjectKind) { + pub fn set_data(&self, data: ObjectData) { if let Self::Object(ref obj) = *self { - (*obj.deref().borrow_mut()).kind = kind; + (*obj.deref().borrow_mut()).data = data; } } - /// Set the property in the value - pub fn set_property(&self, field: String, prop: Property) -> Property { - if let Self::Object(ref obj) = *self { - obj.borrow_mut().properties.insert(field, prop.clone()); + /// Set the property in the value. + pub fn set_property(&self, field: S, property: Property) -> Property + where + S: Into, + { + if let Some(mut object) = self.as_object_mut() { + object + .properties_mut() + .insert(field.into(), property.clone()); } - prop - } - - /// Set the property in the value - pub fn set_property_slice(&self, field: &str, prop: Property) -> Property { - self.set_property(field.to_string(), prop) + property } /// Set internal state of an Object. Discards the previous state if it was set. pub fn set_internal_state(&self, state: T) { - if let Self::Object(ref obj) = *self { - obj.borrow_mut() - .state - .replace(InternalStateCell::new(state)); + if let Some(mut object) = self.as_object_mut() { + object.state_mut().replace(InternalStateCell::new(state)); } } /// Consume the function and return a Value - pub fn from_func(native_func: Function) -> Value { - // Object with Kind set to function - let mut new_func = crate::builtins::object::Object::function(); + pub fn from_func(function: Function) -> Value { // Get Length - let length = native_func.params.len(); - // Set [[Call]] internal slot - new_func.set_func(native_func); + let length = function.params.len(); + // Object with Kind set to function + let new_func = Object::function(function); // Wrap Object in GC'd Value let new_func_val = Value::from(new_func); // Set length to parameters @@ -688,12 +715,12 @@ impl ValueData { JSONValue::Array(vs) => { let mut new_obj = Object::default(); for (idx, json) in vs.iter().enumerate() { - new_obj.properties.insert( + new_obj.properties_mut().insert( idx.to_string(), Property::default().value(Value::from(json.clone())), ); } - new_obj.properties.insert( + new_obj.properties_mut().insert( "length".to_string(), Property::default().value(Value::from(vs.len())), ); @@ -702,7 +729,7 @@ impl ValueData { JSONValue::Object(obj) => { let mut new_obj = Object::default(); for (key, json) in obj.iter() { - new_obj.properties.insert( + new_obj.properties_mut().insert( key.clone(), Property::default().value(Value::from(json.clone())), ); @@ -720,9 +747,9 @@ impl ValueData { Self::Null => JSONValue::Null, Self::Boolean(b) => JSONValue::Bool(b), Self::Object(ref obj) => { - if obj.borrow().kind == ObjectKind::Array { + if obj.borrow().is_array() { let mut arr: Vec = Vec::new(); - obj.borrow().properties.keys().for_each(|k| { + obj.borrow().properties().keys().for_each(|k| { if k != "length" { let value = self.get_field(k.to_string()); if value.is_undefined() || value.is_function() { @@ -735,7 +762,7 @@ impl ValueData { JSONValue::Array(arr) } else { let mut new_obj = Map::new(); - obj.borrow().properties.keys().for_each(|k| { + obj.borrow().properties().keys().for_each(|k| { let key = k.clone(); let value = self.get_field(k.to_string()); if !value.is_undefined() && !value.is_function() { @@ -843,7 +870,7 @@ macro_rules! print_obj_value { (impl $field:ident, $v:expr, $f:expr) => { $v .borrow() - .$field + .$field() .iter() .map($f) .collect::>() @@ -856,22 +883,13 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { ValueData::Object(ref v) => { // Can use the private "type" field of an Object to match on // which type of Object it represents for special printing - match v.borrow().kind { - ObjectKind::String => String::from( - v.borrow() - .internal_slots - .get("StringData") - .expect("Cannot get primitive value from String"), - ), - ObjectKind::Boolean => { - let bool_data = v.borrow().get_internal_slot("BooleanData").to_string(); - - format!("Boolean {{ {} }}", bool_data) - } - ObjectKind::Array => { + match v.borrow().data { + ObjectData::String(ref string) => string.clone(), + ObjectData::Boolean(boolean) => format!("Boolean {{ {} }}", boolean), + ObjectData::Array => { let len = i32::from( &v.borrow() - .properties + .properties() .get("length") .unwrap() .value @@ -889,7 +907,7 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { // which are part of the Array log_string_from( &v.borrow() - .properties + .properties() .get(&i.to_string()) .unwrap() .value diff --git a/boa/src/builtins/value/operations.rs b/boa/src/builtins/value/operations.rs index e54459ea84f..67ba5289655 100644 --- a/boa/src/builtins/value/operations.rs +++ b/boa/src/builtins/value/operations.rs @@ -168,7 +168,7 @@ pub fn same_value_non_number(x: &Value, y: &Value) -> bool { } "bigint" => BigInt::try_from(x).unwrap() == BigInt::try_from(y).unwrap(), "boolean" => bool::from(x) == bool::from(y), - "object" => std::ptr::eq(x, y), + "object" => std::ptr::eq(x.data(), y.data()), _ => false, } } diff --git a/boa/src/builtins/value/tests.rs b/boa/src/builtins/value/tests.rs index 193efee084c..191387b1cfe 100644 --- a/boa/src/builtins/value/tests.rs +++ b/boa/src/builtins/value/tests.rs @@ -1,6 +1,9 @@ use super::*; use crate::{forward, Interpreter, Realm}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + #[test] fn check_is_object() { let val = Value::new_object(None); @@ -91,4 +94,64 @@ fn abstract_equality_comparison() { assert_eq!(forward(&mut engine, "0 == NaN"), "false"); assert_eq!(forward(&mut engine, "'foo' == NaN"), "false"); assert_eq!(forward(&mut engine, "NaN == NaN"), "false"); + + assert_eq!( + forward( + &mut engine, + "Number.POSITIVE_INFINITY === Number.POSITIVE_INFINITY" + ), + "true" + ); + assert_eq!( + forward( + &mut engine, + "Number.NEGAVIVE_INFINITY === Number.NEGAVIVE_INFINITY" + ), + "true" + ); +} + +fn hash_value(value: &Value) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() +} + +#[test] +fn hash_undefined() { + let value1 = Value::undefined(); + let value_clone = value1.clone(); + assert_eq!(value1, value_clone); + + let value2 = Value::undefined(); + assert_eq!(value1, value2); + + assert_eq!(hash_value(&value1), hash_value(&value_clone)); + assert_eq!(hash_value(&value2), hash_value(&value_clone)); +} + +#[test] +fn hash_rational() { + let value1 = Value::rational(1.0); + let value2 = Value::rational(1.0); + assert_eq!(value1, value2); + assert_eq!(hash_value(&value1), hash_value(&value2)); + + let nan = Value::nan(); + assert_eq!(nan, nan); + assert_eq!(hash_value(&nan), hash_value(&nan)); + assert_ne!(hash_value(&nan), hash_value(&Value::rational(1.0))); +} + +#[test] +fn hash_object() { + let object1 = Value::object(Object::default()); + assert_eq!(object1, object1); + assert_eq!(object1, object1.clone()); + + let object2 = Value::object(Object::default()); + assert_ne!(object1, object2); + + assert_eq!(hash_value(&object1), hash_value(&object1.clone())); + assert_ne!(hash_value(&object1), hash_value(&object2)); } diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 65919b446ec..1ccd7be0fa8 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -24,7 +24,7 @@ pub type Environment = Gc>>; /// Give each environment an easy way to declare its own type /// This helps with comparisons -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum EnvironmentType { Declarative, Function, @@ -33,7 +33,7 @@ pub enum EnvironmentType { } /// The scope of a given variable -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum VariableScope { /// The variable declaration is scoped to the current block (`let` and `const`) Block, @@ -41,13 +41,13 @@ pub enum VariableScope { Function, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LexicalEnvironment { environment_stack: VecDeque, } /// An error that occurred during lexing or compiling of the source input. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct EnvironmentError { details: String, } diff --git a/boa/src/exec/expression/mod.rs b/boa/src/exec/expression/mod.rs index 57fc661ca7a..e146e96f8b3 100644 --- a/boa/src/exec/expression/mod.rs +++ b/boa/src/exec/expression/mod.rs @@ -3,7 +3,7 @@ use super::{Executable, Interpreter}; use crate::{ builtins::{ - object::{INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, value::{ResultValue, Value, ValueData}, }, syntax::ast::node::{Call, New, Node}, @@ -69,12 +69,13 @@ impl Executable for New { this.set_internal_slot(INSTANCE_PROTOTYPE, func_object.get_field(PROTOTYPE)); match func_object.data() { - ValueData::Object(ref o) => o.clone().borrow_mut().func.as_ref().unwrap().construct( - &mut func_object.clone(), - &v_args, - interpreter, - &mut this, - ), + ValueData::Object(ref obj) => { + let obj = (**obj).borrow(); + if let ObjectData::Function(ref func) = obj.data { + return func.construct(func_object.clone(), &mut this, &v_args, interpreter); + } + panic!("TypeError: not a function"); + } _ => Ok(Value::undefined()), } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 4f0c8266818..9beb2536be1 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -14,10 +14,7 @@ mod try_node; use crate::{ builtins::{ function::{Function as FunctionObject, FunctionBody, ThisMode}, - object::{ - internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE, - PROTOTYPE, - }, + object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{ResultValue, Value, ValueData}, }, @@ -97,8 +94,8 @@ impl Interpreter { callable, ); - let mut new_func = Object::function(); - new_func.set_func(func); + let new_func = Object::function(func); + let val = Value::from(new_func); val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone()); val.set_field(PROTOTYPE, proto); @@ -122,8 +119,10 @@ impl Interpreter { match *f.data() { ValueData::Object(ref obj) => { let obj = (**obj).borrow(); - let func = obj.func.as_ref().expect("Expected function"); - func.call(&mut f.clone(), arguments_list, self, this) + if let ObjectData::Function(ref func) = obj.data { + return func.call(f.clone(), this, arguments_list, self); + } + panic!("TypeError: not a function"); } _ => Err(Value::undefined()), } @@ -151,7 +150,7 @@ impl Interpreter { pub(crate) fn extract_array_properties(&mut self, value: &Value) -> Result, ()> { if let ValueData::Object(ref x) = *value.deref().borrow() { // Check if object is array - if x.deref().borrow().kind == ObjectKind::Array { + if let ObjectData::Array = x.deref().borrow().data { let length: i32 = self.value_to_rust_number(&value.get_field("length")) as i32; let values: Vec = (0..length) .map(|idx| value.get_field(idx.to_string())) @@ -281,46 +280,51 @@ impl Interpreter { ValueData::Undefined | ValueData::Integer(_) | ValueData::Null => { Err(Value::undefined()) } - ValueData::Boolean(_) => { + ValueData::Boolean(boolean) => { let proto = self .realm .environment .get_binding_value("Boolean") .get_field(PROTOTYPE); - let bool_obj = Value::new_object_from_prototype(proto, ObjectKind::Boolean); - bool_obj.set_internal_slot("BooleanData", value.clone()); - Ok(bool_obj) + Ok(Value::new_object_from_prototype( + proto, + ObjectData::Boolean(boolean), + )) } - ValueData::Rational(_) => { + ValueData::Rational(rational) => { let proto = self .realm .environment .get_binding_value("Number") .get_field(PROTOTYPE); - let number_obj = Value::new_object_from_prototype(proto, ObjectKind::Number); - number_obj.set_internal_slot("NumberData", value.clone()); - Ok(number_obj) + + Ok(Value::new_object_from_prototype( + proto, + ObjectData::Number(rational), + )) } - ValueData::String(_) => { + ValueData::String(ref string) => { let proto = self .realm .environment .get_binding_value("String") .get_field(PROTOTYPE); - let string_obj = Value::new_object_from_prototype(proto, ObjectKind::String); - string_obj.set_internal_slot("StringData", value.clone()); - Ok(string_obj) + + Ok(Value::new_object_from_prototype( + proto, + ObjectData::String(string.clone()), + )) } ValueData::Object(_) | ValueData::Symbol(_) => Ok(value.clone()), - ValueData::BigInt(_) => { + ValueData::BigInt(ref bigint) => { let proto = self .realm .environment .get_binding_value("BigInt") .get_field(PROTOTYPE); - let bigint_obj = Value::new_object_from_prototype(proto, ObjectKind::BigInt); - bigint_obj.set_internal_slot("BigIntData", value.clone()); + let bigint_obj = + Value::new_object_from_prototype(proto, ObjectData::BigInt(bigint.clone())); Ok(bigint_obj) } }