Browse Source

Proposal of new `PropertyDescriptor` design (#1432)

pull/1425/head
jedel1043 3 years ago committed by GitHub
parent
commit
fe665dbe06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      boa/src/builtins/array/array_iterator.rs
  2. 57
      boa/src/builtins/array/mod.rs
  3. 2
      boa/src/builtins/array/tests.rs
  4. 5
      boa/src/builtins/date/tests.rs
  5. 36
      boa/src/builtins/function/mod.rs
  6. 20
      boa/src/builtins/json/mod.rs
  7. 11
      boa/src/builtins/map/map_iterator.rs
  8. 13
      boa/src/builtins/map/mod.rs
  9. 8
      boa/src/builtins/mod.rs
  10. 13
      boa/src/builtins/object/for_in_iterator.rs
  11. 64
      boa/src/builtins/object/mod.rs
  12. 17
      boa/src/builtins/reflect/mod.rs
  13. 11
      boa/src/builtins/regexp/regexp_string_iterator.rs
  14. 1
      boa/src/builtins/regexp/tests.rs
  15. 2
      boa/src/builtins/set/mod.rs
  16. 11
      boa/src/builtins/set/set_iterator.rs
  17. 14
      boa/src/builtins/string/mod.rs
  18. 11
      boa/src/builtins/string/string_iterator.rs
  19. 2
      boa/src/class.rs
  20. 104
      boa/src/context.rs
  21. 32
      boa/src/environment/global_environment_record.rs
  22. 28
      boa/src/environment/object_environment_record.rs
  23. 199
      boa/src/object/gcobject.rs
  24. 389
      boa/src/object/internal_methods.rs
  25. 116
      boa/src/object/mod.rs
  26. 531
      boa/src/property/mod.rs
  27. 4
      boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs
  28. 52
      boa/src/syntax/ast/node/object/mod.rs
  29. 18
      boa/src/value/conversions.rs
  30. 35
      boa/src/value/display.rs
  31. 44
      boa/src/value/mod.rs
  32. 6
      boa/src/value/tests.rs
  33. 2
      boa_tester/src/exec/js262.rs

11
boa/src/builtins/array/array_iterator.rs

@ -2,7 +2,7 @@ use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, Value}, builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, Value},
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::{GcObject, ObjectData}, object::{GcObject, ObjectData},
property::{Attribute, DataDescriptor}, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
}; };
@ -128,10 +128,11 @@ impl ArrayIterator {
array_iterator.set_prototype_instance(iterator_prototype); array_iterator.set_prototype_instance(iterator_prototype);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = WellKnownSymbols::to_string_tag();
let to_string_tag_property = DataDescriptor::new( let to_string_tag_property = PropertyDescriptor::builder()
"Array Iterator", .value("Array Iterator")
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .writable(false)
); .enumerable(false)
.configurable(true);
array_iterator.insert(to_string_tag, to_string_tag_property); array_iterator.insert(to_string_tag, to_string_tag_property);
array_iterator array_iterator
} }

57
boa/src/builtins/array/mod.rs

@ -18,7 +18,7 @@ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
builtins::Number, builtins::Number,
object::{ConstructorBuilder, FunctionBuilder, GcObject, ObjectData, PROTOTYPE}, object::{ConstructorBuilder, FunctionBuilder, GcObject, ObjectData, PROTOTYPE},
property::{Attribute, DataDescriptor}, property::{Attribute, PropertyDescriptor},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::{IntegerOrInfinity, Value}, value::{IntegerOrInfinity, Value},
BoaProfiler, Context, JsString, Result, BoaProfiler, Context, JsString, Result,
@ -225,11 +225,16 @@ impl Array {
array.borrow_mut().data = ObjectData::Array; array.borrow_mut().data = ObjectData::Array;
// 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
let length = DataDescriptor::new(
length as f64, array.ordinary_define_own_property(
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, "length".into(),
PropertyDescriptor::builder()
.value(length as f64)
.writable(true)
.enumerable(false)
.configurable(false)
.build(),
); );
array.ordinary_define_own_property("length".into(), length.into());
Ok(array) Ok(array)
} }
@ -242,11 +247,15 @@ impl Array {
.as_object() .as_object()
.expect("'array' should be an object") .expect("'array' should be an object")
.set_prototype_instance(context.standard_objects().array_object().prototype().into()); .set_prototype_instance(context.standard_objects().array_object().prototype().into());
let length = DataDescriptor::new( array.set_property(
Value::from(0), "length",
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, PropertyDescriptor::builder()
.value(0)
.writable(true)
.enumerable(false)
.configurable(false)
.build(),
); );
array.set_property("length", length);
array array
} }
@ -268,14 +277,25 @@ impl Array {
} }
// Create length // Create length
let length = DataDescriptor::new( array_obj_ptr.set_property(
array_contents.len(), "length".to_string(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, PropertyDescriptor::builder()
.value(array_contents.len())
.writable(true)
.enumerable(false)
.configurable(false)
.build(),
); );
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_property(n, DataDescriptor::new(value, Attribute::all())); array_obj_ptr.set_property(
n,
PropertyDescriptor::builder()
.value(value)
.configurable(true)
.enumerable(true)
.writable(true),
);
} }
Ok(array_obj_ptr) Ok(array_obj_ptr)
} }
@ -389,7 +409,14 @@ 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); let new_index = orig_length.wrapping_add(n);
array_ptr.set_property(new_index, DataDescriptor::new(value, Attribute::all())); array_ptr.set_property(
new_index,
PropertyDescriptor::builder()
.value(value)
.configurable(true)
.enumerable(true)
.writable(true),
);
} }
array_ptr.set_field( array_ptr.set_field(

2
boa/src/builtins/array/tests.rs

@ -1546,5 +1546,5 @@ fn array_length_is_not_enumerable() {
let array = Array::new_array(&context); let array = Array::new_array(&context);
let desc = array.get_property("length").unwrap(); let desc = array.get_property("length").unwrap();
assert!(!desc.enumerable()); assert!(!desc.expect_enumerable());
} }

5
boa/src/builtins/date/tests.rs

@ -63,9 +63,8 @@ fn date_this_time_value() {
let message_property = &error let message_property = &error
.get_property("message") .get_property("message")
.expect("Expected 'message' property") .expect("Expected 'message' property")
.as_data_descriptor() .expect_value()
.unwrap() .clone();
.value();
assert_eq!(Value::string("\'this\' is not a Date"), *message_property); assert_eq!(Value::string("\'this\' is not a Date"), *message_property);
} }

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

@ -17,7 +17,7 @@ use crate::{
environment::lexical_environment::Environment, environment::lexical_environment::Environment,
gc::{custom_trace, empty_trace, Finalize, Trace}, gc::{custom_trace, empty_trace, Finalize, Trace},
object::{ConstructorBuilder, FunctionBuilder, GcObject, Object, ObjectData}, object::{ConstructorBuilder, FunctionBuilder, GcObject, Object, ObjectData},
property::{Attribute, DataDescriptor}, property::{Attribute, PropertyDescriptor},
syntax::ast::node::{FormalParameter, RcStatementList}, syntax::ast::node::{FormalParameter, RcStatementList},
BoaProfiler, Context, Result, Value, BoaProfiler, Context, Result, Value,
}; };
@ -181,19 +181,21 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value {
let len = arguments_list.len(); let len = arguments_list.len();
let obj = GcObject::new(Object::default()); let obj = GcObject::new(Object::default());
// Set length // Set length
let length = DataDescriptor::new( let length = PropertyDescriptor::builder()
len, .value(len)
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .writable(true)
); .enumerable(false)
.configurable(true);
// Define length as a property // Define length as a property
obj.ordinary_define_own_property("length".into(), length.into()); obj.ordinary_define_own_property("length".into(), length.into());
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");
let prop = DataDescriptor::new( let prop = PropertyDescriptor::builder()
val.clone(), .value(val.clone())
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, .writable(true)
); .enumerable(true)
.configurable(true);
obj.insert(index, prop); obj.insert(index, prop);
index += 1; index += 1;
@ -243,14 +245,20 @@ pub fn make_builtin_fn<N>(
.prototype() .prototype()
.into(), .into(),
); );
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; let attribute = PropertyDescriptor::builder()
function.insert_property("length", length, attribute); .writable(false)
function.insert_property("name", name.as_str(), attribute); .enumerable(false)
.configurable(true);
function.insert_property("length", attribute.clone().value(length));
function.insert_property("name", attribute.value(name.as_str()));
parent.clone().insert_property( parent.clone().insert_property(
name, name,
function, PropertyDescriptor::builder()
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .value(function)
.writable(true)
.enumerable(false)
.configurable(true),
); );
} }

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

@ -17,7 +17,7 @@ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::Object, object::Object,
object::ObjectInitializer, object::ObjectInitializer,
property::{Attribute, DataDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::IntegerOrInfinity, value::IntegerOrInfinity,
BoaProfiler, Context, Result, Value, BoaProfiler, Context, Result, Value,
@ -201,15 +201,16 @@ impl Json {
let this_arg = object.clone(); let this_arg = object.clone();
object_to_return.set_property( object_to_return.set_property(
key.to_owned(), key.to_owned(),
DataDescriptor::new( PropertyDescriptor::builder()
context.call( .value(context.call(
replacer, replacer,
&this_arg, &this_arg,
&[Value::from(key.clone()), val.clone()], &[Value::from(key.clone()), val.clone()],
)?, )?)
Attribute::all(), .writable(true)
), .enumerable(true)
); .configurable(true),
)
} }
if let Some(value) = object_to_return.to_json(context)? { if let Some(value) = object_to_return.to_json(context)? {
Ok(Value::from(json_to_pretty_string(&value, gap))) Ok(Value::from(json_to_pretty_string(&value, gap)))
@ -229,9 +230,10 @@ impl Json {
replacer replacer
.get_property(key) .get_property(key)
.as_ref() .as_ref()
.and_then(|p| p.as_data_descriptor())
.map(|d| d.value()) .map(|d| d.value())
.unwrap_or_else(Value::undefined), .flatten()
.cloned()
.unwrap_or_default(),
) )
} }
}); });

11
boa/src/builtins/map/map_iterator.rs

@ -1,7 +1,7 @@
use crate::{ use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, Value}, builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, Value},
object::{GcObject, ObjectData}, object::{GcObject, ObjectData},
property::{Attribute, DataDescriptor}, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
}; };
@ -154,10 +154,11 @@ impl MapIterator {
map_iterator.set_prototype_instance(iterator_prototype); map_iterator.set_prototype_instance(iterator_prototype);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = WellKnownSymbols::to_string_tag();
let to_string_tag_property = DataDescriptor::new( let to_string_tag_property = PropertyDescriptor::builder()
"Map Iterator", .value("Map Iterator")
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .writable(false)
); .enumerable(false)
.configurable(true);
map_iterator.insert(to_string_tag, to_string_tag_property); map_iterator.insert(to_string_tag, to_string_tag_property);
map_iterator map_iterator
} }

13
boa/src/builtins/map/mod.rs

@ -15,7 +15,7 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE}, object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE},
property::{Attribute, DataDescriptor}, property::{Attribute, PropertyDescriptor},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, Result, Value, BoaProfiler, Context, Result, Value,
}; };
@ -217,12 +217,13 @@ impl Map {
/// Helper function to set the size property. /// Helper function to set the size property.
fn set_size(this: &Value, size: usize) { fn set_size(this: &Value, size: usize) {
let size = DataDescriptor::new( let size = PropertyDescriptor::builder()
size, .value(size)
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, .writable(false)
); .enumerable(false)
.configurable(false);
this.set_property("size".to_string(), size); this.set_property("size", size);
} }
/// `Map.prototype.set( key, value )` /// `Map.prototype.set( key, value )`

8
boa/src/builtins/mod.rs

@ -53,7 +53,7 @@ pub(crate) use self::{
undefined::Undefined, undefined::Undefined,
}; };
use crate::{ use crate::{
property::{Attribute, DataDescriptor}, property::{Attribute, PropertyDescriptor},
Context, Value, Context, Value,
}; };
@ -104,7 +104,11 @@ pub fn init(context: &mut Context) {
for init in &globals { for init in &globals {
let (name, value, attribute) = init(context); let (name, value, attribute) = init(context);
let property = DataDescriptor::new(value, attribute); let property = PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
global_object.borrow_mut().insert(name, property); global_object.borrow_mut().insert(name, property);
} }
} }

