Browse Source

Index `PropertyKey`, `Object` iterators and symbol support (#603)

* Index `PropertyKey`, `Object` iterators and `Symbol` indexing support.

* PR feedback
pull/650/head
HalidOdat 4 years ago committed by GitHub
parent
commit
b42dd4cd8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 109
      boa/src/builtins/array/mod.rs
  2. 9
      boa/src/builtins/function/mod.rs
  3. 35
      boa/src/builtins/json/mod.rs
  4. 111
      boa/src/builtins/object/internal_methods.rs
  5. 421
      boa/src/builtins/object/iter.rs
  6. 48
      boa/src/builtins/object/mod.rs
  7. 111
      boa/src/builtins/property/mod.rs
  8. 136
      boa/src/builtins/symbol/mod.rs
  9. 17
      boa/src/builtins/symbol/tests.rs
  10. 10
      boa/src/builtins/value/conversions.rs
  11. 17
      boa/src/builtins/value/display.rs
  12. 93
      boa/src/builtins/value/mod.rs
  13. 4
      boa/src/environment/global_environment_record.rs
  14. 6
      boa/src/exec/call/mod.rs
  15. 2
      boa/src/exec/field/mod.rs
  16. 23
      boa/src/exec/mod.rs
  17. 2
      boa/src/exec/tests.rs

109
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<Value> = (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

9
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;
}

35
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<Value> {
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()?
{

111
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,
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
};
}
return 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]]
/// <https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver>
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<K>(&mut self, key: K, desc: Property) -> bool
where
K: Into<PropertyKey>,
{
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,13 +259,16 @@ 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 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();
@ -280,23 +281,6 @@ impl Object {
d
})
}
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
}),
}
}
/// `Object.setPropertyOf(obj, prototype)`
///
@ -352,17 +336,29 @@ impl Object {
/// Helper function for property insertion.
#[inline]
pub(crate) fn insert_property<N>(&mut self, name: N, p: Property)
pub(crate) fn insert_property<K>(&mut self, key: K, property: Property) -> Option<Property>
where
N: Into<RcString>,
K: Into<PropertyKey>,
{
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<Property> {
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<N>(&mut self, name: N, value: Value) -> Option<Property>
pub(crate) fn insert_field<K>(&mut self, key: K, value: Value) -> Option<Property>
where
N: Into<RcString>,
K: Into<PropertyKey>,
{
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())
}
}

421
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<Self::Item> {
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<Self::Item> {
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<Self::Item> {
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::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
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::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
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::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
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::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
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::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
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::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
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::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
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::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
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::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl ExactSizeIterator for StringPropertyValues<'_> {
#[inline]
fn len(&self) -> usize {
self.0.len()
}
}
impl FusedIterator for StringPropertyValues<'_> {}

48
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<u32, Property>,
/// Properties
properties: FxHashMap<RcString, Property>,
string_properties: FxHashMap<RcString, Property>,
/// Symbol Properties
symbol_properties: FxHashMap<u32, Property>,
symbol_properties: FxHashMap<RcSymbol, Property>,
/// 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<RcString, Property> {
&self.properties
}
#[inline]
pub fn properties_mut(&mut self) -> &mut FxHashMap<RcString, Property> {
&mut self.properties
}
#[inline]
pub fn symbol_properties(&self) -> &FxHashMap<u32, Property> {
&self.symbol_properties
}
#[inline]
pub fn symbol_properties_mut(&mut self) -> &mut FxHashMap<u32, Property> {
&mut self.symbol_properties
}
pub fn prototype(&self) -> &Value {
&self.prototype
}

111
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,35 +291,52 @@ impl<'a> From<&'a Value> for Property {
pub enum PropertyKey {
String(RcString),
Symbol(RcSymbol),
Index(u32),
}
impl From<RcString> for PropertyKey {
#[inline]
fn from(string: RcString) -> PropertyKey {
if let Ok(index) = string.parse() {
PropertyKey::Index(index)
} else {
PropertyKey::String(string)
}
}
}
impl From<&str> for PropertyKey {
#[inline]
fn from(string: &str) -> PropertyKey {
if let Ok(index) = string.parse() {
PropertyKey::Index(index)
} else {
PropertyKey::String(string.into())
}
}
}
impl From<String> for PropertyKey {
#[inline]
fn from(string: String) -> PropertyKey {
if let Ok(index) = string.parse() {
PropertyKey::Index(index)
} else {
PropertyKey::String(string.into())
}
}
}
impl From<Box<str>> for PropertyKey {
#[inline]
fn from(string: Box<str>) -> PropertyKey {
if let Ok(index) = string.parse() {
PropertyKey::Index(index)
} else {
PropertyKey::String(string.into())
}
}
}
impl From<RcSymbol> for PropertyKey {
#[inline]
@ -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<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)
}
}
}
}
}
impl From<u8> for PropertyKey {
fn from(value: u8) -> Self {
PropertyKey::Index(value.into())
}
}
impl From<u16> for PropertyKey {
fn from(value: u16) -> Self {
PropertyKey::Index(value.into())
}
}
impl From<u32> for PropertyKey {
fn from(value: u32) -> Self {
PropertyKey::Index(value)
}
}
impl From<usize> 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<isize> 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<i32> 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<f64> 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,
}
}
}

136
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<RcString>, u32);
pub struct Symbol {
hash: u32,
description: Option<RcString>,
}
impl Symbol {
pub(crate) fn new(hash: u32, description: Option<RcString>) -> 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<RcSymbol> {
@ -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(
{
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",
Value::symbol(symbol_is_concat_spreadable),
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.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));
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)
}

17
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");
}

10
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<T>) -> 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)
}

17
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::<Vec<String>>()
@ -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")

93
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<JSONValue> = 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<Key>(&self, key: Key) -> bool
where
Key: Into<PropertyKey>,
{
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<Property> {
pub fn get_property<Key>(&self, key: Key) -> Option<Property>
where
Key: Into<PropertyKey>,
{
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,25 +486,20 @@ 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<F>(&self, field: F) -> Self
pub fn get_field<K>(&self, key: K) -> Self
where
F: Into<Value>,
K: Into<PropertyKey>,
{
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) {
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 {
@ -523,42 +521,39 @@ impl Value {
None => Value::undefined(),
}
}
Self::Symbol(_) => unimplemented!(),
_ => 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<K>(&self, key: K) -> bool
where
K: Into<PropertyKey>,
{
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<F, V>(&self, field: F, value: V) -> Value
pub fn set_field<K, V>(&self, key: K, value: V) -> Value
where
F: Into<PropertyKey>,
K: Into<PropertyKey>,
V: Into<Value>,
{
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::<usize>() {
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<S>(&self, field: S, property: Property) -> Property
pub fn set_property<K>(&self, key: K, property: Property) -> Property
where
S: Into<RcString>,
K: Into<PropertyKey>,
{
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
}

4
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 =

6
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(),

2
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)?))
}
}

23
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::{
@ -173,9 +173,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))
}
@ -362,6 +362,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<RcString>) -> 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 {

2
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 };

Loading…
Cancel
Save