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. 133
      boa/src/builtins/object/internal_methods.rs
  5. 421
      boa/src/builtins/object/iter.rs
  6. 48
      boa/src/builtins/object/mod.rs
  7. 119
      boa/src/builtins/property/mod.rs
  8. 140
      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. 127
      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 // Wipe existing contents of the array object
let orig_length = array_obj.get_field("length").as_number().unwrap() as i32; let orig_length = array_obj.get_field("length").as_number().unwrap() as i32;
for n in 0..orig_length { for n in 0..orig_length {
array_obj_ptr.remove_property(&n.to_string()); array_obj_ptr.remove_property(n);
} }
// Create length // Create length
@ -82,7 +82,7 @@ impl Array {
array_obj_ptr.set_property("length".to_string(), length); array_obj_ptr.set_property("length".to_string(), length);
for (n, value) in array_contents.iter().enumerate() { 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) Ok(array_obj_ptr)
} }
@ -94,7 +94,7 @@ impl Array {
for (n, value) in add_values.iter().enumerate() { for (n, value) in add_values.iter().enumerate() {
let new_index = orig_length.wrapping_add(n as i32); 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( array_ptr.set_field(
@ -127,7 +127,7 @@ impl Array {
length = args[0].as_number().unwrap() as i32; 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`. // 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 { 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() => { 1 if args[0].is_double() => {
@ -135,7 +135,7 @@ impl Array {
} }
_ => { _ => {
for (n, value) in args.iter().enumerate() { 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; let this_length = this.get_field("length").as_number().unwrap() as i32;
for n in 0..this_length { 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 { for concat_array in args {
let concat_length = concat_array.get_field("length").as_number().unwrap() as i32; let concat_length = concat_array.get_field("length").as_number().unwrap() as i32;
for n in 0..concat_length { 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_index = curr_length.wrapping_sub(1);
let pop_value: Value = this.get_field(pop_index.to_string()); 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)); this.set_field("length", Value::from(pop_index));
Ok(pop_value) Ok(pop_value)
} }
@ -275,7 +275,7 @@ impl Array {
let length = this.get_field("length").as_number().unwrap() as i32; let length = this.get_field("length").as_number().unwrap() as i32;
for i in 0..length { 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 arguments = [element, Value::from(i), this.clone()];
interpreter.call(callback_arg, &this_arg, &arguments)?; interpreter.call(callback_arg, &this_arg, &arguments)?;
@ -309,7 +309,7 @@ impl Array {
let mut elem_strs = Vec::new(); let mut elem_strs = Vec::new();
let length = this.get_field("length").as_number().unwrap() as i32; let length = this.get_field("length").as_number().unwrap() as i32;
for n in 0..length { 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); elem_strs.push(elem_str);
} }
@ -377,21 +377,21 @@ impl Array {
for lower in 0..middle { for lower in 0..middle {
let upper = len.wrapping_sub(lower).wrapping_sub(1); let upper = len.wrapping_sub(lower).wrapping_sub(1);
let upper_exists = this.has_field(&upper.to_string()); let upper_exists = this.has_field(upper);
let lower_exists = this.has_field(&lower.to_string()); let lower_exists = this.has_field(lower);
let upper_value = this.get_field(upper.to_string()); let upper_value = this.get_field(upper);
let lower_value = this.get_field(lower.to_string()); let lower_value = this.get_field(lower);
if upper_exists && lower_exists { if upper_exists && lower_exists {
this.set_field(upper.to_string(), lower_value); this.set_field(upper, lower_value);
this.set_field(lower.to_string(), upper_value); this.set_field(lower, upper_value);
} else if upper_exists { } else if upper_exists {
this.set_field(lower.to_string(), upper_value); this.set_field(lower, upper_value);
this.remove_property(&upper.to_string()); this.remove_property(upper);
} else if lower_exists { } else if lower_exists {
this.set_field(upper.to_string(), lower_value); this.set_field(upper, lower_value);
this.remove_property(&lower.to_string()); this.remove_property(lower);
} }
} }
@ -413,26 +413,25 @@ impl Array {
if len == 0 { if len == 0 {
this.set_field("length", 0); this.set_field("length", 0);
// Since length is 0, this will be an Undefined value return Ok(Value::undefined());
return Ok(this.get_field(0.to_string()));
} }
let first: Value = this.get_field(0.to_string()); let first: Value = this.get_field(0);
for k in 1..len { for k in 1..len {
let from = k.to_string(); let from = k;
let to = (k.wrapping_sub(1)).to_string(); let to = k.wrapping_sub(1);
let from_value = this.get_field(from); let from_value = this.get_field(from);
if from_value.is_undefined() { if from_value.is_undefined() {
this.remove_property(&to); this.remove_property(to);
} else { } else {
this.set_field(to, from_value); this.set_field(to, from_value);
} }
} }
let final_index = len.wrapping_sub(1); 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)); this.set_field("length", Value::from(final_index));
Ok(first) Ok(first)
@ -457,19 +456,19 @@ impl Array {
if arg_c > 0 { if arg_c > 0 {
for k in (1..=len).rev() { for k in (1..=len).rev() {
let from = (k.wrapping_sub(1)).to_string(); let from = k.wrapping_sub(1);
let to = (k.wrapping_add(arg_c).wrapping_sub(1)).to_string(); let to = k.wrapping_add(arg_c).wrapping_sub(1);
let from_value = this.get_field(from); let from_value = this.get_field(from);
if from_value.is_undefined() { if from_value.is_undefined() {
this.remove_property(&to); this.remove_property(to);
} else { } else {
this.set_field(to, from_value); this.set_field(to, from_value);
} }
} }
for j in 0..arg_c { for j in 0..arg_c {
this.set_field( this.set_field(
j.to_string(), j,
args.get(j as usize) args.get(j as usize)
.expect("Could not get argument") .expect("Could not get argument")
.clone(), .clone(),
@ -515,7 +514,7 @@ impl Array {
let max_len = this.get_field("length").as_number().unwrap() as i32; let max_len = this.get_field("length").as_number().unwrap() as i32;
let mut len = max_len; let mut len = max_len;
while i < 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 arguments = [element, Value::from(i), this.clone()];
let result = interpreter.call(callback, &this_arg, &arguments)?; let result = interpreter.call(callback, &this_arg, &arguments)?;
if !result.to_boolean() { if !result.to_boolean() {
@ -561,7 +560,7 @@ impl Array {
let values: Vec<Value> = (0..length) let values: Vec<Value> = (0..length)
.map(|idx| { .map(|idx| {
let element = this.get_field(idx.to_string()); let element = this.get_field(idx);
let args = [element, Value::from(idx), new.clone()]; let args = [element, Value::from(idx), new.clone()];
interpreter interpreter
@ -615,7 +614,7 @@ impl Array {
}; };
while idx < len { 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) { if check_element.strict_equals(&search_element) {
return Ok(Value::from(idx)); return Ok(Value::from(idx));
@ -672,7 +671,7 @@ impl Array {
}; };
while idx >= 0 { 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) { if check_element.strict_equals(&search_element) {
return Ok(Value::from(idx)); return Ok(Value::from(idx));
@ -710,7 +709,7 @@ impl Array {
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined);
let len = this.get_field("length").as_number().unwrap() as i32; let len = this.get_field("length").as_number().unwrap() as i32;
for i in 0..len { 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 arguments = [element.clone(), Value::from(i), this.clone()];
let result = interpreter.call(callback, &this_arg, &arguments)?; let result = interpreter.call(callback, &this_arg, &arguments)?;
if result.to_boolean() { if result.to_boolean() {
@ -750,7 +749,7 @@ impl Array {
let length = this.get_field("length").as_number().unwrap() as i32; let length = this.get_field("length").as_number().unwrap() as i32;
for i in 0..length { 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 arguments = [element, Value::from(i), this.clone()];
let result = interpreter.call(predicate_arg, &this_arg, &arguments)?; let result = interpreter.call(predicate_arg, &this_arg, &arguments)?;
@ -798,7 +797,7 @@ impl Array {
}; };
for i in start..fin { for i in start..fin {
this.set_field(i.to_string(), value.clone()); this.set_field(i, value.clone());
} }
Ok(this.clone()) Ok(this.clone())
@ -824,7 +823,7 @@ impl Array {
let length = this.get_field("length").as_number().unwrap() as i32; let length = this.get_field("length").as_number().unwrap() as i32;
for idx in 0..length { 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) { if same_value_zero(&check_element, &search_element) {
return Ok(Value::from(true)); return Ok(Value::from(true));
@ -879,7 +878,7 @@ impl Array {
let span = max(to.wrapping_sub(from), 0); let span = max(to.wrapping_sub(from), 0);
let mut new_array_len: i32 = 0; let mut new_array_len: i32 = 0;
for i in from..from.wrapping_add(span) { 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_len = new_array_len.wrapping_add(1);
} }
new_array.set_field("length", Value::from(new_array_len)); new_array.set_field("length", Value::from(new_array_len));
@ -917,7 +916,7 @@ impl Array {
let values = (0..length) let values = (0..length)
.filter_map(|idx| { .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()]; 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 max_len = this.get_field("length").as_number().unwrap() as i32;
let mut len = max_len; let mut len = max_len;
while i < 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 arguments = [element, Value::from(i), this.clone()];
let result = interpreter.call(callback, &this_arg, &arguments)?; let result = interpreter.call(callback, &this_arg, &arguments)?;
if result.to_boolean() { if result.to_boolean() {
@ -1018,7 +1017,7 @@ impl Array {
let mut accumulator = if initial_value.is_undefined() { let mut accumulator = if initial_value.is_undefined() {
let mut k_present = false; let mut k_present = false;
while k < length { while k < length {
if this.has_field(&k.to_string()) { if this.has_field(k) {
k_present = true; k_present = true;
break; break;
} }
@ -1029,20 +1028,15 @@ impl Array {
"Reduce was called on an empty array and with no initial value", "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; k += 1;
result result
} else { } else {
initial_value initial_value
}; };
while k < length { while k < length {
if this.has_field(&k.to_string()) { if this.has_field(k) {
let arguments = [ let arguments = [accumulator, this.get_field(k), Value::from(k), this.clone()];
accumulator,
this.get_field(k.to_string()),
Value::from(k),
this.clone(),
];
accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?; accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?;
/* We keep track of possibly shortened length in order to prevent unnecessary iteration. /* 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 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 accumulator = if initial_value.is_undefined() {
let mut k_present = false; let mut k_present = false;
loop { loop {
if this.has_field(&k.to_string()) { if this.has_field(k) {
k_present = true; k_present = true;
break; break;
} }
@ -1106,20 +1100,15 @@ impl Array {
"reduceRight was called on an empty array and with no initial value", "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; k -= 1;
result result
} else { } else {
initial_value initial_value
}; };
loop { loop {
if this.has_field(&k.to_string()) { if this.has_field(k) {
let arguments = [ let arguments = [accumulator, this.get_field(k), Value::from(k), this.clone()];
accumulator,
this.get_field(k.to_string()),
Value::from(k),
this.clone(),
];
accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?; accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?;
/* We keep track of possibly shortened length in order to prevent unnecessary iteration. /* 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 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::{ use crate::{
builtins::{ builtins::{
object::{Object, ObjectData, PROTOTYPE}, object::{Object, ObjectData, PROTOTYPE},
property::{Attribute, Property, PropertyKey}, property::{Attribute, Property},
value::{RcString, Value}, value::Value,
Array, Array,
}, },
environment::lexical_environment::Environment, 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, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
); );
// Define length as a property // 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; let mut index: usize = 0;
while index < len { while index < len {
let val = arguments_list.get(index).expect("Could not get argument"); 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, Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
); );
obj.properties_mut() obj.insert_property(index, prop);
.insert(RcString::from(index.to_string()), prop);
index += 1; index += 1;
} }

35
boa/src/builtins/json/mod.rs

@ -13,8 +13,15 @@
//! [json]: https://www.json.org/json-en.html //! [json]: https://www.json.org/json-en.html
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON //! [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::{
use crate::{exec::Interpreter, BoaProfiler, Result}; builtins::{
function::make_builtin_fn,
property::{Property, PropertyKey},
value::Value,
},
exec::Interpreter,
BoaProfiler, Result,
};
use serde_json::{self, Value as JSONValue}; use serde_json::{self, Value as JSONValue};
#[cfg(test)] #[cfg(test)]
@ -53,7 +60,7 @@ impl Json {
Some(reviver) if reviver.is_function() => { Some(reviver) if reviver.is_function() => {
let mut holder = Value::new_object(None); let mut holder = Value::new_object(None);
holder.set_field("", j); holder.set_field("", j);
Self::walk(reviver, ctx, &mut holder, Value::from("")) Self::walk(reviver, ctx, &mut holder, &PropertyKey::from(""))
} }
_ => Ok(j), _ => Ok(j),
} }
@ -72,26 +79,26 @@ impl Json {
reviver: &Value, reviver: &Value,
ctx: &mut Interpreter, ctx: &mut Interpreter,
holder: &mut Value, holder: &mut Value,
key: Value, key: &PropertyKey,
) -> Result<Value> { ) -> Result<Value> {
let mut value = holder.get_field(key.clone()); let mut value = holder.get_field(key.clone());
let obj = value.as_object().as_deref().cloned(); let obj = value.as_object().as_deref().cloned();
if let Some(obj) = obj { if let Some(obj) = obj {
for key in obj.properties().keys() { for key in obj.keys() {
let v = Self::walk(reviver, ctx, &mut value, Value::from(key.as_str())); let v = Self::walk(reviver, ctx, &mut value, &key);
match v { match v {
Ok(v) if !v.is_undefined() => { Ok(v) if !v.is_undefined() => {
value.set_field(key.as_str(), v); value.set_field(key.clone(), v);
} }
Ok(_) => { Ok(_) => {
value.remove_property(key.as_str()); value.remove_property(key.clone());
} }
Err(_v) => {} Err(_v) => {}
} }
} }
} }
ctx.call(reviver, holder, &[key, value]) ctx.call(reviver, holder, &[key.into(), value])
} }
/// `JSON.stringify( value[, replacer[, space]] )` /// `JSON.stringify( value[, replacer[, space]] )`
@ -132,7 +139,6 @@ impl Json {
.map(|obj| { .map(|obj| {
let object_to_return = Value::new_object(None); let object_to_return = Value::new_object(None);
for (key, val) in obj for (key, val) in obj
.properties()
.iter() .iter()
.filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value))) .filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value)))
{ {
@ -150,18 +156,17 @@ impl Json {
}) })
.ok_or_else(Value::undefined)? .ok_or_else(Value::undefined)?
} else if replacer_as_object.is_array() { } else if replacer_as_object.is_array() {
let mut obj_to_return = let mut obj_to_return = serde_json::Map::new();
serde_json::Map::with_capacity(replacer_as_object.properties().len() - 1); let fields = replacer_as_object.keys().filter_map(|key| {
let fields = replacer_as_object.properties().keys().filter_map(|key| {
if key == "length" { if key == "length" {
None None
} else { } else {
Some(replacer.get_field(key.to_owned())) Some(replacer.get_field(key))
} }
}); });
for field in fields { for field in fields {
if let Some(value) = object 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))) .and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx)))
.transpose()? .transpose()?
{ {

133
boa/src/builtins/object/internal_methods.rs

@ -8,7 +8,7 @@
use crate::builtins::{ use crate::builtins::{
object::Object, object::Object,
property::{Attribute, Property, PropertyKey}, property::{Attribute, Property, PropertyKey},
value::{same_value, RcString, Value}, value::{same_value, Value},
}; };
use crate::BoaProfiler; use crate::BoaProfiler;
@ -21,19 +21,14 @@ impl Object {
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
pub fn has_property(&self, property_key: &PropertyKey) -> bool { pub fn has_property(&self, property_key: &PropertyKey) -> bool {
let prop = self.get_own_property(property_key); let prop = self.get_own_property(property_key);
if prop.value.is_none() { if prop.is_none() {
let parent: Value = self.get_prototype_of(); let parent = self.get_prototype_of();
if !parent.is_null() { return if let Value::Object(ref object) = parent {
// the parent value variant should be an object object.borrow().has_property(property_key)
// In the unlikely event it isn't return false } else {
return match parent { false
Value::Object(ref obj) => obj.borrow().has_property(property_key), };
_ => false,
};
}
return false;
} }
true true
} }
@ -72,7 +67,7 @@ impl Object {
return true; return true;
} }
if desc.configurable_or(false) { if desc.configurable_or(false) {
self.remove_property(&property_key.to_string()); self.remove_property(&property_key);
return true; return true;
} }
@ -96,9 +91,7 @@ impl Object {
return Value::undefined(); return Value::undefined();
} }
let parent_obj = Object::from(&parent).expect("Failed to get object"); return parent.get_field(property_key.clone());
return parent_obj.get(property_key);
} }
if desc.is_data_descriptor() { if desc.is_data_descriptor() {
@ -116,11 +109,11 @@ impl Object {
/// [[Set]] /// [[Set]]
/// <https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver> /// <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"); let _timer = BoaProfiler::global().start_event("Object::set", "object");
// Fetch property key // Fetch property key
let mut own_desc = self.get_own_property(property_key); let mut own_desc = self.get_own_property(&property_key);
// [2] // [2]
if own_desc.is_none() { if own_desc.is_none() {
let parent = self.get_prototype_of(); let parent = self.get_prototype_of();
@ -158,10 +151,14 @@ impl Object {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc /// [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 _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(); let extensible = self.is_extensible();
// https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor // https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
@ -171,7 +168,7 @@ impl Object {
return false; return false;
} }
self.insert_property(property_key, desc); self.insert_property(key, desc);
return true; return true;
} }
// If every field is absent we don't need to set anything // If every field is absent we don't need to set anything
@ -210,7 +207,8 @@ impl Object {
current.set = None; current.set = None;
} }
self.insert_property(property_key, current); self.insert_property(key, current);
return true;
// 7 // 7
} else if current.is_data_descriptor() && desc.is_data_descriptor() { } else if current.is_data_descriptor() && desc.is_data_descriptor() {
// a // a
@ -249,7 +247,7 @@ impl Object {
return true; return true;
} }
// 9 // 9
self.insert_property(property_key, desc); self.insert_property(key, desc);
true true
} }
@ -261,41 +259,27 @@ impl Object {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p /// [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"); let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object");
// Prop could either be a String or Symbol // Prop could either be a String or Symbol
match property_key { let property = match key {
PropertyKey::String(ref st) => { PropertyKey::Index(index) => self.indexed_properties.get(&index),
self.properties().get(st).map_or_else(Property::empty, |v| { PropertyKey::String(ref st) => self.string_properties.get(st),
let mut d = Property::empty(); PropertyKey::Symbol(ref symbol) => self.symbol_properties.get(symbol),
if v.is_data_descriptor() { };
d.value = v.value.clone(); property.map_or_else(Property::empty, |v| {
} else { let mut d = Property::empty();
debug_assert!(v.is_accessor_descriptor()); if v.is_data_descriptor() {
d.get = v.get.clone(); d.value = v.value.clone();
d.set = v.set.clone(); } else {
} debug_assert!(v.is_accessor_descriptor());
d.attribute = v.attribute; d.get = v.get.clone();
d d.set = v.set.clone();
})
} }
PropertyKey::Symbol(ref symbol) => self d.attribute = v.attribute;
.symbol_properties() d
.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)` /// `Object.setPropertyOf(obj, prototype)`
@ -352,17 +336,29 @@ impl Object {
/// Helper function for property insertion. /// Helper function for property insertion.
#[inline] #[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 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. /// Helper function for property removal.
#[inline] #[inline]
pub(crate) fn remove_property(&mut self, name: &str) { pub(crate) fn remove_property(&mut self, key: &PropertyKey) -> Option<Property> {
self.properties.remove(name); 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. /// 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 /// If a field was already in the object with the same name that a `Some` is returned
/// with that field, otherwise None is retuned. /// with that field, otherwise None is retuned.
#[inline] #[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 where
N: Into<RcString>, K: Into<PropertyKey>,
{ {
self.properties.insert( self.insert_property(
name.into(), key.into(),
Property::data_descriptor( Property::data_descriptor(
value, value,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, 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::{ builtins::{
function::Function, function::Function,
map::ordered_map::OrderedMap, map::ordered_map::OrderedMap,
property::Property, property::{Property, PropertyKey},
value::{RcBigInt, RcString, RcSymbol, Value}, value::{RcBigInt, RcString, RcSymbol, Value},
BigInt, Date, RegExp, BigInt, Date, RegExp,
}, },
@ -34,9 +34,10 @@ use crate::builtins::value::same_value;
mod gcobject; mod gcobject;
mod internal_methods; mod internal_methods;
mod iter;
pub use gcobject::GcObject; pub use gcobject::GcObject;
pub use internal_methods::*; pub use iter::*;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -49,10 +50,11 @@ pub static PROTOTYPE: &str = "prototype";
pub struct Object { pub struct Object {
/// The type of the object. /// The type of the object.
pub data: ObjectData, pub data: ObjectData,
indexed_properties: FxHashMap<u32, Property>,
/// Properties /// Properties
properties: FxHashMap<RcString, Property>, string_properties: FxHashMap<RcString, Property>,
/// Symbol Properties /// Symbol Properties
symbol_properties: FxHashMap<u32, Property>, symbol_properties: FxHashMap<RcSymbol, Property>,
/// Instance prototype `__proto__`. /// Instance prototype `__proto__`.
prototype: Value, prototype: Value,
/// Whether it can have new properties added to it. /// Whether it can have new properties added to it.
@ -107,7 +109,8 @@ impl Default for Object {
fn default() -> Self { fn default() -> Self {
Self { Self {
data: ObjectData::Ordinary, data: ObjectData::Ordinary,
properties: FxHashMap::default(), indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype: Value::null(), prototype: Value::null(),
extensible: true, extensible: true,
@ -127,7 +130,8 @@ impl Object {
Self { Self {
data: ObjectData::Function(function), data: ObjectData::Function(function),
properties: FxHashMap::default(), indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype, prototype,
extensible: true, extensible: true,
@ -151,7 +155,8 @@ impl Object {
pub fn boolean(value: bool) -> Self { pub fn boolean(value: bool) -> Self {
Self { Self {
data: ObjectData::Boolean(value), data: ObjectData::Boolean(value),
properties: FxHashMap::default(), indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype: Value::null(), prototype: Value::null(),
extensible: true, extensible: true,
@ -162,7 +167,8 @@ impl Object {
pub fn number(value: f64) -> Self { pub fn number(value: f64) -> Self {
Self { Self {
data: ObjectData::Number(value), data: ObjectData::Number(value),
properties: FxHashMap::default(), indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype: Value::null(), prototype: Value::null(),
extensible: true, extensible: true,
@ -176,7 +182,8 @@ impl Object {
{ {
Self { Self {
data: ObjectData::String(value.into()), data: ObjectData::String(value.into()),
properties: FxHashMap::default(), indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype: Value::null(), prototype: Value::null(),
extensible: true, extensible: true,
@ -187,7 +194,8 @@ impl Object {
pub fn bigint(value: RcBigInt) -> Self { pub fn bigint(value: RcBigInt) -> Self {
Self { Self {
data: ObjectData::BigInt(value), data: ObjectData::BigInt(value),
properties: FxHashMap::default(), indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype: Value::null(), prototype: Value::null(),
extensible: true, extensible: true,
@ -388,26 +396,6 @@ impl Object {
matches!(self.data, ObjectData::Ordinary) 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 { pub fn prototype(&self) -> &Value {
&self.prototype &self.prototype
} }

119
boa/src/builtins/property/mod.rs

@ -18,6 +18,7 @@ use crate::builtins::value::RcString;
use crate::builtins::value::RcSymbol; use crate::builtins::value::RcSymbol;
use crate::builtins::Value; use crate::builtins::Value;
use gc::{Finalize, Trace}; use gc::{Finalize, Trace};
use std::convert::TryFrom;
use std::fmt; use std::fmt;
pub mod attribute; pub mod attribute;
@ -290,33 +291,50 @@ impl<'a> From<&'a Value> for Property {
pub enum PropertyKey { pub enum PropertyKey {
String(RcString), String(RcString),
Symbol(RcSymbol), Symbol(RcSymbol),
Index(u32),
} }
impl From<RcString> for PropertyKey { impl From<RcString> for PropertyKey {
#[inline] #[inline]
fn from(string: RcString) -> PropertyKey { 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 { impl From<&str> for PropertyKey {
#[inline] #[inline]
fn from(string: &str) -> PropertyKey { 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<String> for PropertyKey { impl From<String> for PropertyKey {
#[inline] #[inline]
fn from(string: String) -> PropertyKey { 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<Box<str>> for PropertyKey { impl From<Box<str>> for PropertyKey {
#[inline] #[inline]
fn from(string: Box<str>) -> PropertyKey { fn from(string: Box<str>) -> 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 { match self {
PropertyKey::String(ref string) => string.fmt(f), PropertyKey::String(ref string) => string.fmt(f),
PropertyKey::Symbol(ref symbol) => symbol.fmt(f), PropertyKey::Symbol(ref symbol) => symbol.fmt(f),
} PropertyKey::Index(index) => index.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(),
} }
} }
} }
@ -353,6 +362,13 @@ impl From<&PropertyKey> for Value {
match property_key { match property_key {
PropertyKey::String(ref string) => string.clone().into(), PropertyKey::String(ref string) => string.clone().into(),
PropertyKey::Symbol(ref symbol) => symbol.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 { match property_key {
PropertyKey::String(ref string) => string.clone().into(), PropertyKey::String(ref string) => string.clone().into(),
PropertyKey::Symbol(ref symbol) => symbol.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,
} }
} }
} }

140
boa/src/builtins/symbol/mod.rs

@ -20,14 +20,26 @@ mod tests;
use super::function::{make_builtin_fn, make_constructor_fn}; use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{ use crate::{
builtins::value::{RcString, RcSymbol, Value}, builtins::{
property::{Attribute, Property},
value::{RcString, RcSymbol, Value},
},
exec::Interpreter, exec::Interpreter,
BoaProfiler, Result, BoaProfiler, Result,
}; };
use gc::{Finalize, Trace}; use gc::{Finalize, Trace};
#[derive(Debug, Finalize, Trace, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[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 { impl Symbol {
/// The name of the object. /// The name of the object.
@ -38,12 +50,12 @@ impl Symbol {
/// Returns the `Symbol`s description. /// Returns the `Symbol`s description.
pub fn description(&self) -> Option<&str> { pub fn description(&self) -> Option<&str> {
self.0.as_deref() self.description.as_deref()
} }
/// Returns the `Symbol`s hash. /// Returns the `Symbol`s hash.
pub fn hash(&self) -> u32 { pub fn hash(&self) -> u32 {
self.1 self.hash
} }
fn this_symbol_value(value: &Value, ctx: &mut Interpreter) -> Result<RcSymbol> { fn this_symbol_value(value: &Value, ctx: &mut Interpreter) -> Result<RcSymbol> {
@ -78,7 +90,7 @@ impl Symbol {
_ => None, _ => None,
}; };
Ok(Value::symbol(Symbol(description, ctx.generate_hash()))) Ok(ctx.construct_symbol(description).into())
} }
/// `Symbol.prototype.toString()` /// `Symbol.prototype.toString()`
@ -103,37 +115,21 @@ impl Symbol {
pub fn init(interpreter: &mut Interpreter) -> (&'static str, Value) { pub fn init(interpreter: &mut Interpreter) -> (&'static str, Value) {
// Define the Well-Known Symbols // Define the Well-Known Symbols
// https://tc39.es/ecma262/#sec-well-known-symbols // https://tc39.es/ecma262/#sec-well-known-symbols
let symbol_async_iterator = Symbol( let symbol_async_iterator =
Some("Symbol.asyncIterator".into()), interpreter.construct_symbol(Some("Symbol.asyncIterator".into()));
interpreter.generate_hash(), let symbol_has_instance = interpreter.construct_symbol(Some("Symbol.hasInstance".into()));
); let symbol_is_concat_spreadable =
let symbol_has_instance = Symbol( interpreter.construct_symbol(Some("Symbol.isConcatSpreadable".into()));
Some("Symbol.hasInstance".into()), let symbol_iterator = interpreter.construct_symbol(Some("Symbol.iterator".into()));
interpreter.generate_hash(), let symbol_match = interpreter.construct_symbol(Some("Symbol.match".into()));
); let symbol_match_all = interpreter.construct_symbol(Some("Symbol.matchAll".into()));
let symbol_is_concat_spreadable = Symbol( let symbol_replace = interpreter.construct_symbol(Some("Symbol.replace".into()));
Some("Symbol.isConcatSpreadable".into()), let symbol_search = interpreter.construct_symbol(Some("Symbol.search".into()));
interpreter.generate_hash(), let symbol_species = interpreter.construct_symbol(Some("Symbol.species".into()));
); let symbol_split = interpreter.construct_symbol(Some("Symbol.split".into()));
let symbol_iterator = Symbol(Some("Symbol.iterator".into()), interpreter.generate_hash()); let symbol_to_primitive = interpreter.construct_symbol(Some("Symbol.toPrimitive".into()));
let symbol_match = Symbol(Some("Symbol.match".into()), interpreter.generate_hash()); let symbol_to_string_tag = interpreter.construct_symbol(Some("Symbol.toStringTag".into()));
let symbol_match_all = Symbol(Some("Symbol.matchAll".into()), interpreter.generate_hash()); let symbol_unscopables = interpreter.construct_symbol(Some("Symbol.unscopables".into()));
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 global = interpreter.global(); let global = interpreter.global();
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
@ -153,22 +149,62 @@ impl Symbol {
true, true,
); );
symbol_object.set_field("asyncIterator", Value::symbol(symbol_async_iterator)); {
symbol_object.set_field("hasInstance", Value::symbol(symbol_has_instance)); let mut symbol_object = symbol_object.as_object_mut().unwrap();
symbol_object.set_field( let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
"isConcatSpreadable", symbol_object.insert_property(
Value::symbol(symbol_is_concat_spreadable), "asyncIterator",
); Property::data_descriptor(symbol_async_iterator.into(), attribute),
symbol_object.set_field("iterator", Value::symbol(symbol_iterator)); );
symbol_object.set_field("match", Value::symbol(symbol_match)); symbol_object.insert_property(
symbol_object.set_field("matchAll", Value::symbol(symbol_match_all)); "hasInstance",
symbol_object.set_field("replace", Value::symbol(symbol_replace)); Property::data_descriptor(symbol_has_instance.into(), attribute),
symbol_object.set_field("search", Value::symbol(symbol_search)); );
symbol_object.set_field("species", Value::symbol(symbol_species)); symbol_object.insert_property(
symbol_object.set_field("split", Value::symbol(symbol_split)); "isConcatSpreadable",
symbol_object.set_field("toPrimitive", Value::symbol(symbol_to_primitive)); Property::data_descriptor(symbol_is_concat_spreadable.into(), attribute),
symbol_object.set_field("toStringTag", Value::symbol(symbol_to_string_tag)); );
symbol_object.set_field("unscopables", Value::symbol(symbol_unscopables)); 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) (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(); let sym = forward_val(&mut engine, "sym.toString()").unwrap();
assert_eq!(sym.display().to_string(), "\"Symbol(Hello)\""); 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 { fn from(value: &[T]) -> Self {
let mut array = Object::default(); let mut array = Object::default();
for (i, item) in value.iter().enumerate() { for (i, item) in value.iter().enumerate() {
array.properties_mut().insert( array.insert_property(i, Property::default().value(item.clone().into()));
RcString::from(i.to_string()),
Property::default().value(item.clone().into()),
);
} }
Self::from(array) Self::from(array)
} }
@ -143,10 +140,7 @@ where
fn from(value: Vec<T>) -> Self { fn from(value: Vec<T>) -> Self {
let mut array = Object::default(); let mut array = Object::default();
for (i, item) in value.into_iter().enumerate() { for (i, item) in value.into_iter().enumerate() {
array.properties_mut().insert( array.insert_property(i, Property::default().value(item.into()));
RcString::from(i.to_string()),
Property::default().value(item.into()),
);
} }
Value::from(array) 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) => { (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 let v = &val
.value .value
.as_ref() .as_ref()
@ -64,10 +64,9 @@ macro_rules! print_obj_value {
// A private overload of the macro // A private overload of the macro
// DO NOT use directly // DO NOT use directly
(impl $field:ident, $v:expr, $f:expr) => { (impl $v:expr, $f:expr) => {
$v $v
.borrow() .borrow()
.$field()
.iter() .iter()
.map($f) .map($f)
.collect::<Vec<String>>() .collect::<Vec<String>>()
@ -94,9 +93,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children:
ObjectData::Array => { ObjectData::Array => {
let len = v let len = v
.borrow() .borrow()
.properties() .get_own_property(&PropertyKey::from("length"))
.get("length")
.expect("Could not get Array's length property")
.value .value
.clone() .clone()
.expect("Could not borrow value") .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 // which are part of the Array
log_string_from( log_string_from(
&v.borrow() &v.borrow()
.properties() .get_own_property(&i.into())
.get(i.to_string().as_str())
.unwrap()
.value .value
.clone() .clone()
.expect("Could not borrow value"), .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) => { ObjectData::Map(ref map) => {
let size = v let size = v
.borrow() .borrow()
.properties() .get_own_property(&PropertyKey::from("size"))
.get("size")
.unwrap()
.value .value
.clone() .clone()
.expect("Could not borrow value") .expect("Could not borrow value")

127
boa/src/builtins/value/mod.rs

@ -9,7 +9,7 @@ use super::number::{f64_to_int32, f64_to_uint32};
use crate::builtins::{ use crate::builtins::{
object::{GcObject, Object, ObjectData, PROTOTYPE}, object::{GcObject, Object, ObjectData, PROTOTYPE},
property::{Attribute, Property, PropertyKey}, property::{Attribute, Property, PropertyKey},
BigInt, Number, Symbol, BigInt, Number,
}; };
use crate::exec::Interpreter; use crate::exec::Interpreter;
use crate::{BoaProfiler, Result}; use crate::{BoaProfiler, Result};
@ -145,8 +145,8 @@ impl Value {
/// Creates a new symbol value. /// Creates a new symbol value.
#[inline] #[inline]
pub(crate) fn symbol(symbol: Symbol) -> Self { pub fn symbol(symbol: RcSymbol) -> Self {
Self::Symbol(RcSymbol::from(symbol)) Self::Symbol(symbol)
} }
/// Returns a new empty object /// Returns a new empty object
@ -239,7 +239,7 @@ impl Value {
Self::Object(ref obj) => { Self::Object(ref obj) => {
if obj.borrow().is_array() { if obj.borrow().is_array() {
let mut arr: Vec<JSONValue> = Vec::new(); let mut arr: Vec<JSONValue> = Vec::new();
for k in obj.borrow().properties().keys() { for k in obj.borrow().keys() {
if k != "length" { if k != "length" {
let value = self.get_field(k.to_string()); let value = self.get_field(k.to_string());
if value.is_undefined() || value.is_function() || value.is_symbol() { if value.is_undefined() || value.is_function() || value.is_symbol() {
@ -252,7 +252,7 @@ impl Value {
Ok(JSONValue::Array(arr)) Ok(JSONValue::Array(arr))
} else { } else {
let mut new_obj = Map::new(); let mut new_obj = Map::new();
for k in obj.borrow().properties().keys() { for k in obj.borrow().keys() {
let key = k.clone(); let key = k.clone();
let value = self.get_field(k.to_string()); let value = self.get_field(k.to_string());
if !value.is_undefined() && !value.is_function() && !value.is_symbol() { if !value.is_undefined() && !value.is_function() && !value.is_symbol() {
@ -446,30 +446,33 @@ impl Value {
/// Removes a property from a Value object. /// Removes a property from a Value object.
/// ///
/// It will return a boolean based on if the value was removed, if there was no value to remove false is returned. /// It will return a boolean based on if the value was removed, if there was no value to remove false is returned.
pub fn remove_property(&self, field: &str) -> bool { pub fn remove_property<Key>(&self, key: Key) -> bool
where
Key: Into<PropertyKey>,
{
self.as_object_mut() self.as_object_mut()
.and_then(|mut x| x.properties_mut().remove(field)) .map(|mut x| x.remove_property(&key.into()))
.is_some() .is_some()
} }
/// Resolve the property in the object. /// Resolve the property in the object.
/// ///
/// A copy of the Property is returned. /// 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"); 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 { 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) => { Self::Object(ref object) => {
let object = object.borrow(); let object = object.borrow();
match object.properties().get(field) { let property = object.get_own_property(&key);
Some(value) => Some(value.clone()), if !property.is_none() {
None => object.prototype().get_property(field), return Some(property);
} }
object.prototype().get_property(key)
} }
_ => None, _ => None,
} }
@ -483,82 +486,74 @@ impl Value {
let _timer = BoaProfiler::global().start_event("Value::update_property", "value"); let _timer = BoaProfiler::global().start_event("Value::update_property", "value");
if let Some(ref mut object) = self.as_object_mut() { if let Some(ref mut object) = self.as_object_mut() {
// Use value, or walk up the prototype chain object.insert_property(field, new_property);
if let Some(property) = object.properties_mut().get_mut(field) {
*property = 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 /// 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]] /// 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 /// 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 where
F: Into<Value>, K: Into<PropertyKey>,
{ {
let _timer = BoaProfiler::global().start_event("Value::get_field", "value"); let _timer = BoaProfiler::global().start_event("Value::get_field", "value");
match field.into() { let key = key.into();
// Our field will either be a String or a Symbol match self.get_property(key) {
Self::String(ref s) => { Some(prop) => {
match self.get_property(s) { // If the Property has [[Get]] set to a function, we should run that and return the Value
Some(prop) => { let prop_getter = match prop.get {
// If the Property has [[Get]] set to a function, we should run that and return the Value Some(_) => None,
let prop_getter = match prop.get { None => None,
Some(_) => None, };
None => None,
}; // If the getter is populated, use that. If not use [[Value]] instead
if let Some(val) = prop_getter {
// If the getter is populated, use that. If not use [[Value]] instead val
if let Some(val) = prop_getter { } else {
val let val = prop
} else { .value
let val = prop .as_ref()
.value .expect("Could not get property as reference");
.as_ref() val.clone()
.expect("Could not get property as reference");
val.clone()
}
}
None => Value::undefined(),
} }
} }
Self::Symbol(_) => unimplemented!(), None => Value::undefined(),
_ => Value::undefined(),
} }
} }
/// Check to see if the Value has the field, mainly used by environment records. /// Check to see if the Value has the field, mainly used by environment records.
#[inline] #[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"); 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 /// Set the field in the value
#[inline] #[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 where
F: Into<PropertyKey>, K: Into<PropertyKey>,
V: Into<Value>, V: Into<Value>,
{ {
let field = field.into(); let key = key.into();
let value = value.into(); let value = value.into();
let _timer = BoaProfiler::global().start_event("Value::set_field", "value"); let _timer = BoaProfiler::global().start_event("Value::set_field", "value");
if let Self::Object(ref obj) = *self { 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 obj.borrow().is_array() {
if let Ok(num) = string.parse::<usize>() { let len = self.get_field("length").as_number().unwrap() as u32;
if num > 0 { if len < index + 1 {
let len = self.get_field("length").as_number().unwrap() as i32; self.set_field("length", index + 1);
if len < (num + 1) as i32 {
self.set_field("length", num + 1);
}
}
} }
} }
} }
obj.borrow_mut().set(&field, value.clone()); obj.borrow_mut().set(key, value.clone());
} }
value value
} }
@ -572,14 +567,12 @@ impl Value {
} }
/// Set the property in the 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 where
S: Into<RcString>, K: Into<PropertyKey>,
{ {
if let Some(mut object) = self.as_object_mut() { if let Some(mut object) = self.as_object_mut() {
object object.insert_property(key.into(), property.clone());
.properties_mut()
.insert(field.into(), property.clone());
} }
property 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) { pub fn create_global_var_binding(&mut self, name: String, deletion: bool) {
let obj_rec = &mut self.object_record; let obj_rec = &mut self.object_record;
let global_object = &obj_rec.bindings; 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(); let extensible = global_object.is_extensible();
if !has_property && extensible { if !has_property && extensible {
obj_rec.create_mutable_binding(name.clone(), deletion); 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) { pub fn create_global_function_binding(&mut self, name: &str, value: Value, deletion: bool) {
let global_object = &mut self.object_record.bindings; 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 let Some(prop) = existing_prop {
if prop.value.is_none() || prop.configurable_or(false) { if prop.value.is_none() || prop.configurable_or(false) {
let mut property = let mut property =

6
boa/src/exec/call/mod.rs

@ -19,8 +19,10 @@ impl Executable for Call {
Node::GetField(ref get_field) => { Node::GetField(ref get_field) => {
let obj = get_field.obj().run(interpreter)?; let obj = get_field.obj().run(interpreter)?;
let field = get_field.field().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(), 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)?; 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}, function::{Function, FunctionFlags, NativeFunction},
object::{GcObject, Object, ObjectData, PROTOTYPE}, object::{GcObject, Object, ObjectData, PROTOTYPE},
property::PropertyKey, property::PropertyKey,
value::{PreferredType, Type, Value}, value::{PreferredType, RcString, RcSymbol, Type, Value},
Console, Console, Symbol,
}, },
realm::Realm, realm::Realm,
syntax::ast::{ syntax::ast::{
@ -173,9 +173,9 @@ impl Interpreter {
Function::BuiltIn(body.into(), FunctionFlags::CALLABLE), Function::BuiltIn(body.into(), FunctionFlags::CALLABLE),
function_prototype, function_prototype,
); );
function.set(&PROTOTYPE.into(), proto); function.set(PROTOTYPE.into(), proto);
function.set(&"length".into(), length.into()); function.set("length".into(), length.into());
function.set(&"name".into(), name.into()); function.set("name".into(), name.into());
Ok(GcObject::new(function)) Ok(GcObject::new(function))
} }
@ -362,6 +362,19 @@ impl Interpreter {
pub(crate) fn console_mut(&mut self) -> &mut Console { pub(crate) fn console_mut(&mut self) -> &mut Console {
&mut self.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 { impl Executable for Node {

2
boa/src/exec/tests.rs

@ -628,7 +628,7 @@ fn unary_delete() {
const c = delete a.c + ''; const c = delete a.c + '';
a.b + c a.b + c
"#; "#;
assert_eq!(&exec(delete_not_existing_prop), "\"5false\""); assert_eq!(&exec(delete_not_existing_prop), "\"5true\"");
let delete_field = r#" let delete_field = r#"
const a = { b: 5 }; const a = { b: 5 };

Loading…
Cancel
Save