13
boa/src/builtins/object/for_in_iterator.rs

@ -2,8 +2,8 @@ use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object}, builtins::{function::make_builtin_fn, iterable::create_iter_result_object},
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::{GcObject, ObjectData}, object::{GcObject, ObjectData},
property::PropertyDescriptor,
property::PropertyKey, property::PropertyKey,
property::{Attribute, DataDescriptor},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, JsString, Result, Value, BoaProfiler, Context, JsString, Result, Value,
}; };
@ -90,7 +90,7 @@ impl ForInIterator {
object.__get_own_property__(&PropertyKey::from(r.clone())) object.__get_own_property__(&PropertyKey::from(r.clone()))
{ {
iterator.visited_keys.insert(r.clone()); iterator.visited_keys.insert(r.clone());
if desc.enumerable() { if desc.expect_enumerable() {
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
context, context,
Value::from(r.to_string()), Value::from(r.to_string()),
@ -134,10 +134,11 @@ impl ForInIterator {
for_in_iterator.set_prototype_instance(iterator_prototype); for_in_iterator.set_prototype_instance(iterator_prototype);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = WellKnownSymbols::to_string_tag();
let to_string_tag_property = DataDescriptor::new( let to_string_tag_property = PropertyDescriptor::builder()
"For In Iterator", .value("For In Iterator")
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .writable(false)
); .enumerable(false)
.configurable(true);
for_in_iterator.insert(to_string_tag, to_string_tag_property); for_in_iterator.insert(to_string_tag, to_string_tag_property);
for_in_iterator for_in_iterator
} }

64
boa/src/builtins/object/mod.rs

@ -18,9 +18,7 @@ use crate::{
object::{ object::{
ConstructorBuilder, Object as BuiltinObject, ObjectData, ObjectInitializer, PROTOTYPE, ConstructorBuilder, Object as BuiltinObject, ObjectData, ObjectInitializer, PROTOTYPE,
}, },
property::Attribute, property::{Attribute, DescriptorKind, PropertyDescriptor},
property::DataDescriptor,
property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::{Type, Value}, value::{Type, Value},
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
@ -205,7 +203,11 @@ impl Object {
if !descriptor.is_undefined() { if !descriptor.is_undefined() {
descriptors.borrow_mut().insert( descriptors.borrow_mut().insert(
key, key,
PropertyDescriptor::from(DataDescriptor::new(descriptor, Attribute::all())), PropertyDescriptor::builder()
.value(descriptor)
.writable(true)
.enumerable(true)
.configurable(true),
); );
} }
} }
@ -221,37 +223,35 @@ impl Object {
fn from_property_descriptor(desc: PropertyDescriptor, context: &mut Context) -> Value { fn from_property_descriptor(desc: PropertyDescriptor, context: &mut Context) -> Value {
let mut descriptor = ObjectInitializer::new(context); let mut descriptor = ObjectInitializer::new(context);
if let PropertyDescriptor::Data(data_desc) = &desc { // TODO: use CreateDataPropertyOrThrow
descriptor.property("value", data_desc.value(), Attribute::all());
}
if let PropertyDescriptor::Accessor(accessor_desc) = &desc { match desc.kind() {
if let Some(setter) = accessor_desc.setter() { DescriptorKind::Data { value, writable } => {
descriptor.property("set", Value::Object(setter.to_owned()), Attribute::all()); if let Some(value) = value {
descriptor.property("value", value.clone(), Attribute::all());
}
if let Some(writable) = writable {
descriptor.property("writable", *writable, Attribute::all());
}
} }
if let Some(getter) = accessor_desc.getter() { DescriptorKind::Accessor { get, set } => {
descriptor.property("get", Value::Object(getter.to_owned()), Attribute::all()); if let Some(get) = get {
descriptor.property("get", get.clone(), Attribute::all());
}
if let Some(set) = set {
descriptor.property("set", set.clone(), Attribute::all());
}
} }
_ => {}
} }
let writable = if let PropertyDescriptor::Data(data_desc) = &desc { if let Some(enumerable) = desc.enumerable() {
data_desc.writable() descriptor.property("enumerable", enumerable, Attribute::all());
} else { }
false
};
descriptor if let Some(configurable) = desc.configurable() {
.property("writable", Value::from(writable), Attribute::all()) descriptor.property("configurable", configurable, Attribute::all());
.property( }
"enumerable",
Value::from(desc.enumerable()),
Attribute::all(),
)
.property(
"configurable",
Value::from(desc.configurable()),
Attribute::all(),
);
descriptor.build().into() descriptor.build().into()
} }
@ -363,11 +363,11 @@ impl Object {
if let Some(object) = object.as_object() { if let Some(object) = object.as_object() {
let key = args let key = args
.get(1) .get(1)
.unwrap_or(&Value::undefined()) .unwrap_or(&Value::Undefined)
.to_property_key(context)?; .to_property_key(context)?;
let desc = args let desc = args
.get(2) .get(2)
.unwrap_or(&Value::undefined()) .unwrap_or(&Value::Undefined)
.to_property_descriptor(context)?; .to_property_descriptor(context)?;
object.define_property_or_throw(key, desc, context)?; object.define_property_or_throw(key, desc, context)?;
@ -548,7 +548,7 @@ impl Object {
// 3.a.iii.1. Let desc be ? from.[[GetOwnProperty]](nextKey). // 3.a.iii.1. Let desc be ? from.[[GetOwnProperty]](nextKey).
if let Some(desc) = from.__get_own_property__(&key) { if let Some(desc) = from.__get_own_property__(&key) {
// 3.a.iii.2. If desc is not undefined and desc.[[Enumerable]] is true, then // 3.a.iii.2. If desc is not undefined and desc.[[Enumerable]] is true, then
if desc.enumerable() { if desc.expect_enumerable() {
// 3.a.iii.2.a. Let propValue be ? Get(from, nextKey). // 3.a.iii.2.a. Let propValue be ? Get(from, nextKey).
let property = from.get(key.clone(), context)?; let property = from.get(key.clone(), context)?;
// 3.a.iii.2.b. Perform ? Set(to, nextKey, propValue, true). // 3.a.iii.2.b. Perform ? Set(to, nextKey, propValue, true).

17
boa/src/builtins/reflect/mod.rs

@ -13,7 +13,7 @@
use crate::{ use crate::{
builtins::{self, BuiltIn}, builtins::{self, BuiltIn},
object::{Object, ObjectData, ObjectInitializer}, object::{Object, ObjectData, ObjectInitializer},
property::{Attribute, DataDescriptor}, property::{Attribute, PropertyDescriptor},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, Result, Value, BoaProfiler, Context, Result, Value,
}; };
@ -147,14 +147,14 @@ impl Reflect {
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?; .ok_or_else(|| context.construct_type_error("target must be an object"))?;
let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?; let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?;
let prop_desc = args let prop_desc: Value = args
.get(2) .get(2)
.and_then(|v| v.as_object()) .and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("property descriptor must be an object"))? .ok_or_else(|| context.construct_type_error("property descriptor must be an object"))?
.to_property_descriptor(context)?; .into();
target target
.__define_own_property__(key, prop_desc, context) .__define_own_property__(key, prop_desc.to_property_descriptor(context)?, context)
.map(|b| b.into()) .map(|b| b.into())
} }
@ -305,10 +305,11 @@ impl Reflect {
Object::with_prototype(array_prototype.into(), ObjectData::Array).into(); Object::with_prototype(array_prototype.into(), ObjectData::Array).into();
result.set_property( result.set_property(
"length", "length",
DataDescriptor::new( PropertyDescriptor::builder()
0, .value(0)
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, .writable(true)
), .enumerable(false)
.configurable(false),
); );
let keys = target.own_property_keys(); let keys = target.own_property_keys();

11
boa/src/builtins/regexp/regexp_string_iterator.rs

@ -15,7 +15,7 @@ use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, regexp}, builtins::{function::make_builtin_fn, iterable::create_iter_result_object, regexp},
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::{GcObject, ObjectData}, object::{GcObject, ObjectData},
property::{Attribute, DataDescriptor}, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, JsString, Result, Value, BoaProfiler, Context, JsString, Result, Value,
}; };
@ -162,10 +162,11 @@ impl RegExpStringIterator {
result.set_prototype_instance(iterator_prototype); result.set_prototype_instance(iterator_prototype);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = WellKnownSymbols::to_string_tag();
let to_string_tag_property = DataDescriptor::new( let to_string_tag_property = PropertyDescriptor::builder()
"RegExp String Iterator", .value("RegExp String Iterator")
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .writable(false)
); .enumerable(false)
.configurable(true);
result.insert(to_string_tag, to_string_tag_property); result.insert(to_string_tag, to_string_tag_property);
result result
} }

1
boa/src/builtins/regexp/tests.rs

@ -56,7 +56,6 @@ fn species() {
"\"function\"" "\"function\""
); );
assert_eq!(forward(&mut context, "descriptor.enumerable"), "false"); assert_eq!(forward(&mut context, "descriptor.enumerable"), "false");
assert_eq!(forward(&mut context, "descriptor.writable"), "false");
assert_eq!(forward(&mut context, "descriptor.configurable"), "true"); assert_eq!(forward(&mut context, "descriptor.configurable"), "true");
} }

2
boa/src/builtins/set/mod.rs

@ -232,7 +232,7 @@ impl Set {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear
pub(crate) fn clear(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> { pub(crate) fn clear(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
if let Some(object) = this.as_object() { if let Some(object) = this.as_object() {
if object.borrow_mut().is_set() { if object.borrow().is_set() {
this.set_data(ObjectData::Set(OrderedSet::new())); this.set_data(ObjectData::Set(OrderedSet::new()));
Ok(Value::Undefined) Ok(Value::Undefined)
} else { } else {

11
boa/src/builtins/set/set_iterator.rs

@ -4,7 +4,7 @@ use crate::{
builtins::Array, builtins::Array,
builtins::Value, builtins::Value,
object::{GcObject, ObjectData}, object::{GcObject, ObjectData},
property::{Attribute, DataDescriptor}, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
}; };
@ -144,10 +144,11 @@ impl SetIterator {
set_iterator.set_prototype_instance(iterator_prototype); set_iterator.set_prototype_instance(iterator_prototype);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = WellKnownSymbols::to_string_tag();
let to_string_tag_property = DataDescriptor::new( let to_string_tag_property = PropertyDescriptor::builder()
"Set Iterator", .value("Set Iterator")
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .writable(false)
); .enumerable(false)
.configurable(true);
set_iterator.insert(to_string_tag, to_string_tag_property); set_iterator.insert(to_string_tag, to_string_tag_property);
set_iterator set_iterator
} }

14
boa/src/builtins/string/mod.rs

@ -15,11 +15,10 @@ mod tests;
use crate::builtins::Symbol; use crate::builtins::Symbol;
use crate::object::PROTOTYPE; use crate::object::PROTOTYPE;
use crate::property::DataDescriptor;
use crate::{ use crate::{
builtins::{string::string_iterator::StringIterator, Array, BuiltIn, RegExp}, builtins::{string::string_iterator::StringIterator, Array, BuiltIn, RegExp},
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData},
property::Attribute, property::{Attribute, PropertyDescriptor},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, JsString, Result, Value, BoaProfiler, Context, JsString, Result, Value,
}; };
@ -191,11 +190,14 @@ impl String {
.expect("this should be an object") .expect("this should be an object")
.set_prototype_instance(prototype.into()); .set_prototype_instance(prototype.into());
let length = DataDescriptor::new( this.set_property(
Value::from(string.encode_utf16().count()), "length",
Attribute::NON_ENUMERABLE, PropertyDescriptor::builder()
.value(string.encode_utf16().count())
.writable(false)
.enumerable(false)
.configurable(false),
); );
this.set_property("length", length);
this.set_data(ObjectData::String(string)); this.set_data(ObjectData::String(string));

11
boa/src/builtins/string/string_iterator.rs

@ -4,7 +4,7 @@ use crate::{
}, },
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::{GcObject, ObjectData}, object::{GcObject, ObjectData},
property::{Attribute, DataDescriptor}, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, Result, Value, BoaProfiler, Context, Result, Value,
}; };
@ -79,10 +79,11 @@ impl StringIterator {
array_iterator.set_prototype_instance(iterator_prototype); array_iterator.set_prototype_instance(iterator_prototype);
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = WellKnownSymbols::to_string_tag();
let to_string_tag_property = DataDescriptor::new( let to_string_tag_property = PropertyDescriptor::builder()
"String Iterator", .value("String Iterator")
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .writable(false)
); .enumerable(false)
.configurable(true);
array_iterator.insert(to_string_tag, to_string_tag_property); array_iterator.insert(to_string_tag, to_string_tag_property);
array_iterator array_iterator
} }

2
boa/src/class.rs

@ -74,7 +74,7 @@ pub trait Class: NativeObject + Sized {
/// The amount of arguments the class `constructor` takes, default is `0`. /// The amount of arguments the class `constructor` takes, default is `0`.
const LENGTH: usize = 0; const LENGTH: usize = 0;
/// The attibutes the class will be binded with, default is `writable`, `enumerable`, `configurable`. /// The attibutes the class will be binded with, default is `writable`, `enumerable`, `configurable`.
const ATTRIBUTE: Attribute = Attribute::all(); const ATTRIBUTES: Attribute = Attribute::all();
/// The constructor of the class. /// The constructor of the class.
fn constructor(this: &Value, args: &[Value], context: &mut Context) -> Result<Self>; fn constructor(this: &Value, args: &[Value], context: &mut Context) -> Result<Self>;

104
boa/src/context.rs

@ -9,7 +9,7 @@ use crate::{
class::{Class, ClassBuilder}, class::{Class, ClassBuilder},
exec::Interpreter, exec::Interpreter,
object::{FunctionBuilder, GcObject, Object, PROTOTYPE}, object::{FunctionBuilder, GcObject, Object, PROTOTYPE},
property::{Attribute, DataDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm, realm::Realm,
syntax::{ syntax::{
ast::{ ast::{
@ -223,7 +223,7 @@ impl StandardObjects {
/// ## Execute Function of Script File /// ## Execute Function of Script File
/// ///
/// ```rust /// ```rust
/// use boa::{Context, object::ObjectInitializer, property::Attribute}; /// use boa::{Context, object::ObjectInitializer, property::{Attribute, PropertyDescriptor}};
/// ///
/// let script = r#" /// let script = r#"
/// function test(arg1) { /// function test(arg1) {
@ -243,7 +243,11 @@ impl StandardObjects {
/// let arg = ObjectInitializer::new(&mut context) /// let arg = ObjectInitializer::new(&mut context)
/// .property("x", 12, Attribute::READONLY) /// .property("x", 12, Attribute::READONLY)
/// .build(); /// .build();
/// context.register_global_property("arg", arg, Attribute::all()); /// context.register_global_property(
/// "arg",
/// arg,
/// Attribute::all()
/// );
/// ///
/// let value = context.eval("test(arg)").unwrap(); /// let value = context.eval("test(arg)").unwrap();
/// ///
@ -517,26 +521,32 @@ impl Context {
let function = GcObject::new(Object::function(func, function_prototype)); let function = GcObject::new(Object::function(func, function_prototype));
// Set constructor field to the newly created Value (function object) // Set constructor field to the newly created Value (function object)
let constructor = DataDescriptor::new( let constructor = PropertyDescriptor::builder()
function.clone(), .value(function.clone())
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .writable(true)
); .enumerable(false)
.configurable(true);
prototype.define_property_or_throw("constructor", constructor, self)?; prototype.define_property_or_throw("constructor", constructor, self)?;
let prototype = DataDescriptor::new( let prototype = PropertyDescriptor::builder()
prototype, .value(prototype)
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, .writable(true)
); .enumerable(false)
.configurable(false);
function.define_property_or_throw(PROTOTYPE, prototype, self)?; function.define_property_or_throw(PROTOTYPE, prototype, self)?;
let length = DataDescriptor::new(
params_len, let length = PropertyDescriptor::builder()
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .value(params_len)
); .writable(false)
.enumerable(false)
.configurable(true);
function.define_property_or_throw("length", length, self)?; function.define_property_or_throw("length", length, self)?;
let name = DataDescriptor::new(
name, let name = PropertyDescriptor::builder()
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .value(name)
); .writable(false)
.enumerable(false)
.configurable(true);
function.define_property_or_throw("name", name, self)?; function.define_property_or_throw("name", name, self)?;
Ok(function.into()) Ok(function.into())
@ -572,8 +582,11 @@ impl Context {
self.global_object().insert_property( self.global_object().insert_property(
name, name,
function, PropertyDescriptor::builder()
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .value(function)
.writable(true)
.enumerable(false)
.configurable(true),
); );
Ok(()) Ok(())
} }
@ -603,8 +616,11 @@ impl Context {
self.global_object().insert_property( self.global_object().insert_property(
name, name,
function, PropertyDescriptor::builder()
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .value(function)
.writable(true)
.enumerable(false)
.configurable(true),
); );
Ok(()) Ok(())
} }
@ -664,7 +680,11 @@ impl Context {
T::init(&mut class_builder)?; T::init(&mut class_builder)?;
let class = class_builder.build(); let class = class_builder.build();
let property = DataDescriptor::new(class, T::ATTRIBUTE); let property = PropertyDescriptor::builder()
.value(class)
.writable(T::ATTRIBUTES.writable())
.enumerable(T::ATTRIBUTES.enumerable())
.configurable(T::ATTRIBUTES.configurable());
self.global_object().insert(T::NAME, property); self.global_object().insert(T::NAME, property);
Ok(()) Ok(())
} }
@ -673,17 +693,33 @@ impl Context {
/// ///
/// # Example /// # Example
/// ``` /// ```
/// use boa::{Context, property::Attribute, object::ObjectInitializer}; /// use boa::{Context, property::{Attribute, PropertyDescriptor}, object::ObjectInitializer};
/// ///
/// let mut context = Context::new(); /// let mut context = Context::new();
/// ///
/// context.register_global_property("myPrimitiveProperty", 10, Attribute::all()); /// context.register_global_property(
/// "myPrimitiveProperty",
/// 10,
/// Attribute::all()
/// );
/// ///
/// let object = ObjectInitializer::new(&mut context) /// let object = ObjectInitializer::new(&mut context)
/// .property("x", 0, Attribute::all()) /// .property(
/// .property("y", 1, Attribute::all()) /// "x",
/// 0,
/// Attribute::all()
/// )
/// .property(
/// "y",
/// 1,
/// Attribute::all()
/// )
/// .build(); /// .build();
/// context.register_global_property("myObjectProperty", object, Attribute::all()); /// context.register_global_property(
/// "myObjectProperty",
/// object,
/// Attribute::all()
/// );
/// ``` /// ```
#[inline] #[inline]
pub fn register_global_property<K, V>(&mut self, key: K, value: V, attribute: Attribute) pub fn register_global_property<K, V>(&mut self, key: K, value: V, attribute: Attribute)
@ -691,8 +727,14 @@ impl Context {
K: Into<PropertyKey>, K: Into<PropertyKey>,
V: Into<Value>, V: Into<Value>,
{ {
let property = DataDescriptor::new(value, attribute); self.global_object().insert(
self.global_object().insert(key, property); key,
PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable()),
);
} }
/// Evaluates the given code. /// Evaluates the given code.

32
boa/src/environment/global_environment_record.rs

@ -16,7 +16,7 @@ use crate::{
}, },
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::GcObject, object::GcObject,
property::{Attribute, DataDescriptor}, property::PropertyDescriptor,
Context, Result, Value, Context, Result, Value,
}; };
use gc::{Gc, GcCell}; use gc::{Gc, GcCell};
@ -66,7 +66,7 @@ impl GlobalEnvironmentRecord {
let existing_prop = global_object.get_property(name); let existing_prop = global_object.get_property(name);
match existing_prop { match existing_prop {
Some(desc) => { Some(desc) => {
if desc.configurable() { if desc.expect_configurable() {
return false; return false;
} }
true true
@ -89,11 +89,11 @@ impl GlobalEnvironmentRecord {
let existing_prop = global_object.get_property(name); let existing_prop = global_object.get_property(name);
match existing_prop { match existing_prop {
Some(prop) => { Some(prop) => {
if prop.configurable() { prop.expect_configurable()
true || prop
} else { .enumerable()
prop.is_data_descriptor() && prop.attributes().writable() && prop.enumerable() .zip(prop.writable())
} .map_or(false, |(a, b)| a && b)
} }
None => global_object.is_extensible(), None => global_object.is_extensible(),
} }
@ -124,18 +124,18 @@ 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);
// TODO: change to desc.is_undefined()
let desc = match existing_prop { let desc = match existing_prop {
Some(desc) if desc.configurable() => DataDescriptor::new(value, Attribute::empty()), Some(desc) if desc.expect_configurable() => PropertyDescriptor::builder().value(value),
Some(_) => { Some(_) => PropertyDescriptor::builder()
let mut attributes = Attribute::WRITABLE | Attribute::ENUMERABLE; .value(value)
if deletion { .writable(true)
attributes |= Attribute::CONFIGURABLE; .enumerable(true)
} .configurable(deletion),
DataDescriptor::new(value, attributes) None => PropertyDescriptor::builder().value(value),
}
None => DataDescriptor::new(value, Attribute::empty()),
}; };
// TODO: fix spec by adding Set and append name to varDeclaredNames
global_object global_object
.as_object() .as_object()
.expect("global object") .expect("global object")

28
boa/src/environment/object_environment_record.rs

@ -16,7 +16,6 @@ use crate::{
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::GcObject, object::GcObject,
property::PropertyDescriptor, property::PropertyDescriptor,
property::{Attribute, DataDescriptor},
Context, Result, Value, Context, Result, Value,
}; };
@ -65,11 +64,11 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
// TODO: could save time here and not bother generating a new undefined object, // TODO: could save time here and not bother generating a new undefined object,
// only for it to be replace with the real value later. We could just add the name to a Vector instead // only for it to be replace with the real value later. We could just add the name to a Vector instead
let bindings = &self.bindings; let bindings = &self.bindings;
let mut prop = DataDescriptor::new( let prop = PropertyDescriptor::builder()
Value::undefined(), .value(Value::undefined())
Attribute::WRITABLE | Attribute::ENUMERABLE, .writable(true)
); .enumerable(true)
prop.set_configurable(deletion); .configurable(deletion);
bindings.set_property(name, prop); bindings.set_property(name, prop);
Ok(()) Ok(())
@ -100,8 +99,10 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
_context: &mut Context, _context: &mut Context,
) -> Result<()> { ) -> Result<()> {
debug_assert!(value.is_object() || value.is_function()); debug_assert!(value.is_object() || value.is_function());
let mut property = DataDescriptor::new(value, Attribute::ENUMERABLE); let property = PropertyDescriptor::builder()
property.set_configurable(strict); .value(value)
.enumerable(true)
.configurable(strict);
self.bindings self.bindings
.as_object() .as_object()
.expect("binding object") .expect("binding object")
@ -111,10 +112,13 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
fn get_binding_value(&self, name: &str, strict: bool, context: &mut Context) -> Result<Value> { fn get_binding_value(&self, name: &str, strict: bool, context: &mut Context) -> Result<Value> {
if self.bindings.has_field(name) { if self.bindings.has_field(name) {
match self.bindings.get_property(name) { Ok(self
Some(PropertyDescriptor::Data(ref d)) => Ok(d.value()), .bindings
_ => Ok(Value::undefined()), .get_property(name)
} .as_ref()
.and_then(|prop| prop.value())
.cloned()
.unwrap_or(Value::Undefined))
} else if strict { } else if strict {
context.throw_reference_error(format!("{} has no binding", name)) context.throw_reference_error(format!("{} has no binding", name))
} else { } else {

199
boa/src/object/gcobject.rs

@ -12,7 +12,7 @@ use crate::{
function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::Environment, lexical_environment::Environment,
}, },
property::{AccessorDescriptor, Attribute, DataDescriptor, PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
syntax::ast::node::RcStatementList, syntax::ast::node::RcStatementList,
value::PreferredType, value::PreferredType,
@ -456,114 +456,6 @@ impl GcObject {
} }
} }
/// Convert the object to a `PropertyDescriptor`
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
pub fn to_property_descriptor(&self, context: &mut Context) -> Result<PropertyDescriptor> {
// 1. If Type(Obj) is not Object, throw a TypeError exception.
// 2. Let desc be a new Property Descriptor that initially has no fields.
let mut attribute = Attribute::empty();
// 3. Let hasEnumerable be ? HasProperty(Obj, "enumerable").
let has_enumerable = self.has_property("enumerable", context)?;
// 4. If hasEnumerable is true, then
// a. Let enumerable be ! ToBoolean(? Get(Obj, "enumerable")).
// b. Set desc.[[Enumerable]] to enumerable.
if has_enumerable && self.get("enumerable", context)?.to_boolean() {
attribute |= Attribute::ENUMERABLE;
}
// 5. Let hasConfigurable be ? HasProperty(Obj, "configurable").
let has_configurable = self.has_property("configurable", context)?;
// 6. If hasConfigurable is true, then
// a. Let configurable be ! ToBoolean(? Get(Obj, "configurable")).
// b. Set desc.[[Configurable]] to configurable.
if has_configurable && self.get("configurable", context)?.to_boolean() {
attribute |= Attribute::CONFIGURABLE;
}
let mut value = None;
// 7. Let hasValue be ? HasProperty(Obj, "value").
let has_value = self.has_property("value", context)?;
// 8. If hasValue is true, then
if has_value {
// a. Let value be ? Get(Obj, "value").
// b. Set desc.[[Value]] to value.
value = Some(self.get("value", context)?);
}
// 9. Let hasWritable be ? HasProperty(Obj, ).
let has_writable = self.has_property("writable", context)?;
// 10. If hasWritable is true, then
if has_writable {
// a. Let writable be ! ToBoolean(? Get(Obj, "writable")).
if self.get("writable", context)?.to_boolean() {
// b. Set desc.[[Writable]] to writable.
attribute |= Attribute::WRITABLE;
}
}
// 11. Let hasGet be ? HasProperty(Obj, "get").
let has_get = self.has_property("get", context)?;
// 12. If hasGet is true, then
let mut get = None;
if has_get {
// a. Let getter be ? Get(Obj, "get").
let getter = self.get("get", context)?;
// b. If IsCallable(getter) is false and getter is not undefined, throw a TypeError exception.
match getter {
Value::Object(ref object) if object.is_callable() => {
// c. Set desc.[[Get]] to getter.
get = Some(object.clone());
}
_ => {
return Err(
context.construct_type_error("Property descriptor getter must be callable")
);
}
}
}
// 13. Let hasSet be ? HasProperty(Obj, "set").
let has_set = self.has_property("set", context)?;
// 14. If hasSet is true, then
let mut set = None;
if has_set {
// 14.a. Let setter be ? Get(Obj, "set").
let setter = self.get("set", context)?;
// 14.b. If IsCallable(setter) is false and setter is not undefined, throw a TypeError exception.
match setter {
Value::Object(ref object) if object.is_callable() => {
// 14.c. Set desc.[[Set]] to setter.
set = Some(object.clone());
}
_ => {
return Err(
context.construct_type_error("Property descriptor setter must be callable")
);
}
};
}
// 15. If desc.[[Get]] is present or desc.[[Set]] is present, then
// 16. Return desc.
if get.is_some() || set.is_some() {
// 15.a. If desc.[[Value]] is present or desc.[[Writable]] is present, throw a TypeError exception.
if value.is_some() || has_writable {
return Err(context.construct_type_error("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"));
}
Ok(AccessorDescriptor::new(get, set, attribute).into())
} else if let Some(v) = value {
Ok(DataDescriptor::new(v, attribute).into())
} else {
Ok(DataDescriptor::new_without_value(attribute).into())
}
}
/// Return `true` if it is a native object and the native type is `T`. /// Return `true` if it is a native object and the native type is `T`.
/// ///
/// # Panics /// # Panics
@ -912,6 +804,95 @@ impl GcObject {
context.throw_type_error("property 'constructor' is not an object") context.throw_type_error("property 'constructor' is not an object")
} }
} }
pub fn to_property_descriptor(&self, context: &mut Context) -> Result<PropertyDescriptor> {
// 1 is implemented on the method `to_property_descriptor` of value
// 2. Let desc be a new Property Descriptor that initially has no fields.
let mut desc = PropertyDescriptor::builder();
// 3. Let hasEnumerable be ? HasProperty(Obj, "enumerable").
// 4. If hasEnumerable is true, then ...
if self.has_property("enumerable", context)? {
// a. Let enumerable be ! ToBoolean(? Get(Obj, "enumerable")).
// b. Set desc.[[Enumerable]] to enumerable.
desc = desc.enumerable(self.get("enumerable", context)?.to_boolean());
}
// 5. Let hasConfigurable be ? HasProperty(Obj, "configurable").
// 6. If hasConfigurable is true, then ...
if self.has_property("configurable", context)? {
// a. Let configurable be ! ToBoolean(? Get(Obj, "configurable")).
// b. Set desc.[[Configurable]] to configurable.
desc = desc.configurable(self.get("configurable", context)?.to_boolean());
}
// 7. Let hasValue be ? HasProperty(Obj, "value").
// 8. If hasValue is true, then ...
if self.has_property("value", context)? {
// a. Let value be ? Get(Obj, "value").
// b. Set desc.[[Value]] to value.
desc = desc.value(self.get("value", context)?);
}
// 9. Let hasWritable be ? HasProperty(Obj, ).
// 10. If hasWritable is true, then ...
if self.has_property("writable", context)? {
// a. Let writable be ! ToBoolean(? Get(Obj, "writable")).
// b. Set desc.[[Writable]] to writable.
desc = desc.writable(self.get("writable", context)?.to_boolean());
}
// 11. Let hasGet be ? HasProperty(Obj, "get").
// 12. If hasGet is true, then
let get = if self.has_property("get", context)? {
// a. Let getter be ? Get(Obj, "get").
let getter = self.get("get", context)?;
// b. If IsCallable(getter) is false and getter is not undefined, throw a TypeError exception.
// todo: extract IsCallable to be callable from Value
if !getter.is_undefined() && getter.as_object().map_or(true, |o| !o.is_callable()) {
return Err(
context.construct_type_error("Property descriptor getter must be callable")
);
}
// c. Set desc.[[Get]] to getter.
Some(getter)
} else {
None
};
// 13. Let hasSet be ? HasProperty(Obj, "set").
// 14. If hasSet is true, then
let set = if self.has_property("set", context)? {
// 14.a. Let setter be ? Get(Obj, "set").
let setter = self.get("set", context)?;
// 14.b. If IsCallable(setter) is false and setter is not undefined, throw a TypeError exception.
// todo: extract IsCallable to be callable from Value
if !setter.is_undefined() && setter.as_object().map_or(true, |o| !o.is_callable()) {
return Err(
context.construct_type_error("Property descriptor setter must be callable")
);
}
// 14.c. Set desc.[[Set]] to setter.
Some(setter)
} else {
None
};
// 15. If desc.[[Get]] is present or desc.[[Set]] is present, then ...
// a. If desc.[[Value]] is present or desc.[[Writable]] is present, throw a TypeError exception.
if get.as_ref().or_else(|| set.as_ref()).is_some() && desc.inner().is_data_descriptor() {
return Err(context.construct_type_error(
"Invalid property descriptor.\
Cannot both specify accessors and a value or writable attribute",
));
}
desc = desc.maybe_get(get).maybe_set(set);
// 16. Return desc.
Ok(desc.build())
}
} }
impl AsRef<GcCell<Object>> for GcObject { impl AsRef<GcCell<Object>> for GcObject {

389
boa/src/object/internal_methods.rs

@ -7,7 +7,7 @@
use crate::{ use crate::{
object::{GcObject, Object, ObjectData}, object::{GcObject, Object, ObjectData},
property::{AccessorDescriptor, Attribute, DataDescriptor, PropertyDescriptor, PropertyKey}, property::{DescriptorKind, PropertyDescriptor, PropertyKey},
value::{Type, Value}, value::{Type, Value},
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
}; };
@ -193,10 +193,11 @@ impl GcObject {
// 1. Assert: Type(O) is Object. // 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true. // 2. Assert: IsPropertyKey(P) is true.
// 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. // 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
let new_desc = DataDescriptor::new( let new_desc = PropertyDescriptor::builder()
value, .value(value)
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, .writable(true)
); .enumerable(true)
.configurable(true);
// 4. Return ? O.[[DefineOwnProperty]](P, newDesc). // 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
self.__define_own_property__(key.into(), new_desc.into(), context) self.__define_own_property__(key.into(), new_desc.into(), context)
} }
@ -272,7 +273,7 @@ impl GcObject {
#[inline] #[inline]
pub(crate) fn __delete__(&self, key: &PropertyKey) -> bool { pub(crate) fn __delete__(&self, key: &PropertyKey) -> bool {
match self.__get_own_property__(key) { match self.__get_own_property__(key) {
Some(desc) if desc.configurable() => { Some(desc) if desc.expect_configurable() => {
self.remove(key); self.remove(key);
true true
} }
@ -297,10 +298,12 @@ impl GcObject {
Ok(Value::undefined()) Ok(Value::undefined())
} }
} }
Some(ref desc) => match desc { Some(ref desc) => match desc.kind() {
PropertyDescriptor::Data(desc) => Ok(desc.value()), DescriptorKind::Data {
PropertyDescriptor::Accessor(AccessorDescriptor { get: Some(get), .. }) => { value: Some(value), ..
get.call(&receiver, &[], context) } => Ok(value.clone()),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
context.call(get, &receiver, &[])
} }
_ => Ok(Value::undefined()), _ => Ok(Value::undefined()),
}, },
@ -323,43 +326,44 @@ impl GcObject {
} else if let Some(ref mut parent) = self.__get_prototype_of__().as_object() { } else if let Some(ref mut parent) = self.__get_prototype_of__().as_object() {
return parent.__set__(key, value, receiver, context); return parent.__set__(key, value, receiver, context);
} else { } else {
DataDescriptor::new(Value::undefined(), Attribute::all()).into() PropertyDescriptor::builder()
.value(Value::undefined())
.writable(true)
.enumerable(true)
.configurable(true)
.build()
}; };
match &own_desc { if own_desc.is_data_descriptor() {
PropertyDescriptor::Data(desc) => { if !own_desc.expect_writable() {
if !desc.writable() { return Ok(false);
}
let receiver = match receiver.as_object() {
Some(obj) => obj,
_ => return Ok(false),
};
if let Some(ref existing_desc) = receiver.__get_own_property__(&key) {
if existing_desc.is_accessor_descriptor() {
return Ok(false); return Ok(false);
} }
if let Some(ref mut receiver) = receiver.as_object() { if !existing_desc.expect_writable() {
if let Some(ref existing_desc) = receiver.__get_own_property__(&key) { return Ok(false);
match existing_desc {
PropertyDescriptor::Accessor(_) => Ok(false),
PropertyDescriptor::Data(existing_data_desc) => {
if !existing_data_desc.writable() {
return Ok(false);
}
receiver.__define_own_property__(
key,
DataDescriptor::new(value, existing_data_desc.attributes())
.into(),
context,
)
}
}
} else {
receiver.__define_own_property__(
key,
DataDescriptor::new(value, Attribute::all()).into(),
context,
)
}
} else {
Ok(false)
} }
return receiver.__define_own_property__(
key,
PropertyDescriptor::builder().value(value).build(),
context,
);
} else {
return receiver.create_data_property(key, value, context);
} }
PropertyDescriptor::Accessor(AccessorDescriptor { set: Some(set), .. }) => { }
set.call(&receiver, &[value], context)?;
match own_desc.set() {
Some(set) if !set.is_undefined() => {
context.call(set, &receiver, &[value])?;
Ok(true) Ok(true)
} }
_ => Ok(false), _ => Ok(false),
@ -391,102 +395,77 @@ impl GcObject {
let extensible = self.__is_extensible__(); let extensible = self.__is_extensible__();
let current = if let Some(desc) = self.__get_own_property__(&key) { let mut current = if let Some(own) = self.__get_own_property__(&key) {
desc own
} else { } else {
if !extensible { if !extensible {
return false; return false;
} }
self.insert(key, desc); self.insert(
key,
if desc.is_generic_descriptor() || desc.is_data_descriptor() {
desc.into_data_defaulted()
} else {
desc.into_accessor_defaulted()
},
);
return true; return true;
}; };
// 3
if desc.is_empty() {
return true;
}
// 4 // 4
if !current.configurable() { if !current.expect_configurable() {
if desc.configurable() { if matches!(desc.configurable(), Some(true)) {
return false; return false;
} }
if desc.enumerable() != current.enumerable() { if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable())
{
return false; return false;
} }
} }
match (&current, &desc) { // 5
( if desc.is_generic_descriptor() {
PropertyDescriptor::Data(current), // no further validation required
PropertyDescriptor::Accessor(AccessorDescriptor { get, set, .. }), } else if current.is_data_descriptor() != desc.is_data_descriptor() {
) => { if !current.expect_configurable() {
// 6. b return false;
if !current.configurable() { }
if current.is_data_descriptor() {
current = current.into_accessor_defaulted();
} else {
current = current.into_data_defaulted();
}
} else if current.is_data_descriptor() && desc.is_data_descriptor() {
if !current.expect_configurable() && !current.expect_writable() {
if matches!(desc.writable(), Some(true)) {
return false; return false;
} }
if matches!(desc.value(), Some(value) if !Value::same_value(value, current.expect_value()))
let current = {
AccessorDescriptor::new(get.clone(), set.clone(), current.attributes());
self.insert(key, current);
return true;
}
(
PropertyDescriptor::Accessor(current),
PropertyDescriptor::Data(DataDescriptor { value, .. }),
) => {
// 6. c
if !current.configurable() {
return false; return false;
} }
self.insert(key, DataDescriptor::new(value, current.attributes()));
return true; return true;
} }
(PropertyDescriptor::Data(current), PropertyDescriptor::Data(desc)) => { } else if !current.expect_configurable() {
// 7. if matches!(desc.set(), Some(set) if !Value::same_value(set, current.expect_set())) {
if !current.configurable() && !current.writable() { return false;
if desc.writable() {
return false;
}
if !Value::same_value(&desc.value(), &current.value()) {
return false;
}
}
} }
(PropertyDescriptor::Accessor(current), PropertyDescriptor::Accessor(desc)) => { if matches!(desc.get(), Some(get) if !Value::same_value(get, current.expect_get())) {
// 8. return false;
if !current.configurable() {
if let (Some(current_get), Some(desc_get)) = (current.getter(), desc.getter()) {
if !GcObject::equals(current_get, desc_get) {
return false;
}
}
if let (Some(current_set), Some(desc_set)) = (current.setter(), desc.setter()) {
if !GcObject::equals(current_set, desc_set) {
return false;
}
}
}
} }
return true;
} }
match (&current, &desc) { current.fill_with(desc);
(PropertyDescriptor::Data(current_data), PropertyDescriptor::Data(desc_data)) => { self.insert(key, current);
if desc_data.has_value() {
self.insert(key, desc);
} else {
self.insert(
key,
DataDescriptor::new(current_data.value.clone(), desc_data.attributes()),
);
}
}
_ => {
self.insert(key, desc);
}
}
true true
} }
@ -505,99 +484,95 @@ impl GcObject {
) -> Result<bool> { ) -> Result<bool> {
match key { match key {
PropertyKey::String(ref s) if s == "length" => { PropertyKey::String(ref s) if s == "length" => {
match desc { let new_len_val = match desc.value() {
PropertyDescriptor::Accessor(_) => { Some(value) => value,
return Ok(self.ordinary_define_own_property("length".into(), desc)) _ => return Ok(self.ordinary_define_own_property("length".into(), desc)),
} };
PropertyDescriptor::Data(ref d) => {
if d.value().is_undefined() { let new_len = new_len_val.to_u32(context)?;
return Ok(self.ordinary_define_own_property("length".into(), desc)); let number_len = new_len_val.to_number(context)?;
}
let new_len = d.value().to_u32(context)?; #[allow(clippy::float_cmp)]
let number_len = d.value().to_number(context)?; if new_len as f64 != number_len {
#[allow(clippy::float_cmp)] return Err(context.construct_range_error("bad length for array"));
if new_len as f64 != number_len { }
return Err(context.construct_range_error("bad length for array"));
} let mut new_len_desc = PropertyDescriptor::builder()
let mut new_len_desc = .value(new_len)
PropertyDescriptor::Data(DataDescriptor::new(new_len, d.attributes())); .maybe_writable(desc.writable())
let old_len_desc = self.__get_own_property__(&"length".into()).unwrap(); .maybe_enumerable(desc.enumerable())
let old_len_desc = old_len_desc.as_data_descriptor().unwrap(); .maybe_configurable(desc.configurable());
let old_len = old_len_desc.value(); let old_len_desc = self.__get_own_property__(&"length".into()).unwrap();
if new_len >= old_len.to_u32(context)? { let old_len = old_len_desc.expect_value();
return Ok( if new_len >= old_len.to_u32(context)? {
self.ordinary_define_own_property("length".into(), new_len_desc) return Ok(
self.ordinary_define_own_property("length".into(), new_len_desc.build())
);
}
if !old_len_desc.expect_writable() {
return Ok(false);
}
let new_writable = if new_len_desc.inner().writable().unwrap_or(true) {
true
} else {
new_len_desc = new_len_desc.writable(true);
false
};
if !self.ordinary_define_own_property("length".into(), new_len_desc.clone().build())
{
return Ok(false);
}
let max_value = self.borrow().index_property_keys().max().copied();
if let Some(mut index) = max_value {
while index >= new_len {
let contains_index = self.borrow().indexed_properties.contains_key(&index);
if contains_index && !self.__delete__(&index.into()) {
new_len_desc = new_len_desc.value(index + 1);
if !new_writable {
new_len_desc = new_len_desc.writable(false);
}
self.ordinary_define_own_property(
"length".into(),
new_len_desc.build(),
); );
}
if !old_len_desc.writable() {
return Ok(false); return Ok(false);
} }
let new_writable = if new_len_desc.attributes().writable() {
true index = if let Some(sub) = index.checked_sub(1) {
sub
} else { } else {
let mut new_attributes = new_len_desc.attributes(); break;
new_attributes.set_writable(true);
new_len_desc = PropertyDescriptor::Data(DataDescriptor::new(
new_len,
new_attributes,
));
false
};
if !self.ordinary_define_own_property("length".into(), new_len_desc.clone())
{
return Ok(false);
}
let keys_to_delete = {
let obj = self.borrow();
let mut keys = obj
.index_property_keys()
.filter(|&&k| k >= new_len)
.cloned()
.collect::<Vec<_>>();
keys.sort_unstable();
keys
};
for key in keys_to_delete.into_iter().rev() {
if !self.__delete__(&key.into()) {
let mut new_len_desc_attribute = new_len_desc.attributes();
if !new_writable {
new_len_desc_attribute.set_writable(false);
}
let new_len_desc = PropertyDescriptor::Data(DataDescriptor::new(
key + 1,
new_len_desc_attribute,
));
self.ordinary_define_own_property("length".into(), new_len_desc);
return Ok(false);
}
}
if !new_writable {
let mut new_desc_attr = new_len_desc.attributes();
new_desc_attr.set_writable(false);
let new_desc = PropertyDescriptor::Data(DataDescriptor::new(
new_len,
new_desc_attr,
));
self.ordinary_define_own_property("length".into(), new_desc);
} }
} }
} }
if !new_writable {
self.ordinary_define_own_property(
"length".into(),
PropertyDescriptor::builder().writable(false).build(),
);
}
Ok(true) Ok(true)
} }
PropertyKey::Index(index) => { PropertyKey::Index(index) => {
let old_len_desc = self.__get_own_property__(&"length".into()).unwrap(); let old_len_desc = self.__get_own_property__(&"length".into()).unwrap();
let old_len_data_desc = old_len_desc.as_data_descriptor().unwrap(); let old_len = old_len_desc.expect_value().to_u32(context)?;
let old_len = old_len_data_desc.value().to_u32(context)?; if index >= old_len && !old_len_desc.expect_writable() {
if index >= old_len && !old_len_data_desc.writable() {
return Ok(false); return Ok(false);
} }
if self.ordinary_define_own_property(key, desc) { if self.ordinary_define_own_property(key, desc) {
if index >= old_len && index < u32::MAX { if index >= old_len && index < u32::MAX {
let desc = PropertyDescriptor::Data(DataDescriptor::new( let desc = PropertyDescriptor::builder()
index + 1, .value(index + 1)
old_len_data_desc.attributes(), .maybe_writable(old_len_desc.writable())
)); .maybe_enumerable(old_len_desc.enumerable())
self.ordinary_define_own_property("length".into(), desc); .maybe_configurable(old_len_desc.configurable());
self.ordinary_define_own_property("length".into(), desc.into());
} }
Ok(true) Ok(true)
} else { } else {
@ -645,10 +620,12 @@ impl GcObject {
.map_or_else(|| Value::from(format!("\\u{:x}", utf16_val)), Value::from) .map_or_else(|| Value::from(format!("\\u{:x}", utf16_val)), Value::from)
})?; })?;
let desc = PropertyDescriptor::from(DataDescriptor::new( let desc = PropertyDescriptor::builder()
result_str, .value(result_str)
Attribute::READONLY | Attribute::ENUMERABLE | Attribute::PERMANENT, .writable(false)
)); .enumerable(true)
.configurable(false)
.build();
Some(desc) Some(desc)
} }
@ -719,8 +696,8 @@ impl GcObject {
for next_key in keys { for next_key in keys {
if let Some(prop_desc) = props.__get_own_property__(&next_key) { if let Some(prop_desc) = props.__get_own_property__(&next_key) {
if prop_desc.enumerable() { if prop_desc.expect_enumerable() {
let desc_obj = props.__get__(&next_key, props.clone().into(), context)?; let desc_obj = props.get(next_key.clone(), context)?;
let desc = desc_obj.to_property_descriptor(context)?; let desc = desc_obj.to_property_descriptor(context)?;
descriptors.push((next_key, desc)); descriptors.push((next_key, desc));
} }
@ -728,7 +705,7 @@ impl GcObject {
} }
for (p, d) in descriptors { for (p, d) in descriptors {
self.__define_own_property__(p, d, context)?; self.define_property_or_throw(p, d, context)?;
} }
Ok(()) Ok(())
@ -811,17 +788,12 @@ impl GcObject {
/// 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 returned. /// with that field, otherwise None is returned.
#[inline] #[inline]
pub fn insert_property<K, V>( pub fn insert_property<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor>
&self,
key: K,
value: V,
attribute: Attribute,
) -> Option<PropertyDescriptor>
where where
K: Into<PropertyKey>, K: Into<PropertyKey>,
V: Into<Value>, P: Into<PropertyDescriptor>,
{ {
self.insert(key.into(), DataDescriptor::new(value, attribute)) self.insert(key.into(), property)
} }
/// It determines if Object is a callable function with a `[[Call]]` internal method. /// It determines if Object is a callable function with a `[[Call]]` internal method.
@ -950,16 +922,11 @@ 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 fn insert_property<K, V>( pub fn insert_property<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor>
&mut self,
key: K,
value: V,
attribute: Attribute,
) -> Option<PropertyDescriptor>
where where
K: Into<PropertyKey>, K: Into<PropertyKey>,
V: Into<Value>, P: Into<PropertyDescriptor>,
{ {
self.insert(key.into(), DataDescriptor::new(value, attribute)) self.insert(key, property)
} }
} }

116
boa/src/object/mod.rs

@ -14,7 +14,7 @@ use crate::{
}, },
context::StandardConstructor, context::StandardConstructor,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
property::{AccessorDescriptor, Attribute, DataDescriptor, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
BoaProfiler, Context, JsBigInt, JsString, JsSymbol, Value, BoaProfiler, Context, JsBigInt, JsString, JsSymbol, Value,
}; };
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -767,9 +767,12 @@ impl<'context> FunctionBuilder<'context> {
.prototype() .prototype()
.into(), .into(),
); );
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; let property = PropertyDescriptor::builder()
function.insert_property("name", self.name.clone(), attribute); .writable(false)
function.insert_property("length", self.length, attribute); .enumerable(false)
.configurable(true);
function.insert_property("name", property.clone().value(self.name.clone()));
function.insert_property("length", property.value(self.length));
GcObject::new(function) GcObject::new(function)
} }
@ -785,9 +788,13 @@ impl<'context> FunctionBuilder<'context> {
.prototype() .prototype()
.into(), .into(),
); );
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
object.insert_property("name", self.name.clone(), attribute); let property = PropertyDescriptor::builder()
object.insert_property("length", self.length, attribute); .writable(false)
.enumerable(false)
.configurable(true);
object.insert_property("name", property.clone().value(self.name.clone()));
object.insert_property("length", property.value(self.length));
} }
} }
@ -799,8 +806,16 @@ impl<'context> FunctionBuilder<'context> {
/// # use boa::{Context, Value, object::ObjectInitializer, property::Attribute}; /// # use boa::{Context, Value, object::ObjectInitializer, property::Attribute};
/// let mut context = Context::new(); /// let mut context = Context::new();
/// let object = ObjectInitializer::new(&mut context) /// let object = ObjectInitializer::new(&mut context)
/// .property("hello", "world", Attribute::all()) /// .property(
/// .property(1, 1, Attribute::all()) /// "hello",
/// "world",
/// Attribute::all()
/// )
/// .property(
/// 1,
/// 1,
/// Attribute::all()
/// )
/// .function(|_, _, _| Ok(Value::undefined()), "func", 0) /// .function(|_, _, _| Ok(Value::undefined()), "func", 0)
/// .build(); /// .build();
/// ``` /// ```
@ -842,8 +857,11 @@ impl<'context> ObjectInitializer<'context> {
self.object.borrow_mut().insert_property( self.object.borrow_mut().insert_property(
binding.binding, binding.binding,
function, PropertyDescriptor::builder()
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .value(function)
.writable(true)
.enumerable(false)
.configurable(true),
); );
self self
} }
@ -855,7 +873,11 @@ impl<'context> ObjectInitializer<'context> {
K: Into<PropertyKey>, K: Into<PropertyKey>,
V: Into<Value>, V: Into<Value>,
{ {
let property = DataDescriptor::new(value, attribute); let property = PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.object.borrow_mut().insert(key, property); self.object.borrow_mut().insert(key, property);
self self
} }
@ -945,8 +967,11 @@ impl<'context> ConstructorBuilder<'context> {
self.prototype.borrow_mut().insert_property( self.prototype.borrow_mut().insert_property(
binding.binding, binding.binding,
function, PropertyDescriptor::builder()
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .value(function)
.writable(true)
.enumerable(false)
.configurable(true),
); );
self self
} }
@ -971,8 +996,11 @@ impl<'context> ConstructorBuilder<'context> {
self.constructor_object.borrow_mut().insert_property( self.constructor_object.borrow_mut().insert_property(
binding.binding, binding.binding,
function, PropertyDescriptor::builder()
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .value(function)
.writable(true)
.enumerable(false)
.configurable(true),
); );
self self
} }
@ -984,7 +1012,11 @@ impl<'context> ConstructorBuilder<'context> {
K: Into<PropertyKey>, K: Into<PropertyKey>,
V: Into<Value>, V: Into<Value>,
{ {
let property = DataDescriptor::new(value, attribute); let property = PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.prototype.borrow_mut().insert(key, property); self.prototype.borrow_mut().insert(key, property);
self self
} }
@ -996,7 +1028,11 @@ impl<'context> ConstructorBuilder<'context> {
K: Into<PropertyKey>, K: Into<PropertyKey>,
V: Into<Value>, V: Into<Value>,
{ {
let property = DataDescriptor::new(value, attribute); let property = PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.constructor_object.borrow_mut().insert(key, property); self.constructor_object.borrow_mut().insert(key, property);
self self
} }
@ -1013,7 +1049,11 @@ impl<'context> ConstructorBuilder<'context> {
where where
K: Into<PropertyKey>, K: Into<PropertyKey>,
{ {
let property = AccessorDescriptor::new(get, set, attribute); let property = PropertyDescriptor::builder()
.maybe_get(get)
.maybe_set(set)
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.prototype.borrow_mut().insert(key, property); self.prototype.borrow_mut().insert(key, property);
self self
} }
@ -1030,7 +1070,11 @@ impl<'context> ConstructorBuilder<'context> {
where where
K: Into<PropertyKey>, K: Into<PropertyKey>,
{ {
let property = AccessorDescriptor::new(get, set, attribute); let property = PropertyDescriptor::builder()
.maybe_get(get)
.maybe_set(set)
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.constructor_object.borrow_mut().insert(key, property); self.constructor_object.borrow_mut().insert(key, property);
self self
} }
@ -1122,14 +1166,16 @@ impl<'context> ConstructorBuilder<'context> {
constructable: self.constructable, constructable: self.constructable,
}; };
let length = DataDescriptor::new( let length = PropertyDescriptor::builder()
self.length, .value(self.length)
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .writable(false)
); .enumerable(false)
let name = DataDescriptor::new( .configurable(true);
self.name.clone(), let name = PropertyDescriptor::builder()
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .value(self.name.clone())
); .writable(false)
.enumerable(false)
.configurable(true);
{ {
let mut constructor = self.constructor_object.borrow_mut(); let mut constructor = self.constructor_object.borrow_mut();
@ -1147,8 +1193,11 @@ impl<'context> ConstructorBuilder<'context> {
constructor.insert_property( constructor.insert_property(
PROTOTYPE, PROTOTYPE,
self.prototype.clone(), PropertyDescriptor::builder()
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, .value(self.prototype.clone())
.writable(false)
.enumerable(false)
.configurable(false),
); );
} }
@ -1156,8 +1205,11 @@ impl<'context> ConstructorBuilder<'context> {
let mut prototype = self.prototype.borrow_mut(); let mut prototype = self.prototype.borrow_mut();
prototype.insert_property( prototype.insert_property(
"constructor", "constructor",
self.constructor_object.clone(), PropertyDescriptor::builder()
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, .value(self.constructor_object.clone())
.writable(true)
.enumerable(false)
.configurable(true),
); );
if let Some(proto) = self.inherit.take() { if let Some(proto) = self.inherit.take() {

531
boa/src/property/mod.rs

@ -16,7 +16,6 @@
use crate::{ use crate::{
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::GcObject,
JsString, JsSymbol, Value, JsString, JsSymbol, Value,
}; };
use std::{convert::TryFrom, fmt}; use std::{convert::TryFrom, fmt};
@ -24,7 +23,21 @@ use std::{convert::TryFrom, fmt};
mod attribute; mod attribute;
pub use attribute::Attribute; pub use attribute::Attribute;
/// A data descriptor is a property that has a value, which may or may not be writable. /// This represents a JavaScript Property AKA The Property Descriptor.
///
/// Property descriptors present in objects come in three main flavors:
/// - data descriptors
/// - accessor descriptors
/// - generic descriptor
///
/// A data Property Descriptor is one that includes any fields named either
/// \[\[Value\]\] or \[\[Writable\]\].
///
/// An accessor Property Descriptor is one that includes any fields named either
/// \[\[Get\]\] or \[\[Set\]\].
///
/// A generic Property Descriptor is a Property Descriptor value that is neither
/// a data Property Descriptor nor an accessor Property Descriptor.
/// ///
/// More information: /// More information:
/// - [MDN documentation][mdn] /// - [MDN documentation][mdn]
@ -32,285 +45,423 @@ pub use attribute::Attribute;
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type /// [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
#[derive(Default, Debug, Clone, Trace, Finalize)]
pub struct PropertyDescriptor {
enumerable: Option<bool>,
configurable: Option<bool>,
kind: DescriptorKind,
}
#[derive(Debug, Clone, Trace, Finalize)] #[derive(Debug, Clone, Trace, Finalize)]
pub struct DataDescriptor { pub enum DescriptorKind {
pub(crate) value: Value, Data {
attributes: Attribute, value: Option<Value>,
has_value: bool, writable: Option<bool>,
},
Accessor {
get: Option<Value>,
set: Option<Value>,
},
Generic,
} }
impl DataDescriptor { impl Default for DescriptorKind {
/// Create a new `DataDescriptor`. fn default() -> Self {
#[inline] Self::Generic
pub fn new<V>(value: V, attributes: Attribute) -> Self
where
V: Into<Value>,
{
Self {
value: value.into(),
attributes,
has_value: true,
}
} }
}
/// Create a new `DataDescriptor` without a value. impl PropertyDescriptor {
/// An accessor Property Descriptor is one that includes any fields named either `[[Get]]` or `[[Set]]`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isaccessordescriptor
#[inline] #[inline]
pub fn new_without_value(attributes: Attribute) -> Self { pub fn is_accessor_descriptor(&self) -> bool {
Self { matches!(self.kind, DescriptorKind::Accessor { .. })
value: Value::undefined(),
attributes,
has_value: false,
}
} }
/// Return the `value` of the data descriptor. /// A data Property Descriptor is one that includes any fields named either `[[Value]]` or `[[Writable]]`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isdatadescriptor
#[inline] #[inline]
pub fn value(&self) -> Value { pub fn is_data_descriptor(&self) -> bool {
self.value.clone() matches!(self.kind, DescriptorKind::Data { .. })
} }
/// Check whether the data descriptor has a value. /// A generic Property Descriptor is one that is neither a data descriptor nor an accessor descriptor.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isgenericdescriptor
#[inline] #[inline]
pub fn has_value(&self) -> bool { pub fn is_generic_descriptor(&self) -> bool {
self.has_value matches!(self.kind, DescriptorKind::Generic)
} }
/// Return the attributes of the descriptor.
#[inline] #[inline]
pub fn attributes(&self) -> Attribute { pub fn is_empty(&self) -> bool {
self.attributes self.is_generic_descriptor() && self.enumerable.is_none() && self.configurable.is_none()
} }
/// Check whether the descriptor is configurable.
#[inline] #[inline]
pub fn configurable(&self) -> bool { pub fn enumerable(&self) -> Option<bool> {
self.attributes.configurable() self.enumerable
} }
/// Set whether the descriptor is configurable.
#[inline] #[inline]
pub fn set_configurable(&mut self, configurable: bool) { pub fn configurable(&self) -> Option<bool> {
self.attributes.set_configurable(configurable) self.configurable
} }
/// Check whether the descriptor is enumerable.
#[inline] #[inline]
pub fn enumerable(&self) -> bool { pub fn writable(&self) -> Option<bool> {
self.attributes.enumerable() match self.kind {
DescriptorKind::Data { writable, .. } => writable,
_ => None,
}
} }
/// Set whether the descriptor is enumerable.
#[inline] #[inline]
pub fn set_enumerable(&mut self, enumerable: bool) { pub fn value(&self) -> Option<&Value> {
self.attributes.set_enumerable(enumerable) match &self.kind {
DescriptorKind::Data { value, .. } => value.as_ref(),
_ => None,
}
} }
/// Check whether the descriptor is writable.
#[inline] #[inline]
pub fn writable(&self) -> bool { pub fn get(&self) -> Option<&Value> {
self.attributes.writable() match &self.kind {
DescriptorKind::Accessor { get, .. } => get.as_ref(),
_ => None,
}
} }
/// Set whether the descriptor is writable.
#[inline] #[inline]
pub fn set_writable(&mut self, writable: bool) { pub fn set(&self) -> Option<&Value> {
self.attributes.set_writable(writable) match &self.kind {
DescriptorKind::Accessor { set, .. } => set.as_ref(),
_ => None,
}
} }
}
impl From<DataDescriptor> for PropertyDescriptor {
#[inline] #[inline]
fn from(value: DataDescriptor) -> Self { pub fn expect_enumerable(&self) -> bool {
Self::Data(value) if let Some(enumerable) = self.enumerable {
enumerable
} else {
panic!("[[enumerable]] field not in property descriptor")
}
} }
}
/// An accessor descriptor is a property described by a getter-setter pair of functions.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
#[derive(Debug, Clone, Trace, Finalize)]
pub struct AccessorDescriptor {
/// The function serving as getter.
pub(crate) get: Option<GcObject>,
/// The function serving as setter.
pub(crate) set: Option<GcObject>,
/// The attributes of the accessor descriptor.
pub(crate) attributes: Attribute,
}
impl AccessorDescriptor {
/// Create a new `AccessorDescriptor`.
///
/// If the `attributes` argument contains a `writable` flag, it will be removed so only `enumerable`
/// and `configurable` remains.
#[inline] #[inline]
pub fn new(get: Option<GcObject>, set: Option<GcObject>, mut attributes: Attribute) -> Self { pub fn expect_configurable(&self) -> bool {
// Accessors can not have writable attribute. if let Some(configurable) = self.configurable {
attributes.remove(Attribute::WRITABLE); configurable
Self { } else {
get, panic!("[[configurable]] field not in property descriptor")
set,
attributes,
} }
} }
/// Return the getter if it exists.
#[inline] #[inline]
pub fn getter(&self) -> Option<&GcObject> { pub fn expect_writable(&self) -> bool {
self.get.as_ref() if let Some(writable) = self.writable() {
writable
} else {
panic!("[[writable]] field not in property descriptor")
}
} }
/// Return the setter if it exists.
#[inline] #[inline]
pub fn setter(&self) -> Option<&GcObject> { pub fn expect_value(&self) -> &Value {
self.set.as_ref() if let Some(value) = self.value() {
value
} else {
panic!("[[value]] field not in property descriptor")
}
} }
/// Set the getter of the accessor descriptor.
#[inline] #[inline]
pub fn set_getter(&mut self, get: Option<GcObject>) { pub fn expect_get(&self) -> &Value {
self.get = get; if let Some(get) = self.get() {
get
} else {
panic!("[[get]] field not in property descriptor")
}
} }
/// Set the setter of the accessor descriptor.
#[inline] #[inline]
pub fn set_setter(&mut self, set: Option<GcObject>) { pub fn expect_set(&self) -> &Value {
self.set = set; if let Some(set) = self.set() {
set
} else {
panic!("[[set]] field not in property descriptor")
}
} }
/// Return the attributes of the accessor descriptor.
///
/// It is guaranteed to not contain a `writable` flag
#[inline] #[inline]
pub fn attributes(&self) -> Attribute { pub fn kind(&self) -> &DescriptorKind {
self.attributes &self.kind
} }
/// Check whether the descriptor is configurable.
#[inline] #[inline]
pub fn configurable(&self) -> bool { pub fn builder() -> PropertyDescriptorBuilder {
self.attributes.configurable() PropertyDescriptorBuilder::new()
} }
/// Set whether the descriptor is configurable.
#[inline] #[inline]
pub fn set_configurable(&mut self, configurable: bool) { pub fn into_accessor_defaulted(mut self) -> Self {
self.attributes.set_configurable(configurable) self.kind = DescriptorKind::Accessor {
get: self.get().cloned(),
set: self.set().cloned(),
};
PropertyDescriptorBuilder { inner: self }
.complete_with_defaults()
.build()
} }
/// Check whether the descriptor is enumerable. pub fn into_data_defaulted(mut self) -> Self {
#[inline] self.kind = DescriptorKind::Data {
pub fn enumerable(&self) -> bool { value: self.value().cloned(),
self.attributes.enumerable() writable: self.writable(),
};
PropertyDescriptorBuilder { inner: self }
.complete_with_defaults()
.build()
} }
/// Set whether the descriptor is enumerable.
#[inline] #[inline]
pub fn set_enumerable(&mut self, enumerable: bool) { pub fn complete_property_descriptor(self) -> Self {
self.attributes.set_enumerable(enumerable) PropertyDescriptorBuilder { inner: self }
.complete_with_defaults()
.build()
} }
}
impl From<AccessorDescriptor> for PropertyDescriptor {
#[inline] #[inline]
fn from(value: AccessorDescriptor) -> Self { pub fn fill_with(&mut self, desc: Self) {
Self::Accessor(value) match (&mut self.kind, &desc.kind) {
(
DescriptorKind::Data { value, writable },
DescriptorKind::Data {
value: desc_value,
writable: desc_writable,
},
) => {
if let Some(desc_value) = desc_value {
*value = Some(desc_value.clone())
}
if let Some(desc_writable) = desc_writable {
*writable = Some(*desc_writable)
}
}
(
DescriptorKind::Accessor { get, set },
DescriptorKind::Accessor {
get: desc_get,
set: desc_set,
},
) => {
if let Some(desc_get) = desc_get {
*get = Some(desc_get.clone())
}
if let Some(desc_set) = desc_set {
*set = Some(desc_set.clone())
}
}
(_, DescriptorKind::Generic) => {}
_ => panic!("Tried to fill a descriptor with an incompatible descriptor"),
}
if let Some(enumerable) = desc.enumerable {
self.enumerable = Some(enumerable)
}
if let Some(configurable) = desc.configurable {
self.configurable = Some(configurable)
}
} }
} }
/// This represents a JavaScript Property AKA The Property Descriptor. #[derive(Default, Debug, Clone)]
/// pub struct PropertyDescriptorBuilder {
/// Property descriptors present in objects come in two main flavors: inner: PropertyDescriptor,
/// - data descriptors
/// - accessor descriptors
///
/// A data descriptor is a property that has a value, which may or may not be writable.
/// An accessor descriptor is a property described by a getter-setter pair of functions.
/// A descriptor must be one of these two flavors; it cannot be both.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
#[derive(Debug, Clone, Trace, Finalize)]
pub enum PropertyDescriptor {
Accessor(AccessorDescriptor),
Data(DataDescriptor),
} }
impl PropertyDescriptor { impl PropertyDescriptorBuilder {
/// An accessor Property Descriptor is one that includes any fields named either `[[Get]]` or `[[Set]]`. pub fn new() -> Self {
/// Self::default()
/// More information: }
/// - [ECMAScript reference][spec]
/// pub fn value<V: Into<Value>>(mut self, value: V) -> Self {
/// [spec]: https://tc39.es/ecma262/#sec-isaccessordescriptor match self.inner.kind {
#[inline] DescriptorKind::Data {
pub fn is_accessor_descriptor(&self) -> bool { value: ref mut v, ..
matches!(self, Self::Accessor(_)) } => *v = Some(value.into()),
// TODO: maybe panic when trying to convert accessor to data?
_ => {
self.inner.kind = DescriptorKind::Data {
value: Some(value.into()),
writable: None,
}
}
}
self
}
pub fn writable(mut self, writable: bool) -> Self {
match self.inner.kind {
DescriptorKind::Data {
writable: ref mut w,
..
} => *w = Some(writable),
// TODO: maybe panic when trying to convert accessor to data?
_ => {
self.inner.kind = DescriptorKind::Data {
value: None,
writable: Some(writable),
}
}
}
self
} }
/// Return `Some()` if it is a accessor descriptor, `None` otherwise. pub fn get<V: Into<Value>>(mut self, get: V) -> Self {
#[inline] match self.inner.kind {
pub fn as_accessor_descriptor(&self) -> Option<&AccessorDescriptor> { DescriptorKind::Accessor { get: ref mut g, .. } => *g = Some(get.into()),
match self { // TODO: maybe panic when trying to convert data to accessor?
Self::Accessor(ref accessor) => Some(accessor), _ => {
_ => None, self.inner.kind = DescriptorKind::Accessor {
get: Some(get.into()),
set: None,
}
}
} }
self
} }
/// A data Property Descriptor is one that includes any fields named either `[[Value]]` or `[[Writable]]`. pub fn set<V: Into<Value>>(mut self, set: V) -> Self {
/// match self.inner.kind {
/// More information: DescriptorKind::Accessor { set: ref mut s, .. } => *s = Some(set.into()),
/// - [ECMAScript reference][spec] // TODO: maybe panic when trying to convert data to accessor?
/// _ => {
/// [spec]: https://tc39.es/ecma262/#sec-isdatadescriptor self.inner.kind = DescriptorKind::Accessor {
#[inline] set: Some(set.into()),
pub fn is_data_descriptor(&self) -> bool { get: None,
matches!(self, Self::Data(_)) }
}
}
self
} }
/// Return `Some()` if it is a data descriptor, `None` otherwise. pub fn maybe_enumerable(mut self, enumerable: Option<bool>) -> Self {
#[inline] if let Some(enumerable) = enumerable {
pub fn as_data_descriptor(&self) -> Option<&DataDescriptor> { self = self.enumerable(enumerable);
match self {
Self::Data(ref data) => Some(data),
_ => None,
} }
self
} }
/// Check whether the descriptor is enumerable. pub fn maybe_configurable(mut self, configurable: Option<bool>) -> Self {
#[inline] if let Some(configurable) = configurable {
pub fn enumerable(&self) -> bool { self = self.configurable(configurable);
match self {
Self::Accessor(ref accessor) => accessor.enumerable(),
Self::Data(ref data) => data.enumerable(),
} }
self
} }
/// Check whether the descriptor is configurable. pub fn maybe_value<V: Into<Value>>(mut self, value: Option<V>) -> Self {
#[inline] if let Some(value) = value {
pub fn configurable(&self) -> bool { self = self.value(value);
match self {
Self::Accessor(ref accessor) => accessor.configurable(),
Self::Data(ref data) => data.configurable(),
} }
self
} }
/// Return the attributes of the descriptor. pub fn maybe_writable(mut self, writable: Option<bool>) -> Self {
#[inline] if let Some(writable) = writable {
pub fn attributes(&self) -> Attribute { self = self.writable(writable);
match self {
Self::Accessor(ref accessor) => accessor.attributes(),
Self::Data(ref data) => data.attributes(),
} }
self
}
pub fn maybe_get<V: Into<Value>>(mut self, get: Option<V>) -> Self {
if let Some(get) = get {
self = self.get(get);
}
self
}
pub fn maybe_set<V: Into<Value>>(mut self, set: Option<V>) -> Self {
if let Some(set) = set {
self = self.set(set);
}
self
}
pub fn enumerable(mut self, enumerable: bool) -> Self {
self.inner.enumerable = Some(enumerable);
self
}
pub fn configurable(mut self, configurable: bool) -> Self {
self.inner.configurable = Some(configurable);
self
}
pub fn complete_with_defaults(mut self) -> Self {
match self.inner.kind {
DescriptorKind::Generic => {
self.inner.kind = DescriptorKind::Data {
value: Some(Value::undefined()),
writable: Some(false),
}
}
DescriptorKind::Data {
ref mut value,
ref mut writable,
} => {
if value.is_none() {
*value = Some(Value::undefined())
}
if writable.is_none() {
*writable = Some(false)
}
}
DescriptorKind::Accessor {
ref mut set,
ref mut get,
} => {
if set.is_none() {
*set = Some(Value::undefined())
}
if get.is_none() {
*get = Some(Value::undefined())
}
}
}
if self.inner.configurable.is_none() {
self.inner.configurable = Some(false);
}
if self.inner.enumerable.is_none() {
self.inner.enumerable = Some(false);
}
self
}
pub fn inner(&self) -> &PropertyDescriptor {
&self.inner
}
pub fn build(self) -> PropertyDescriptor {
self.inner
}
}
impl From<PropertyDescriptorBuilder> for PropertyDescriptor {
fn from(builder: PropertyDescriptorBuilder) -> Self {
builder.build()
} }
} }

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

