diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index e1f3d8214d7..58596a94593 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -71,7 +71,7 @@ impl Array { // Wipe existing contents of the array object let orig_length = array_obj.get_field("length").as_number().unwrap() as i32; for n in 0..orig_length { - array_obj_ptr.remove_property(&n.to_string()); + array_obj_ptr.remove_property(n); } // Create length @@ -82,7 +82,7 @@ impl Array { array_obj_ptr.set_property("length".to_string(), length); for (n, value) in array_contents.iter().enumerate() { - array_obj_ptr.set_field(n.to_string(), value); + array_obj_ptr.set_field(n, value); } Ok(array_obj_ptr) } @@ -94,7 +94,7 @@ impl Array { for (n, value) in add_values.iter().enumerate() { let new_index = orig_length.wrapping_add(n as i32); - array_ptr.set_field(new_index.to_string(), value); + array_ptr.set_field(new_index, value); } array_ptr.set_field( @@ -127,7 +127,7 @@ impl Array { length = args[0].as_number().unwrap() as i32; // TODO: It should not create an array of undefineds, but an empty array ("holy" array in V8) with length `n`. for n in 0..length { - this.set_field(n.to_string(), Value::undefined()); + this.set_field(n, Value::undefined()); } } 1 if args[0].is_double() => { @@ -135,7 +135,7 @@ impl Array { } _ => { for (n, value) in args.iter().enumerate() { - this.set_field(n.to_string(), value.clone()); + this.set_field(n, value.clone()); } } } @@ -197,13 +197,13 @@ impl Array { let this_length = this.get_field("length").as_number().unwrap() as i32; for n in 0..this_length { - new_values.push(this.get_field(n.to_string())); + new_values.push(this.get_field(n)); } for concat_array in args { let concat_length = concat_array.get_field("length").as_number().unwrap() as i32; for n in 0..concat_length { - new_values.push(concat_array.get_field(n.to_string())); + new_values.push(concat_array.get_field(n)); } } @@ -245,7 +245,7 @@ impl Array { } let pop_index = curr_length.wrapping_sub(1); let pop_value: Value = this.get_field(pop_index.to_string()); - this.remove_property(&pop_index.to_string()); + this.remove_property(pop_index); this.set_field("length", Value::from(pop_index)); Ok(pop_value) } @@ -275,7 +275,7 @@ impl Array { let length = this.get_field("length").as_number().unwrap() as i32; for i in 0..length { - let element = this.get_field(i.to_string()); + let element = this.get_field(i); let arguments = [element, Value::from(i), this.clone()]; interpreter.call(callback_arg, &this_arg, &arguments)?; @@ -309,7 +309,7 @@ impl Array { let mut elem_strs = Vec::new(); let length = this.get_field("length").as_number().unwrap() as i32; for n in 0..length { - let elem_str = this.get_field(n.to_string()).to_string(ctx)?.to_string(); + let elem_str = this.get_field(n).to_string(ctx)?.to_string(); elem_strs.push(elem_str); } @@ -377,21 +377,21 @@ impl Array { for lower in 0..middle { let upper = len.wrapping_sub(lower).wrapping_sub(1); - let upper_exists = this.has_field(&upper.to_string()); - let lower_exists = this.has_field(&lower.to_string()); + let upper_exists = this.has_field(upper); + let lower_exists = this.has_field(lower); - let upper_value = this.get_field(upper.to_string()); - let lower_value = this.get_field(lower.to_string()); + let upper_value = this.get_field(upper); + let lower_value = this.get_field(lower); if upper_exists && lower_exists { - this.set_field(upper.to_string(), lower_value); - this.set_field(lower.to_string(), upper_value); + this.set_field(upper, lower_value); + this.set_field(lower, upper_value); } else if upper_exists { - this.set_field(lower.to_string(), upper_value); - this.remove_property(&upper.to_string()); + this.set_field(lower, upper_value); + this.remove_property(upper); } else if lower_exists { - this.set_field(upper.to_string(), lower_value); - this.remove_property(&lower.to_string()); + this.set_field(upper, lower_value); + this.remove_property(lower); } } @@ -413,26 +413,25 @@ impl Array { if len == 0 { this.set_field("length", 0); - // Since length is 0, this will be an Undefined value - return Ok(this.get_field(0.to_string())); + return Ok(Value::undefined()); } - let first: Value = this.get_field(0.to_string()); + let first: Value = this.get_field(0); for k in 1..len { - let from = k.to_string(); - let to = (k.wrapping_sub(1)).to_string(); + let from = k; + let to = k.wrapping_sub(1); let from_value = this.get_field(from); if from_value.is_undefined() { - this.remove_property(&to); + this.remove_property(to); } else { this.set_field(to, from_value); } } let final_index = len.wrapping_sub(1); - this.remove_property(&(final_index).to_string()); + this.remove_property(final_index); this.set_field("length", Value::from(final_index)); Ok(first) @@ -457,19 +456,19 @@ impl Array { if arg_c > 0 { for k in (1..=len).rev() { - let from = (k.wrapping_sub(1)).to_string(); - let to = (k.wrapping_add(arg_c).wrapping_sub(1)).to_string(); + let from = k.wrapping_sub(1); + let to = k.wrapping_add(arg_c).wrapping_sub(1); let from_value = this.get_field(from); if from_value.is_undefined() { - this.remove_property(&to); + this.remove_property(to); } else { this.set_field(to, from_value); } } for j in 0..arg_c { this.set_field( - j.to_string(), + j, args.get(j as usize) .expect("Could not get argument") .clone(), @@ -515,7 +514,7 @@ impl Array { let max_len = this.get_field("length").as_number().unwrap() as i32; let mut len = max_len; while i < len { - let element = this.get_field(i.to_string()); + let element = this.get_field(i); let arguments = [element, Value::from(i), this.clone()]; let result = interpreter.call(callback, &this_arg, &arguments)?; if !result.to_boolean() { @@ -561,7 +560,7 @@ impl Array { let values: Vec = (0..length) .map(|idx| { - let element = this.get_field(idx.to_string()); + let element = this.get_field(idx); let args = [element, Value::from(idx), new.clone()]; interpreter @@ -615,7 +614,7 @@ impl Array { }; while idx < len { - let check_element = this.get_field(idx.to_string()).clone(); + let check_element = this.get_field(idx).clone(); if check_element.strict_equals(&search_element) { return Ok(Value::from(idx)); @@ -672,7 +671,7 @@ impl Array { }; while idx >= 0 { - let check_element = this.get_field(idx.to_string()).clone(); + let check_element = this.get_field(idx).clone(); if check_element.strict_equals(&search_element) { return Ok(Value::from(idx)); @@ -710,7 +709,7 @@ impl Array { let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); let len = this.get_field("length").as_number().unwrap() as i32; for i in 0..len { - let element = this.get_field(i.to_string()); + let element = this.get_field(i); let arguments = [element.clone(), Value::from(i), this.clone()]; let result = interpreter.call(callback, &this_arg, &arguments)?; if result.to_boolean() { @@ -750,7 +749,7 @@ impl Array { let length = this.get_field("length").as_number().unwrap() as i32; for i in 0..length { - let element = this.get_field(i.to_string()); + let element = this.get_field(i); let arguments = [element, Value::from(i), this.clone()]; let result = interpreter.call(predicate_arg, &this_arg, &arguments)?; @@ -798,7 +797,7 @@ impl Array { }; for i in start..fin { - this.set_field(i.to_string(), value.clone()); + this.set_field(i, value.clone()); } Ok(this.clone()) @@ -824,7 +823,7 @@ impl Array { let length = this.get_field("length").as_number().unwrap() as i32; for idx in 0..length { - let check_element = this.get_field(idx.to_string()).clone(); + let check_element = this.get_field(idx).clone(); if same_value_zero(&check_element, &search_element) { return Ok(Value::from(true)); @@ -879,7 +878,7 @@ impl Array { let span = max(to.wrapping_sub(from), 0); let mut new_array_len: i32 = 0; for i in from..from.wrapping_add(span) { - new_array.set_field(new_array_len.to_string(), this.get_field(i.to_string())); + new_array.set_field(new_array_len, this.get_field(i)); new_array_len = new_array_len.wrapping_add(1); } new_array.set_field("length", Value::from(new_array_len)); @@ -917,7 +916,7 @@ impl Array { let values = (0..length) .filter_map(|idx| { - let element = this.get_field(idx.to_string()); + let element = this.get_field(idx); let args = [element.clone(), Value::from(idx), new.clone()]; @@ -971,7 +970,7 @@ impl Array { let max_len = this.get_field("length").as_number().unwrap() as i32; let mut len = max_len; while i < len { - let element = this.get_field(i.to_string()); + let element = this.get_field(i); let arguments = [element, Value::from(i), this.clone()]; let result = interpreter.call(callback, &this_arg, &arguments)?; if result.to_boolean() { @@ -1018,7 +1017,7 @@ impl Array { let mut accumulator = if initial_value.is_undefined() { let mut k_present = false; while k < length { - if this.has_field(&k.to_string()) { + if this.has_field(k) { k_present = true; break; } @@ -1029,20 +1028,15 @@ impl Array { "Reduce was called on an empty array and with no initial value", ); } - let result = this.get_field(k.to_string()); + let result = this.get_field(k); k += 1; result } else { initial_value }; while k < length { - if this.has_field(&k.to_string()) { - let arguments = [ - accumulator, - this.get_field(k.to_string()), - Value::from(k), - this.clone(), - ]; + if this.has_field(k) { + let arguments = [accumulator, this.get_field(k), Value::from(k), this.clone()]; accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?; /* We keep track of possibly shortened length in order to prevent unnecessary iteration. It may also be necessary to do this since shortening the array length does not @@ -1091,7 +1085,7 @@ impl Array { let mut accumulator = if initial_value.is_undefined() { let mut k_present = false; loop { - if this.has_field(&k.to_string()) { + if this.has_field(k) { k_present = true; break; } @@ -1106,20 +1100,15 @@ impl Array { "reduceRight was called on an empty array and with no initial value", ); } - let result = this.get_field(k.to_string()); + let result = this.get_field(k); k -= 1; result } else { initial_value }; loop { - if this.has_field(&k.to_string()) { - let arguments = [ - accumulator, - this.get_field(k.to_string()), - Value::from(k), - this.clone(), - ]; + if this.has_field(k) { + let arguments = [accumulator, this.get_field(k), Value::from(k), this.clone()]; accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?; /* We keep track of possibly shortened length in order to prevent unnecessary iteration. It may also be necessary to do this since shortening the array length does not diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index ec4b0e0a513..f6e6eb56652 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -14,8 +14,8 @@ use crate::{ builtins::{ object::{Object, ObjectData, PROTOTYPE}, - property::{Attribute, Property, PropertyKey}, - value::{RcString, Value}, + property::{Attribute, Property}, + value::Value, Array, }, environment::lexical_environment::Environment, @@ -180,7 +180,7 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, ); // Define length as a property - obj.define_own_property(&PropertyKey::from(RcString::from("length")), length); + obj.define_own_property("length", length); let mut index: usize = 0; while index < len { let val = arguments_list.get(index).expect("Could not get argument"); @@ -189,8 +189,7 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, ); - obj.properties_mut() - .insert(RcString::from(index.to_string()), prop); + obj.insert_property(index, prop); index += 1; } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index f6611e29e82..71f86e50107 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -13,8 +13,15 @@ //! [json]: https://www.json.org/json-en.html //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON -use crate::builtins::{function::make_builtin_fn, property::Property, value::Value}; -use crate::{exec::Interpreter, BoaProfiler, Result}; +use crate::{ + builtins::{ + function::make_builtin_fn, + property::{Property, PropertyKey}, + value::Value, + }, + exec::Interpreter, + BoaProfiler, Result, +}; use serde_json::{self, Value as JSONValue}; #[cfg(test)] @@ -53,7 +60,7 @@ impl Json { Some(reviver) if reviver.is_function() => { let mut holder = Value::new_object(None); holder.set_field("", j); - Self::walk(reviver, ctx, &mut holder, Value::from("")) + Self::walk(reviver, ctx, &mut holder, &PropertyKey::from("")) } _ => Ok(j), } @@ -72,26 +79,26 @@ impl Json { reviver: &Value, ctx: &mut Interpreter, holder: &mut Value, - key: Value, + key: &PropertyKey, ) -> Result { let mut value = holder.get_field(key.clone()); let obj = value.as_object().as_deref().cloned(); if let Some(obj) = obj { - for key in obj.properties().keys() { - let v = Self::walk(reviver, ctx, &mut value, Value::from(key.as_str())); + for key in obj.keys() { + let v = Self::walk(reviver, ctx, &mut value, &key); match v { Ok(v) if !v.is_undefined() => { - value.set_field(key.as_str(), v); + value.set_field(key.clone(), v); } Ok(_) => { - value.remove_property(key.as_str()); + value.remove_property(key.clone()); } Err(_v) => {} } } } - ctx.call(reviver, holder, &[key, value]) + ctx.call(reviver, holder, &[key.into(), value]) } /// `JSON.stringify( value[, replacer[, space]] )` @@ -132,7 +139,6 @@ impl Json { .map(|obj| { let object_to_return = Value::new_object(None); for (key, val) in obj - .properties() .iter() .filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value))) { @@ -150,18 +156,17 @@ impl Json { }) .ok_or_else(Value::undefined)? } 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| { + let mut obj_to_return = serde_json::Map::new(); + let fields = replacer_as_object.keys().filter_map(|key| { if key == "length" { None } else { - Some(replacer.get_field(key.to_owned())) + Some(replacer.get_field(key)) } }); for field in fields { if let Some(value) = object - .get_property(&field.to_string(ctx)?) + .get_property(field.to_string(ctx)?) .and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx))) .transpose()? { diff --git a/boa/src/builtins/object/internal_methods.rs b/boa/src/builtins/object/internal_methods.rs index c86aa6f7353..0595a294abf 100644 --- a/boa/src/builtins/object/internal_methods.rs +++ b/boa/src/builtins/object/internal_methods.rs @@ -8,7 +8,7 @@ use crate::builtins::{ object::Object, property::{Attribute, Property, PropertyKey}, - value::{same_value, RcString, Value}, + value::{same_value, Value}, }; use crate::BoaProfiler; @@ -21,19 +21,14 @@ impl Object { /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p pub fn has_property(&self, property_key: &PropertyKey) -> bool { let prop = self.get_own_property(property_key); - if prop.value.is_none() { - let parent: Value = self.get_prototype_of(); - if !parent.is_null() { - // the parent value variant should be an object - // In the unlikely event it isn't return false - return match parent { - Value::Object(ref obj) => obj.borrow().has_property(property_key), - _ => false, - }; - } - return false; + if prop.is_none() { + let parent = self.get_prototype_of(); + return if let Value::Object(ref object) = parent { + object.borrow().has_property(property_key) + } else { + false + }; } - true } @@ -72,7 +67,7 @@ impl Object { return true; } if desc.configurable_or(false) { - self.remove_property(&property_key.to_string()); + self.remove_property(&property_key); return true; } @@ -96,9 +91,7 @@ impl Object { return Value::undefined(); } - let parent_obj = Object::from(&parent).expect("Failed to get object"); - - return parent_obj.get(property_key); + return parent.get_field(property_key.clone()); } if desc.is_data_descriptor() { @@ -116,11 +109,11 @@ impl Object { /// [[Set]] /// - pub fn set(&mut self, property_key: &PropertyKey, val: Value) -> bool { + pub fn set(&mut self, property_key: PropertyKey, val: Value) -> bool { let _timer = BoaProfiler::global().start_event("Object::set", "object"); // Fetch property key - let mut own_desc = self.get_own_property(property_key); + let mut own_desc = self.get_own_property(&property_key); // [2] if own_desc.is_none() { let parent = self.get_prototype_of(); @@ -158,10 +151,14 @@ impl Object { /// - [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: &PropertyKey, desc: Property) -> bool { + pub fn define_own_property(&mut self, key: K, desc: Property) -> bool + where + K: Into, + { let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object"); - let mut current = self.get_own_property(property_key); + let key = key.into(); + let mut current = self.get_own_property(&key); let extensible = self.is_extensible(); // https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor @@ -171,7 +168,7 @@ impl Object { return false; } - self.insert_property(property_key, desc); + self.insert_property(key, desc); return true; } // If every field is absent we don't need to set anything @@ -210,7 +207,8 @@ impl Object { current.set = None; } - self.insert_property(property_key, current); + self.insert_property(key, current); + return true; // 7 } else if current.is_data_descriptor() && desc.is_data_descriptor() { // a @@ -249,7 +247,7 @@ impl Object { return true; } // 9 - self.insert_property(property_key, desc); + self.insert_property(key, desc); true } @@ -261,41 +259,27 @@ impl Object { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p - pub fn get_own_property(&self, property_key: &PropertyKey) -> Property { + pub fn get_own_property(&self, key: &PropertyKey) -> Property { let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); // Prop could either be a String or Symbol - match property_key { - PropertyKey::String(ref st) => { - self.properties().get(st).map_or_else(Property::empty, |v| { - let mut d = Property::empty(); - if v.is_data_descriptor() { - d.value = v.value.clone(); - } else { - debug_assert!(v.is_accessor_descriptor()); - d.get = v.get.clone(); - d.set = v.set.clone(); - } - d.attribute = v.attribute; - d - }) + let property = match key { + PropertyKey::Index(index) => self.indexed_properties.get(&index), + PropertyKey::String(ref st) => self.string_properties.get(st), + PropertyKey::Symbol(ref symbol) => self.symbol_properties.get(symbol), + }; + property.map_or_else(Property::empty, |v| { + let mut d = Property::empty(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); } - PropertyKey::Symbol(ref symbol) => self - .symbol_properties() - .get(&symbol.hash()) - .map_or_else(Property::empty, |v| { - let mut d = Property::empty(); - if v.is_data_descriptor() { - d.value = v.value.clone(); - } else { - debug_assert!(v.is_accessor_descriptor()); - d.get = v.get.clone(); - d.set = v.set.clone(); - } - d.attribute = v.attribute; - d - }), - } + d.attribute = v.attribute; + d + }) } /// `Object.setPropertyOf(obj, prototype)` @@ -352,17 +336,29 @@ impl Object { /// Helper function for property insertion. #[inline] - pub(crate) fn insert_property(&mut self, name: N, p: Property) + pub(crate) fn insert_property(&mut self, key: K, property: Property) -> Option where - N: Into, + K: Into, { - self.properties.insert(name.into(), p); + match key.into() { + PropertyKey::Index(index) => self.indexed_properties.insert(index, property), + PropertyKey::String(ref string) => { + self.string_properties.insert(string.clone(), property) + } + PropertyKey::Symbol(ref symbol) => { + self.symbol_properties.insert(symbol.clone(), property) + } + } } /// Helper function for property removal. #[inline] - pub(crate) fn remove_property(&mut self, name: &str) { - self.properties.remove(name); + pub(crate) fn remove_property(&mut self, key: &PropertyKey) -> Option { + match key { + PropertyKey::Index(index) => self.indexed_properties.remove(&index), + PropertyKey::String(ref string) => self.string_properties.remove(string), + PropertyKey::Symbol(ref symbol) => self.symbol_properties.remove(symbol), + } } /// Inserts a field in the object `properties` without checking if it's writable. @@ -370,25 +366,16 @@ impl Object { /// If a field was already in the object with the same name that a `Some` is returned /// with that field, otherwise None is retuned. #[inline] - pub(crate) fn insert_field(&mut self, name: N, value: Value) -> Option + pub(crate) fn insert_field(&mut self, key: K, value: Value) -> Option where - N: Into, + K: Into, { - self.properties.insert( - name.into(), + self.insert_property( + key.into(), Property::data_descriptor( value, Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, ), ) } - - /// This function returns an Optional reference value to the objects field. - /// - /// if it exist `Some` is returned with a reference to that fields value. - /// Otherwise `None` is retuned. - #[inline] - pub fn get_field(&self, name: &str) -> Option<&Value> { - self.properties.get(name).and_then(|x| x.value.as_ref()) - } } diff --git a/boa/src/builtins/object/iter.rs b/boa/src/builtins/object/iter.rs new file mode 100644 index 00000000000..801606549d1 --- /dev/null +++ b/boa/src/builtins/object/iter.rs @@ -0,0 +1,421 @@ +use super::{Object, Property, PropertyKey, RcString, RcSymbol}; +use std::{collections::hash_map, iter::FusedIterator}; + +impl Object { + /// An iterator visiting all key-value pairs in arbitrary order. The iterator element type is `(PropertyKey, &'a Property)`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn iter(&self) -> Iter<'_> { + Iter { + indexed_properties: self.indexed_properties.iter(), + string_properties: self.string_properties.iter(), + symbol_properties: self.symbol_properties.iter(), + } + } + + /// An iterator visiting all keys in arbitrary order. The iterator element type is `PropertyKey`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn keys(&self) -> Keys<'_> { + Keys(self.iter()) + } + + /// An iterator visiting all values in arbitrary order. The iterator element type is `&'a Property`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn values(&self) -> Values<'_> { + Values(self.iter()) + } + + /// An iterator visiting all symbol key-value pairs in arbitrary order. The iterator element type is `(&'a RcSymbol, &'a Property)`. + /// + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn symbol_properties(&self) -> SymbolProperties<'_> { + SymbolProperties(self.symbol_properties.iter()) + } + + /// An iterator visiting all symbol keys in arbitrary order. The iterator element type is `&'a RcSymbol`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn symbol_property_keys(&self) -> SymbolPropertyKeys<'_> { + SymbolPropertyKeys(self.symbol_properties.keys()) + } + + /// An iterator visiting all symbol values in arbitrary order. The iterator element type is `&'a Property`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn symbol_property_values(&self) -> SymbolPropertyValues<'_> { + SymbolPropertyValues(self.symbol_properties.values()) + } + + /// An iterator visiting all indexed key-value pairs in arbitrary order. The iterator element type is `(&'a u32, &'a Property)`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn index_properties(&self) -> IndexProperties<'_> { + IndexProperties(self.indexed_properties.iter()) + } + + /// An iterator visiting all index keys in arbitrary order. The iterator element type is `&'a u32`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn index_property_keys(&self) -> IndexPropertyKeys<'_> { + IndexPropertyKeys(self.indexed_properties.keys()) + } + + /// An iterator visiting all index values in arbitrary order. The iterator element type is `&'a Property`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn index_property_values(&self) -> IndexPropertyValues<'_> { + IndexPropertyValues(self.indexed_properties.values()) + } + + /// An iterator visiting all string key-value pairs in arbitrary order. The iterator element type is `(&'a RcString, &'a Property)`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn string_properties(&self) -> StringProperties<'_> { + StringProperties(self.string_properties.iter()) + } + + /// An iterator visiting all string keys in arbitrary order. The iterator element type is `&'a RcString`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn string_property_keys(&self) -> StringPropertyKeys<'_> { + StringPropertyKeys(self.string_properties.keys()) + } + + /// An iterator visiting all string values in arbitrary order. The iterator element type is `&'a Property`. + /// + /// This iterator does not recurse down the prototype chain. + #[inline] + pub fn string_property_values(&self) -> StringPropertyValues<'_> { + StringPropertyValues(self.string_properties.values()) + } +} + +/// An iterator over the property entries of an `Object` +#[derive(Debug, Clone)] +pub struct Iter<'a> { + indexed_properties: hash_map::Iter<'a, u32, Property>, + string_properties: hash_map::Iter<'a, RcString, Property>, + symbol_properties: hash_map::Iter<'a, RcSymbol, Property>, +} + +impl<'a> Iterator for Iter<'a> { + type Item = (PropertyKey, &'a Property); + fn next(&mut self) -> Option { + if let Some((key, value)) = self.indexed_properties.next() { + Some(((*key).into(), value)) + } else if let Some((key, value)) = self.string_properties.next() { + Some((key.clone().into(), value)) + } else { + let (key, value) = self.symbol_properties.next()?; + Some((key.clone().into(), value)) + } + } +} + +impl ExactSizeIterator for Iter<'_> { + #[inline] + fn len(&self) -> usize { + self.indexed_properties.len() + self.string_properties.len() + self.symbol_properties.len() + } +} + +impl FusedIterator for Iter<'_> {} + +/// An iterator over the keys (`PropertyKey`) of an `Object`. +#[derive(Debug, Clone)] +pub struct Keys<'a>(Iter<'a>); + +impl<'a> Iterator for Keys<'a> { + type Item = PropertyKey; + fn next(&mut self) -> Option { + let (key, _) = self.0.next()?; + Some(key) + } +} + +impl ExactSizeIterator for Keys<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for Keys<'_> {} + +/// An iterator over the values (`Property`) of an `Object`. +#[derive(Debug, Clone)] +pub struct Values<'a>(Iter<'a>); + +impl<'a> Iterator for Values<'a> { + type Item = &'a Property; + fn next(&mut self) -> Option { + let (_, value) = self.0.next()?; + Some(value) + } +} + +impl ExactSizeIterator for Values<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for Values<'_> {} + +/// An iterator over the `Symbol` property entries of an `Object` +#[derive(Debug, Clone)] +pub struct SymbolProperties<'a>(hash_map::Iter<'a, RcSymbol, Property>); + +impl<'a> Iterator for SymbolProperties<'a> { + type Item = (&'a RcSymbol, &'a Property); + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for SymbolProperties<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for SymbolProperties<'_> {} + +/// An iterator over the keys (`RcSymbol`) of an `Object`. +#[derive(Debug, Clone)] +pub struct SymbolPropertyKeys<'a>(hash_map::Keys<'a, RcSymbol, Property>); + +impl<'a> Iterator for SymbolPropertyKeys<'a> { + type Item = &'a RcSymbol; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for SymbolPropertyKeys<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for SymbolPropertyKeys<'_> {} + +/// An iterator over the `Symbol` values (`Property`) of an `Object`. +#[derive(Debug, Clone)] +pub struct SymbolPropertyValues<'a>(hash_map::Values<'a, RcSymbol, Property>); + +impl<'a> Iterator for SymbolPropertyValues<'a> { + type Item = &'a Property; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for SymbolPropertyValues<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for SymbolPropertyValues<'_> {} + +/// An iterator over the indexed property entries of an `Object` +#[derive(Debug, Clone)] +pub struct IndexProperties<'a>(hash_map::Iter<'a, u32, Property>); + +impl<'a> Iterator for IndexProperties<'a> { + type Item = (&'a u32, &'a Property); + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for IndexProperties<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for IndexProperties<'_> {} + +/// An iterator over the index keys (`u32`) of an `Object`. +#[derive(Debug, Clone)] +pub struct IndexPropertyKeys<'a>(hash_map::Keys<'a, u32, Property>); + +impl<'a> Iterator for IndexPropertyKeys<'a> { + type Item = &'a u32; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for IndexPropertyKeys<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for IndexPropertyKeys<'_> {} + +/// An iterator over the index values (`Property`) of an `Object`. +#[derive(Debug, Clone)] +pub struct IndexPropertyValues<'a>(hash_map::Values<'a, u32, Property>); + +impl<'a> Iterator for IndexPropertyValues<'a> { + type Item = &'a Property; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for IndexPropertyValues<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for IndexPropertyValues<'_> {} + +/// An iterator over the `String` property entries of an `Object` +#[derive(Debug, Clone)] +pub struct StringProperties<'a>(hash_map::Iter<'a, RcString, Property>); + +impl<'a> Iterator for StringProperties<'a> { + type Item = (&'a RcString, &'a Property); + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for StringProperties<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for StringProperties<'_> {} + +/// An iterator over the string keys (`RcString`) of an `Object`. +#[derive(Debug, Clone)] +pub struct StringPropertyKeys<'a>(hash_map::Keys<'a, RcString, Property>); + +impl<'a> Iterator for StringPropertyKeys<'a> { + type Item = &'a RcString; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for StringPropertyKeys<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for StringPropertyKeys<'_> {} + +/// An iterator over the string values (`Property`) of an `Object`. +#[derive(Debug, Clone)] +pub struct StringPropertyValues<'a>(hash_map::Values<'a, RcString, Property>); + +impl<'a> Iterator for StringPropertyValues<'a> { + type Item = &'a Property; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for StringPropertyValues<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for StringPropertyValues<'_> {} diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index f1572ac8098..18ec20ff435 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -17,7 +17,7 @@ use crate::{ builtins::{ function::Function, map::ordered_map::OrderedMap, - property::Property, + property::{Property, PropertyKey}, value::{RcBigInt, RcString, RcSymbol, Value}, BigInt, Date, RegExp, }, @@ -34,9 +34,10 @@ use crate::builtins::value::same_value; mod gcobject; mod internal_methods; +mod iter; pub use gcobject::GcObject; -pub use internal_methods::*; +pub use iter::*; #[cfg(test)] mod tests; @@ -49,10 +50,11 @@ pub static PROTOTYPE: &str = "prototype"; pub struct Object { /// The type of the object. pub data: ObjectData, + indexed_properties: FxHashMap, /// Properties - properties: FxHashMap, + string_properties: FxHashMap, /// Symbol Properties - symbol_properties: FxHashMap, + symbol_properties: FxHashMap, /// Instance prototype `__proto__`. prototype: Value, /// Whether it can have new properties added to it. @@ -107,7 +109,8 @@ impl Default for Object { fn default() -> Self { Self { data: ObjectData::Ordinary, - properties: FxHashMap::default(), + indexed_properties: FxHashMap::default(), + string_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), extensible: true, @@ -127,7 +130,8 @@ impl Object { Self { data: ObjectData::Function(function), - properties: FxHashMap::default(), + indexed_properties: FxHashMap::default(), + string_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype, extensible: true, @@ -151,7 +155,8 @@ impl Object { pub fn boolean(value: bool) -> Self { Self { data: ObjectData::Boolean(value), - properties: FxHashMap::default(), + indexed_properties: FxHashMap::default(), + string_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), extensible: true, @@ -162,7 +167,8 @@ impl Object { pub fn number(value: f64) -> Self { Self { data: ObjectData::Number(value), - properties: FxHashMap::default(), + indexed_properties: FxHashMap::default(), + string_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), extensible: true, @@ -176,7 +182,8 @@ impl Object { { Self { data: ObjectData::String(value.into()), - properties: FxHashMap::default(), + indexed_properties: FxHashMap::default(), + string_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), extensible: true, @@ -187,7 +194,8 @@ impl Object { pub fn bigint(value: RcBigInt) -> Self { Self { data: ObjectData::BigInt(value), - properties: FxHashMap::default(), + indexed_properties: FxHashMap::default(), + string_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), extensible: true, @@ -388,26 +396,6 @@ impl Object { matches!(self.data, ObjectData::Ordinary) } - #[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 - } - pub fn prototype(&self) -> &Value { &self.prototype } diff --git a/boa/src/builtins/property/mod.rs b/boa/src/builtins/property/mod.rs index 0cc272bcdaf..f32895e1fc8 100644 --- a/boa/src/builtins/property/mod.rs +++ b/boa/src/builtins/property/mod.rs @@ -18,6 +18,7 @@ use crate::builtins::value::RcString; use crate::builtins::value::RcSymbol; use crate::builtins::Value; use gc::{Finalize, Trace}; +use std::convert::TryFrom; use std::fmt; pub mod attribute; @@ -290,33 +291,50 @@ impl<'a> From<&'a Value> for Property { pub enum PropertyKey { String(RcString), Symbol(RcSymbol), + Index(u32), } impl From for PropertyKey { #[inline] fn from(string: RcString) -> PropertyKey { - PropertyKey::String(string) + if let Ok(index) = string.parse() { + PropertyKey::Index(index) + } else { + PropertyKey::String(string) + } } } impl From<&str> for PropertyKey { #[inline] fn from(string: &str) -> PropertyKey { - PropertyKey::String(string.into()) + if let Ok(index) = string.parse() { + PropertyKey::Index(index) + } else { + PropertyKey::String(string.into()) + } } } impl From for PropertyKey { #[inline] fn from(string: String) -> PropertyKey { - PropertyKey::String(string.into()) + if let Ok(index) = string.parse() { + PropertyKey::Index(index) + } else { + PropertyKey::String(string.into()) + } } } impl From> for PropertyKey { #[inline] fn from(string: Box) -> PropertyKey { - PropertyKey::String(string.into()) + if let Ok(index) = string.parse() { + PropertyKey::Index(index) + } else { + PropertyKey::String(string.into()) + } } } @@ -333,16 +351,7 @@ impl fmt::Display for PropertyKey { match self { PropertyKey::String(ref string) => string.fmt(f), PropertyKey::Symbol(ref symbol) => symbol.fmt(f), - } - } -} - -impl From<&PropertyKey> for RcString { - #[inline] - fn from(property_key: &PropertyKey) -> RcString { - match property_key { - PropertyKey::String(ref string) => string.clone(), - PropertyKey::Symbol(ref symbol) => symbol.to_string().into(), + PropertyKey::Index(index) => index.fmt(f), } } } @@ -353,6 +362,13 @@ impl From<&PropertyKey> for Value { match property_key { PropertyKey::String(ref string) => string.clone().into(), PropertyKey::Symbol(ref symbol) => symbol.clone().into(), + PropertyKey::Index(index) => { + if let Ok(integer) = i32::try_from(*index) { + Value::integer(integer) + } else { + Value::number(*index) + } + } } } } @@ -363,6 +379,81 @@ impl From for Value { match property_key { PropertyKey::String(ref string) => string.clone().into(), PropertyKey::Symbol(ref symbol) => symbol.clone().into(), + PropertyKey::Index(index) => { + if let Ok(integer) = i32::try_from(index) { + Value::integer(integer) + } else { + Value::number(index) + } + } + } + } +} + +impl From for PropertyKey { + fn from(value: u8) -> Self { + PropertyKey::Index(value.into()) + } +} + +impl From for PropertyKey { + fn from(value: u16) -> Self { + PropertyKey::Index(value.into()) + } +} + +impl From for PropertyKey { + fn from(value: u32) -> Self { + PropertyKey::Index(value) + } +} + +impl From for PropertyKey { + fn from(value: usize) -> Self { + if let Ok(index) = u32::try_from(value) { + PropertyKey::Index(index) + } else { + PropertyKey::String(RcString::from(value.to_string())) + } + } +} + +impl From for PropertyKey { + fn from(value: isize) -> Self { + if let Ok(index) = u32::try_from(value) { + PropertyKey::Index(index) + } else { + PropertyKey::String(RcString::from(value.to_string())) + } + } +} + +impl From for PropertyKey { + fn from(value: i32) -> Self { + if let Ok(index) = u32::try_from(value) { + PropertyKey::Index(index) + } else { + PropertyKey::String(RcString::from(value.to_string())) + } + } +} + +impl From for PropertyKey { + fn from(value: f64) -> Self { + use num_traits::cast::FromPrimitive; + if let Some(index) = u32::from_f64(value) { + return PropertyKey::Index(index); + } + + PropertyKey::String(ryu_js::Buffer::new().format(value).into()) + } +} + +impl PartialEq<&str> for PropertyKey { + fn eq(&self, other: &&str) -> bool { + match self { + PropertyKey::String(ref string) => string == other, + _ => false, } } } diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index f7b3fb9bc1f..25ccf148f8c 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -20,14 +20,26 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ - builtins::value::{RcString, RcSymbol, Value}, + builtins::{ + property::{Attribute, Property}, + value::{RcString, RcSymbol, Value}, + }, exec::Interpreter, BoaProfiler, Result, }; use gc::{Finalize, Trace}; #[derive(Debug, Finalize, Trace, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Symbol(Option, u32); +pub struct Symbol { + hash: u32, + description: Option, +} + +impl Symbol { + pub(crate) fn new(hash: u32, description: Option) -> Self { + Self { hash, description } + } +} impl Symbol { /// The name of the object. @@ -38,12 +50,12 @@ impl Symbol { /// Returns the `Symbol`s description. pub fn description(&self) -> Option<&str> { - self.0.as_deref() + self.description.as_deref() } /// Returns the `Symbol`s hash. pub fn hash(&self) -> u32 { - self.1 + self.hash } fn this_symbol_value(value: &Value, ctx: &mut Interpreter) -> Result { @@ -78,7 +90,7 @@ impl Symbol { _ => None, }; - Ok(Value::symbol(Symbol(description, ctx.generate_hash()))) + Ok(ctx.construct_symbol(description).into()) } /// `Symbol.prototype.toString()` @@ -103,37 +115,21 @@ impl Symbol { pub fn init(interpreter: &mut Interpreter) -> (&'static str, Value) { // Define the Well-Known Symbols // https://tc39.es/ecma262/#sec-well-known-symbols - let symbol_async_iterator = Symbol( - Some("Symbol.asyncIterator".into()), - interpreter.generate_hash(), - ); - let symbol_has_instance = Symbol( - Some("Symbol.hasInstance".into()), - interpreter.generate_hash(), - ); - let symbol_is_concat_spreadable = Symbol( - Some("Symbol.isConcatSpreadable".into()), - interpreter.generate_hash(), - ); - let symbol_iterator = Symbol(Some("Symbol.iterator".into()), interpreter.generate_hash()); - let symbol_match = Symbol(Some("Symbol.match".into()), interpreter.generate_hash()); - let symbol_match_all = Symbol(Some("Symbol.matchAll".into()), interpreter.generate_hash()); - let symbol_replace = Symbol(Some("Symbol.replace".into()), interpreter.generate_hash()); - let symbol_search = Symbol(Some("Symbol.search".into()), interpreter.generate_hash()); - let symbol_species = Symbol(Some("Symbol.species".into()), interpreter.generate_hash()); - let symbol_split = Symbol(Some("Symbol.split".into()), interpreter.generate_hash()); - let symbol_to_primitive = Symbol( - Some("Symbol.toPrimitive".into()), - interpreter.generate_hash(), - ); - let symbol_to_string_tag = Symbol( - Some("Symbol.toStringTag".into()), - interpreter.generate_hash(), - ); - let symbol_unscopables = Symbol( - Some("Symbol.unscopables".into()), - interpreter.generate_hash(), - ); + let symbol_async_iterator = + interpreter.construct_symbol(Some("Symbol.asyncIterator".into())); + let symbol_has_instance = interpreter.construct_symbol(Some("Symbol.hasInstance".into())); + let symbol_is_concat_spreadable = + interpreter.construct_symbol(Some("Symbol.isConcatSpreadable".into())); + let symbol_iterator = interpreter.construct_symbol(Some("Symbol.iterator".into())); + let symbol_match = interpreter.construct_symbol(Some("Symbol.match".into())); + let symbol_match_all = interpreter.construct_symbol(Some("Symbol.matchAll".into())); + let symbol_replace = interpreter.construct_symbol(Some("Symbol.replace".into())); + let symbol_search = interpreter.construct_symbol(Some("Symbol.search".into())); + let symbol_species = interpreter.construct_symbol(Some("Symbol.species".into())); + let symbol_split = interpreter.construct_symbol(Some("Symbol.split".into())); + let symbol_to_primitive = interpreter.construct_symbol(Some("Symbol.toPrimitive".into())); + let symbol_to_string_tag = interpreter.construct_symbol(Some("Symbol.toStringTag".into())); + let symbol_unscopables = interpreter.construct_symbol(Some("Symbol.unscopables".into())); let global = interpreter.global(); let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); @@ -153,22 +149,62 @@ impl Symbol { true, ); - symbol_object.set_field("asyncIterator", Value::symbol(symbol_async_iterator)); - symbol_object.set_field("hasInstance", Value::symbol(symbol_has_instance)); - symbol_object.set_field( - "isConcatSpreadable", - Value::symbol(symbol_is_concat_spreadable), - ); - symbol_object.set_field("iterator", Value::symbol(symbol_iterator)); - symbol_object.set_field("match", Value::symbol(symbol_match)); - symbol_object.set_field("matchAll", Value::symbol(symbol_match_all)); - symbol_object.set_field("replace", Value::symbol(symbol_replace)); - symbol_object.set_field("search", Value::symbol(symbol_search)); - symbol_object.set_field("species", Value::symbol(symbol_species)); - symbol_object.set_field("split", Value::symbol(symbol_split)); - symbol_object.set_field("toPrimitive", Value::symbol(symbol_to_primitive)); - symbol_object.set_field("toStringTag", Value::symbol(symbol_to_string_tag)); - symbol_object.set_field("unscopables", Value::symbol(symbol_unscopables)); + { + let mut symbol_object = symbol_object.as_object_mut().unwrap(); + let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; + symbol_object.insert_property( + "asyncIterator", + Property::data_descriptor(symbol_async_iterator.into(), attribute), + ); + symbol_object.insert_property( + "hasInstance", + Property::data_descriptor(symbol_has_instance.into(), attribute), + ); + symbol_object.insert_property( + "isConcatSpreadable", + Property::data_descriptor(symbol_is_concat_spreadable.into(), attribute), + ); + symbol_object.insert_property( + "iterator", + Property::data_descriptor(symbol_iterator.into(), attribute), + ); + symbol_object.insert_property( + "match", + Property::data_descriptor(symbol_match.into(), attribute), + ); + symbol_object.insert_property( + "matchAll", + Property::data_descriptor(symbol_match_all.into(), attribute), + ); + symbol_object.insert_property( + "replace", + Property::data_descriptor(symbol_replace.into(), attribute), + ); + symbol_object.insert_property( + "search", + Property::data_descriptor(symbol_search.into(), attribute), + ); + symbol_object.insert_property( + "species", + Property::data_descriptor(symbol_species.into(), attribute), + ); + symbol_object.insert_property( + "split", + Property::data_descriptor(symbol_split.into(), attribute), + ); + symbol_object.insert_property( + "toPrimitive", + Property::data_descriptor(symbol_to_primitive.into(), attribute), + ); + symbol_object.insert_property( + "toStringTag", + Property::data_descriptor(symbol_to_string_tag.into(), attribute), + ); + symbol_object.insert_property( + "unscopables", + Property::data_descriptor(symbol_unscopables.into(), attribute), + ); + } (Self::NAME, symbol_object) } diff --git a/boa/src/builtins/symbol/tests.rs b/boa/src/builtins/symbol/tests.rs index a85b351abac..3e7f293ec90 100644 --- a/boa/src/builtins/symbol/tests.rs +++ b/boa/src/builtins/symbol/tests.rs @@ -23,3 +23,20 @@ fn print_symbol_expect_description() { let sym = forward_val(&mut engine, "sym.toString()").unwrap(); assert_eq!(sym.display().to_string(), "\"Symbol(Hello)\""); } + +#[test] +fn symbol_access() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + let init = r#" + var x = {}; + var sym1 = Symbol("Hello"); + var sym2 = Symbol("Hello"); + x[sym1] = 10; + x[sym2] = 20; + "#; + forward_val(&mut engine, init).unwrap(); + assert_eq!(forward(&mut engine, "x[sym1]"), "10"); + assert_eq!(forward(&mut engine, "x[sym2]"), "20"); + assert_eq!(forward(&mut engine, "x['Symbol(Hello)']"), "undefined"); +} diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index ea13b090e10..5e47b216847 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -127,10 +127,7 @@ where fn from(value: &[T]) -> Self { let mut array = Object::default(); for (i, item) in value.iter().enumerate() { - array.properties_mut().insert( - RcString::from(i.to_string()), - Property::default().value(item.clone().into()), - ); + array.insert_property(i, Property::default().value(item.clone().into())); } Self::from(array) } @@ -143,10 +140,7 @@ where fn from(value: Vec) -> Self { let mut array = Object::default(); for (i, item) in value.into_iter().enumerate() { - array.properties_mut().insert( - RcString::from(i.to_string()), - Property::default().value(item.into()), - ); + array.insert_property(i, Property::default().value(item.into())); } Value::from(array) } diff --git a/boa/src/builtins/value/display.rs b/boa/src/builtins/value/display.rs index 01b4bc54454..a4509be0aad 100644 --- a/boa/src/builtins/value/display.rs +++ b/boa/src/builtins/value/display.rs @@ -47,7 +47,7 @@ macro_rules! print_obj_value { } }; (props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => { - print_obj_value!(impl properties, $obj, |(key, val)| { + print_obj_value!(impl $obj, |(key, val)| { let v = &val .value .as_ref() @@ -64,10 +64,9 @@ macro_rules! print_obj_value { // A private overload of the macro // DO NOT use directly - (impl $field:ident, $v:expr, $f:expr) => { + (impl $v:expr, $f:expr) => { $v .borrow() - .$field() .iter() .map($f) .collect::>() @@ -94,9 +93,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children: ObjectData::Array => { let len = v .borrow() - .properties() - .get("length") - .expect("Could not get Array's length property") + .get_own_property(&PropertyKey::from("length")) .value .clone() .expect("Could not borrow value") @@ -114,9 +111,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children: // which are part of the Array log_string_from( &v.borrow() - .properties() - .get(i.to_string().as_str()) - .unwrap() + .get_own_property(&i.into()) .value .clone() .expect("Could not borrow value"), @@ -135,9 +130,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children: ObjectData::Map(ref map) => { let size = v .borrow() - .properties() - .get("size") - .unwrap() + .get_own_property(&PropertyKey::from("size")) .value .clone() .expect("Could not borrow value") diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 7ccf46469b0..21811146a88 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -9,7 +9,7 @@ use super::number::{f64_to_int32, f64_to_uint32}; use crate::builtins::{ object::{GcObject, Object, ObjectData, PROTOTYPE}, property::{Attribute, Property, PropertyKey}, - BigInt, Number, Symbol, + BigInt, Number, }; use crate::exec::Interpreter; use crate::{BoaProfiler, Result}; @@ -145,8 +145,8 @@ impl Value { /// Creates a new symbol value. #[inline] - pub(crate) fn symbol(symbol: Symbol) -> Self { - Self::Symbol(RcSymbol::from(symbol)) + pub fn symbol(symbol: RcSymbol) -> Self { + Self::Symbol(symbol) } /// Returns a new empty object @@ -239,7 +239,7 @@ impl Value { Self::Object(ref obj) => { if obj.borrow().is_array() { let mut arr: Vec = Vec::new(); - for k in obj.borrow().properties().keys() { + for k in obj.borrow().keys() { if k != "length" { let value = self.get_field(k.to_string()); if value.is_undefined() || value.is_function() || value.is_symbol() { @@ -252,7 +252,7 @@ impl Value { Ok(JSONValue::Array(arr)) } else { let mut new_obj = Map::new(); - for k in obj.borrow().properties().keys() { + for k in obj.borrow().keys() { let key = k.clone(); let value = self.get_field(k.to_string()); if !value.is_undefined() && !value.is_function() && !value.is_symbol() { @@ -446,30 +446,33 @@ impl Value { /// 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. - pub fn remove_property(&self, field: &str) -> bool { + pub fn remove_property(&self, key: Key) -> bool + where + Key: Into, + { self.as_object_mut() - .and_then(|mut x| x.properties_mut().remove(field)) + .map(|mut x| x.remove_property(&key.into())) .is_some() } /// Resolve the property in the object. /// /// A copy of the Property is returned. - pub fn get_property(&self, field: &str) -> Option { + pub fn get_property(&self, key: Key) -> Option + where + Key: Into, + { + let key = key.into(); let _timer = BoaProfiler::global().start_event("Value::get_property", "value"); - // Spidermonkey has its own GetLengthProperty: https://searchfox.org/mozilla-central/source/js/src/vm/Interpreter-inl.h#154 - // This is only for primitive strings, String() objects have their lengths calculated in string.rs match self { - Self::Undefined => None, - Self::String(ref s) if field == "length" => { - Some(Property::default().value(Value::from(s.chars().count()))) - } Self::Object(ref object) => { let object = object.borrow(); - match object.properties().get(field) { - Some(value) => Some(value.clone()), - None => object.prototype().get_property(field), + let property = object.get_own_property(&key); + if !property.is_none() { + return Some(property); } + + object.prototype().get_property(key) } _ => None, } @@ -483,82 +486,74 @@ impl Value { let _timer = BoaProfiler::global().start_event("Value::update_property", "value"); if let Some(ref mut object) = self.as_object_mut() { - // Use value, or walk up the prototype chain - if let Some(property) = object.properties_mut().get_mut(field) { - *property = new_property; - } + object.insert_property(field, new_property); } } /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist /// get_field recieves a Property from get_prop(). It should then return the [[Get]] result value if that's set, otherwise fall back to [[Value]] /// TODO: this function should use the get Value if its set - pub fn get_field(&self, field: F) -> Self + pub fn get_field(&self, key: K) -> Self where - F: Into, + K: Into, { let _timer = BoaProfiler::global().start_event("Value::get_field", "value"); - match field.into() { - // Our field will either be a String or a Symbol - Self::String(ref s) => { - match self.get_property(s) { - Some(prop) => { - // If the Property has [[Get]] set to a function, we should run that and return the Value - let prop_getter = match prop.get { - Some(_) => None, - None => None, - }; - - // If the getter is populated, use that. If not use [[Value]] instead - if let Some(val) = prop_getter { - val - } else { - let val = prop - .value - .as_ref() - .expect("Could not get property as reference"); - val.clone() - } - } - None => Value::undefined(), + let key = key.into(); + match self.get_property(key) { + Some(prop) => { + // If the Property has [[Get]] set to a function, we should run that and return the Value + let prop_getter = match prop.get { + Some(_) => None, + None => None, + }; + + // If the getter is populated, use that. If not use [[Value]] instead + if let Some(val) = prop_getter { + val + } else { + let val = prop + .value + .as_ref() + .expect("Could not get property as reference"); + val.clone() } } - Self::Symbol(_) => unimplemented!(), - _ => Value::undefined(), + None => Value::undefined(), } } /// Check to see if the Value has the field, mainly used by environment records. #[inline] - pub fn has_field(&self, field: &str) -> bool { + pub fn has_field(&self, key: K) -> bool + where + K: Into, + { let _timer = BoaProfiler::global().start_event("Value::has_field", "value"); - self.get_property(field).is_some() + self.as_object() + .map(|object| object.has_property(&key.into())) + .unwrap_or(false) } /// Set the field in the value #[inline] - pub fn set_field(&self, field: F, value: V) -> Value + pub fn set_field(&self, key: K, value: V) -> Value where - F: Into, + K: Into, V: Into, { - let field = field.into(); + let key = key.into(); let value = value.into(); let _timer = BoaProfiler::global().start_event("Value::set_field", "value"); if let Self::Object(ref obj) = *self { - if let PropertyKey::String(ref string) = field { + if let PropertyKey::Index(index) = key { if obj.borrow().is_array() { - if let Ok(num) = string.parse::() { - if num > 0 { - let len = self.get_field("length").as_number().unwrap() as i32; - if len < (num + 1) as i32 { - self.set_field("length", num + 1); - } - } + let len = self.get_field("length").as_number().unwrap() as u32; + if len < index + 1 { + self.set_field("length", index + 1); } } } - obj.borrow_mut().set(&field, value.clone()); + obj.borrow_mut().set(key, value.clone()); } value } @@ -572,14 +567,12 @@ impl Value { } /// Set the property in the value. - pub fn set_property(&self, field: S, property: Property) -> Property + pub fn set_property(&self, key: K, property: Property) -> Property where - S: Into, + K: Into, { if let Some(mut object) = self.as_object_mut() { - object - .properties_mut() - .insert(field.into(), property.clone()); + object.insert_property(key.into(), property.clone()); } property } diff --git a/boa/src/environment/global_environment_record.rs b/boa/src/environment/global_environment_record.rs index 312bb670ae3..24c6ce7e7db 100644 --- a/boa/src/environment/global_environment_record.rs +++ b/boa/src/environment/global_environment_record.rs @@ -56,7 +56,7 @@ impl GlobalEnvironmentRecord { pub fn create_global_var_binding(&mut self, name: String, deletion: bool) { let obj_rec = &mut self.object_record; let global_object = &obj_rec.bindings; - let has_property = global_object.has_field(&name); + let has_property = global_object.has_field(name.as_str()); let extensible = global_object.is_extensible(); if !has_property && extensible { obj_rec.create_mutable_binding(name.clone(), deletion); @@ -71,7 +71,7 @@ impl GlobalEnvironmentRecord { pub fn create_global_function_binding(&mut self, name: &str, value: Value, deletion: bool) { let global_object = &mut self.object_record.bindings; - let existing_prop = global_object.get_property(&name); + let existing_prop = global_object.get_property(name); if let Some(prop) = existing_prop { if prop.value.is_none() || prop.configurable_or(false) { let mut property = diff --git a/boa/src/exec/call/mod.rs b/boa/src/exec/call/mod.rs index 60d70ea1e5d..ad4d23f1557 100644 --- a/boa/src/exec/call/mod.rs +++ b/boa/src/exec/call/mod.rs @@ -19,8 +19,10 @@ impl Executable for Call { Node::GetField(ref get_field) => { let obj = get_field.obj().run(interpreter)?; let field = get_field.field().run(interpreter)?; - // FIXME: This should not call `.display()` - (obj.clone(), obj.get_field(field.display().to_string())) + ( + obj.clone(), + obj.get_field(field.to_property_key(interpreter)?), + ) } _ => ( interpreter.realm().global_obj.clone(), diff --git a/boa/src/exec/field/mod.rs b/boa/src/exec/field/mod.rs index f932b7a15c5..7b854a789bb 100644 --- a/boa/src/exec/field/mod.rs +++ b/boa/src/exec/field/mod.rs @@ -24,6 +24,6 @@ impl Executable for GetField { } let field = self.field().run(interpreter)?; - Ok(obj.get_field(field.to_string(interpreter)?)) + Ok(obj.get_field(field.to_property_key(interpreter)?)) } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 882ed51741a..9f3f5fff361 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -28,8 +28,8 @@ use crate::{ function::{Function, FunctionFlags, NativeFunction}, object::{GcObject, Object, ObjectData, PROTOTYPE}, property::PropertyKey, - value::{PreferredType, Type, Value}, - Console, + value::{PreferredType, RcString, RcSymbol, Type, Value}, + Console, Symbol, }, realm::Realm, syntax::ast::{ @@ -169,9 +169,9 @@ impl Interpreter { Function::BuiltIn(body.into(), FunctionFlags::CALLABLE), function_prototype, ); - function.set(&PROTOTYPE.into(), proto); - function.set(&"length".into(), length.into()); - function.set(&"name".into(), name.into()); + function.set(PROTOTYPE.into(), proto); + function.set("length".into(), length.into()); + function.set("name".into(), name.into()); Ok(GcObject::new(function)) } @@ -358,6 +358,19 @@ impl Interpreter { pub(crate) fn console_mut(&mut self) -> &mut Console { &mut self.console } + + /// Construct a new `Symbol` with an optional description. + #[inline] + pub fn construct_symbol(&mut self, description: Option) -> RcSymbol { + RcSymbol::from(Symbol::new(self.generate_hash(), description)) + } + + /// Construct an empty object. + #[inline] + pub fn construct_object(&self) -> GcObject { + let object_prototype = self.global().get_field("Object").get_field(PROTOTYPE); + GcObject::new(Object::create(object_prototype)) + } } impl Executable for Node { diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index c44289d5479..4d3e2418964 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -628,7 +628,7 @@ fn unary_delete() { const c = delete a.c + ''; a.b + c "#; - assert_eq!(&exec(delete_not_existing_prop), "\"5false\""); + assert_eq!(&exec(delete_not_existing_prop), "\"5true\""); let delete_field = r#" const a = { b: 5 };