@ -92,7 +92,9 @@ impl Executable for ForInLoop {
let for_in_iterator = ForInIterator::create_for_in_iterator(context, Value::from(object)); let for_in_iterator = ForInIterator::create_for_in_iterator(context, Value::from(object));
let next_function = for_in_iterator let next_function = for_in_iterator
.get_property("next") .get_property("next")
.map(|p| p.as_data_descriptor().unwrap().value()) .as_ref()
.map(|p| p.expect_value())
.cloned()
.ok_or_else(|| context.construct_type_error("Could not find property `next`"))?; .ok_or_else(|| context.construct_type_error("Could not find property `next`"))?;
let iterator = IteratorRecord::new(for_in_iterator, next_function); let iterator = IteratorRecord::new(for_in_iterator, next_function);

52
boa/src/syntax/ast/node/object/mod.rs

@ -3,7 +3,7 @@
use crate::{ use crate::{
exec::Executable, exec::Executable,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
property::{AccessorDescriptor, Attribute, DataDescriptor, PropertyDescriptor}, property::PropertyDescriptor,
syntax::ast::node::{join_nodes, MethodDefinitionKind, Node, PropertyDefinition}, syntax::ast::node::{join_nodes, MethodDefinitionKind, Node, PropertyDefinition},
BoaProfiler, Context, Result, Value, BoaProfiler, Context, Result, Value,
}; };
@ -97,54 +97,52 @@ impl Executable for Object {
PropertyDefinition::Property(key, value) => { PropertyDefinition::Property(key, value) => {
obj.set_property( obj.set_property(
key.clone(), key.clone(),
PropertyDescriptor::Data(DataDescriptor::new( PropertyDescriptor::builder()
value.run(context)?, .value(value.run(context)?)
Attribute::all(), .writable(true)
)), .enumerable(true)
.configurable(true),
); );
} }
PropertyDefinition::MethodDefinition(kind, name, func) => match kind { PropertyDefinition::MethodDefinition(kind, name, func) => match kind {
MethodDefinitionKind::Ordinary => { MethodDefinitionKind::Ordinary => {
obj.set_property( obj.set_property(
name.clone(), name.clone(),
PropertyDescriptor::Data(DataDescriptor::new( PropertyDescriptor::builder()
func.run(context)?, .value(func.run(context)?)
Attribute::all(), .writable(true)
)), .enumerable(true)
.configurable(true),
); );
} }
MethodDefinitionKind::Get => { MethodDefinitionKind::Get => {
let set = obj let set = obj
.get_property(name.clone()) .get_property(name.clone())
.as_ref() .as_ref()
.and_then(|p| p.as_accessor_descriptor()) .and_then(|a| a.set())
.and_then(|a| a.setter().cloned()); .cloned();
obj.set_property( obj.set_property(
name.clone(), name.clone(),
PropertyDescriptor::Accessor(AccessorDescriptor { PropertyDescriptor::builder()
get: func.run(context)?.as_object(), .maybe_get(func.run(context)?.as_object())
set, .maybe_set(set)
attributes: Attribute::WRITABLE .enumerable(true)
| Attribute::ENUMERABLE .configurable(true),
| Attribute::CONFIGURABLE,
}),
) )
} }
MethodDefinitionKind::Set => { MethodDefinitionKind::Set => {
let get = obj let get = obj
.get_property(name.clone()) .get_property(name.clone())
.as_ref() .as_ref()
.and_then(|p| p.as_accessor_descriptor()) .and_then(|a| a.get())
.and_then(|a| a.getter().cloned()); .cloned();
obj.set_property( obj.set_property(
name.clone(), name.clone(),
PropertyDescriptor::Accessor(AccessorDescriptor { PropertyDescriptor::builder()
get, .maybe_get(get)
set: func.run(context)?.as_object(), .maybe_set(func.run(context)?.as_object())
attributes: Attribute::WRITABLE .enumerable(true)
| Attribute::ENUMERABLE .configurable(true),
| Attribute::CONFIGURABLE,
}),
) )
} }
}, },

18
boa/src/value/conversions.rs

@ -146,7 +146,14 @@ 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.insert(i, DataDescriptor::new(item.clone(), Attribute::all())); array.insert(
i,
PropertyDescriptor::builder()
.value(item.clone())
.writable(true)
.enumerable(true)
.configurable(true),
);
} }
Self::from(array) Self::from(array)
} }
@ -159,7 +166,14 @@ 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.insert(i, DataDescriptor::new(item, Attribute::all())); array.insert(
i,
PropertyDescriptor::builder()
.value(item)
.writable(true)
.enumerable(true)
.configurable(true),
);
} }
Value::from(array) Value::from(array)
} }

35
boa/src/value/display.rs

@ -49,10 +49,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 $obj, |(key, val)| { print_obj_value!(impl $obj, |(key, val)| {
if val.is_data_descriptor() { if val.is_data_descriptor() {
let v = &val let v = &val.expect_value();
.as_data_descriptor()
.unwrap()
.value();
format!( format!(
"{:>width$}: {}", "{:>width$}: {}",
key, key,
@ -60,8 +57,7 @@ macro_rules! print_obj_value {
width = $indent, width = $indent,
) )
} else { } else {
let accessor = val.as_accessor_descriptor().unwrap(); let display = match (val.set().is_some(), val.get().is_some()) {
let display = match (accessor.setter().is_some(), accessor.getter().is_some()) {
(true, true) => "Getter & Setter", (true, true) => "Getter & Setter",
(true, false) => "Setter", (true, false) => "Setter",
(false, true) => "Getter", (false, true) => "Getter",
@ -106,9 +102,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children:
// TODO: do this in a better way `unwrap` // TODO: do this in a better way `unwrap`
.unwrap() .unwrap()
// FIXME: handle accessor descriptors // FIXME: handle accessor descriptors
.as_data_descriptor() .expect_value()
.unwrap()
.value()
.as_number() .as_number()
.map(|n| n as i32) .map(|n| n as i32)
.unwrap_or_default(); .unwrap_or_default();
@ -123,10 +117,11 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children:
// Introduce recursive call to stringify any objects // Introduce recursive call to stringify any objects
// which are part of the Array // which are part of the Array
log_string_from( log_string_from(
&v.__get_own_property__(&i.into()) v.__get_own_property__(&i.into())
.as_ref()
// FIXME: handle accessor descriptors // FIXME: handle accessor descriptors
.and_then(|p| p.as_data_descriptor().map(|d| d.value())) .and_then(|p| p.value())
.unwrap_or_default(), .unwrap_or(&Value::Undefined),
print_internals, print_internals,
false, false,
) )
@ -204,16 +199,18 @@ pub(crate) fn display_obj(v: &Value, print_internals: bool) -> String {
let name = v let name = v
.get_property("name") .get_property("name")
.as_ref() .as_ref()
.and_then(|p| p.as_data_descriptor()) .and_then(|d| d.value())
.map(|d| d.value()) .unwrap_or(&Value::Undefined)
.unwrap_or_else(Value::undefined); .display()
.to_string();
let message = v let message = v
.get_property("message") .get_property("message")
.as_ref() .as_ref()
.and_then(|p| p.as_data_descriptor()) .and_then(|d| d.value())
.map(|d| d.value()) .unwrap_or(&Value::Undefined)
.unwrap_or_else(Value::undefined); .display()
return format!("{}: {}", name.display(), message.display()); .to_string();
return format!("{}: {}", name, message);
} }
} }

44
boa/src/value/mod.rs

@ -12,7 +12,7 @@ use crate::{
Number, Number,
}, },
object::{GcObject, Object, ObjectData}, object::{GcObject, Object, ObjectData},
property::{Attribute, DataDescriptor, PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
symbol::{JsSymbol, WellKnownSymbols}, symbol::{JsSymbol, WellKnownSymbols},
BoaProfiler, Context, JsBigInt, JsString, Result, BoaProfiler, Context, JsBigInt, JsString, Result,
}; };
@ -190,16 +190,21 @@ impl Value {
for (idx, json) in vs.into_iter().enumerate() { for (idx, json) in vs.into_iter().enumerate() {
new_obj.set_property( new_obj.set_property(
idx.to_string(), idx.to_string(),
DataDescriptor::new( PropertyDescriptor::builder()
Self::from_json(json, context), .value(Self::from_json(json, context))
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, .writable(true)
), .enumerable(true)
.configurable(true),
); );
} }
new_obj.set_property( new_obj.set_property(
"length", "length",
// TODO: Fix length attribute // TODO: Fix length attribute
DataDescriptor::new(length, Attribute::all()), PropertyDescriptor::builder()
.value(length)
.writable(true)
.enumerable(true)
.configurable(true),
); );
new_obj new_obj
} }
@ -209,10 +214,11 @@ impl Value {
let value = Self::from_json(json, context); let value = Self::from_json(json, context);
new_obj.set_property( new_obj.set_property(
key, key,
DataDescriptor::new( PropertyDescriptor::builder()
value, .value(value)
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, .writable(true)
), .enumerable(true)
.configurable(true),
); );
} }
new_obj new_obj
@ -704,9 +710,12 @@ impl Value {
)); ));
// Make sure the correct length is set on our new string object // Make sure the correct length is set on our new string object
object.insert_property( object.insert_property(
PropertyKey::String("length".into()), "length",
Value::from(string.encode_utf16().count()), PropertyDescriptor::builder()
Attribute::NON_ENUMERABLE, .value(string.encode_utf16().count())
.writable(false)
.enumerable(false)
.configurable(false),
); );
Ok(object) Ok(object)
} }
@ -925,10 +934,11 @@ impl Value {
#[inline] #[inline]
pub fn to_property_descriptor(&self, context: &mut Context) -> Result<PropertyDescriptor> { pub fn to_property_descriptor(&self, context: &mut Context) -> Result<PropertyDescriptor> {
if let Self::Object(ref object) = self { // 1. If Type(Obj) is not Object, throw a TypeError exception.
object.to_property_descriptor(context) match self {
} else { Value::Object(ref obj) => obj.to_property_descriptor(context),
Err(context.construct_type_error("Property description must be an object")) _ => Err(context
.construct_type_error("Cannot construct a property descriptor from a non-object")),
} }
} }

6
boa/src/value/tests.rs

@ -269,7 +269,7 @@ fn string_length_is_not_enumerable() {
let length_desc = object let length_desc = object
.__get_own_property__(&PropertyKey::from("length")) .__get_own_property__(&PropertyKey::from("length"))
.unwrap(); .unwrap();
assert!(!length_desc.enumerable()); assert!(!length_desc.expect_enumerable());
} }
#[test] #[test]
@ -283,9 +283,7 @@ fn string_length_is_in_utf16_codeunits() {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
length_desc length_desc
.as_data_descriptor() .expect_value()
.unwrap()
.value()
.to_integer_or_infinity(&mut context) .to_integer_or_infinity(&mut context)
.unwrap(), .unwrap(),
IntegerOrInfinity::Integer(2) IntegerOrInfinity::Integer(2)

2
boa_tester/src/exec/js262.rs

@ -16,7 +16,7 @@ pub(super) fn init(context: &mut Context) -> GcObject {
// .property("agent", agent, Attribute::default()) // .property("agent", agent, Attribute::default())
.build(); .build();
context.register_global_property("$262", obj.clone(), Attribute::default()); context.register_global_property("$262", obj.clone(), Attribute::empty());
obj obj
} }

Loading…
Cancel
Save