diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index f4c9cc4a9e6..844cefadda7 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -273,32 +273,29 @@ impl Array { array } - /// Creates a new `Array` instance. - pub(crate) fn new_array(context: &mut Context) -> JsValue { - Self::array_create(0, None, context) - .expect("creating an empty array with the default prototype must not fail") - .into() - } - /// Utility function for concatenating array objects. /// /// Returns a Boolean valued property that if `true` indicates that /// an object should be flattened to its array elements /// by `Array.prototype.concat`. - fn is_concat_spreadable(this: &JsValue, context: &mut Context) -> JsResult { + fn is_concat_spreadable(o: &JsValue, context: &mut Context) -> JsResult { // 1. If Type(O) is not Object, return false. - if !this.is_object() { + let o = if let Some(o) = o.as_object() { + o + } else { return Ok(false); - } + }; + // 2. Let spreadable be ? Get(O, @@isConcatSpreadable). - let spreadable = this.get_field(WellKnownSymbols::is_concat_spreadable(), context)?; + let spreadable = o.get(WellKnownSymbols::is_concat_spreadable(), context)?; // 3. If spreadable is not undefined, return ! ToBoolean(spreadable). if !spreadable.is_undefined() { return Ok(spreadable.to_boolean()); } + // 4. Return ? IsArray(O). - this.is_array(context) + o.is_array_abstract(context) } /// `get Array [ @@species ]` @@ -374,37 +371,6 @@ impl Array { } } - /// Utility function which takes an existing array object and puts additional - /// values on the end, correctly rewriting the length - pub(crate) fn add_to_array_object( - array_ptr: &JsValue, - add_values: &[JsValue], - context: &mut Context, - ) -> JsResult { - let orig_length = array_ptr.get_field("length", context)?.to_length(context)?; - - for (n, value) in add_values.iter().enumerate() { - let new_index = orig_length.wrapping_add(n); - array_ptr.set_property( - new_index, - PropertyDescriptor::builder() - .value(value) - .configurable(true) - .enumerable(true) - .writable(true), - ); - } - - array_ptr.set_field( - "length", - JsValue::new(orig_length.wrapping_add(add_values.len())), - false, - context, - )?; - - Ok(array_ptr.clone()) - } - /// `Array.isArray( arg )` /// /// The isArray function takes one argument arg, and returns the Boolean value true diff --git a/boa_engine/src/builtins/array/tests.rs b/boa_engine/src/builtins/array/tests.rs index c9fa6cd6f48..54499815370 100644 --- a/boa_engine/src/builtins/array/tests.rs +++ b/boa_engine/src/builtins/array/tests.rs @@ -1543,8 +1543,12 @@ fn get_relative_end() { fn array_length_is_not_enumerable() { let mut context = Context::default(); - let array = Array::new_array(&mut context); - let desc = array.get_property("length").unwrap(); + let array = + Array::array_create(0, None, &mut context).expect("could not create an empty array"); + let desc = array + .__get_own_property__(&"length".into(), &mut context) + .expect("accessing length property on array should not throw") + .expect("there should always be a length property on arrays"); assert!(!desc.expect_enumerable()); } diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index 42291a379ea..ca9e7db09cf 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -17,7 +17,7 @@ use crate::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, }, property::Attribute, - Context, JsResult, JsValue, + Context, JsResult, JsString, JsValue, }; use boa_profiler::Profiler; @@ -108,33 +108,46 @@ impl Error { _: &[JsValue], context: &mut Context, ) -> JsResult { - if !this.is_object() { + // 1. Let O be the this value. + let o = if let Some(o) = this.as_object() { + o + // 2. If Type(O) is not Object, throw a TypeError exception. + } else { return context.throw_type_error("'this' is not an Object"); - } - let name = this.get_field("name", context)?; - let name_to_string; + }; + + // 3. Let name be ? Get(O, "name"). + let name = o.get("name", context)?; + + // 4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name). let name = if name.is_undefined() { - "Error" + JsString::new("Error") } else { - name_to_string = name.to_string(context)?; - name_to_string.as_str() + name.to_string(context)? }; - let message = this.get_field("message", context)?; - let message_to_string; - let message = if message.is_undefined() { - "" + // 5. Let msg be ? Get(O, "message"). + let msg = o.get("message", context)?; + + // 6. If msg is undefined, set msg to the empty String; otherwise set msg to ? ToString(msg). + let msg = if msg.is_undefined() { + JsString::empty() } else { - message_to_string = message.to_string(context)?; - message_to_string.as_str() + msg.to_string(context)? }; + // 7. If name is the empty String, return msg. if name.is_empty() { - Ok(message.into()) - } else if message.is_empty() { - Ok(name.into()) - } else { - Ok(format!("{name}: {message}").into()) + return Ok(msg.into()); } + + // 8. If msg is the empty String, return name. + if msg.is_empty() { + return Ok(name.into()); + } + + // 9. Return the string-concatenation of name, the code unit 0x003A (COLON), + // the code unit 0x0020 (SPACE), and msg. + Ok(format!("{name}: {msg}").into()) } } diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index c443082aff1..fc2596f980e 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -373,7 +373,7 @@ impl Json { // 2. If Type(value) is Object or BigInt, then if value.is_object() || value.is_bigint() { // a. Let toJSON be ? GetV(value, "toJSON"). - let to_json = value.get_field("toJSON", context)?; + let to_json = value.get_v("toJSON", context)?; // b. If IsCallable(toJSON) is true, then if let Some(obj) = to_json.as_object() { diff --git a/boa_engine/src/builtins/json/tests.rs b/boa_engine/src/builtins/json/tests.rs index 9d473ffb421..be350aae4a9 100644 --- a/boa_engine/src/builtins/json/tests.rs +++ b/boa_engine/src/builtins/json/tests.rs @@ -343,7 +343,7 @@ fn json_parse_array_with_reviver() { .unwrap(); assert_eq!( result - .get_field("0", &mut context) + .get_v("0", &mut context) .unwrap() .to_number(&mut context) .unwrap() as u8, @@ -351,7 +351,7 @@ fn json_parse_array_with_reviver() { ); assert_eq!( result - .get_field("1", &mut context) + .get_v("1", &mut context) .unwrap() .to_number(&mut context) .unwrap() as u8, @@ -359,7 +359,7 @@ fn json_parse_array_with_reviver() { ); assert_eq!( result - .get_field("2", &mut context) + .get_v("2", &mut context) .unwrap() .to_number(&mut context) .unwrap() as u8, @@ -367,7 +367,7 @@ fn json_parse_array_with_reviver() { ); assert_eq!( result - .get_field("3", &mut context) + .get_v("3", &mut context) .unwrap() .to_number(&mut context) .unwrap() as u8, diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index 5a369054cc1..3698194da1f 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -2065,6 +2065,9 @@ pub(crate) fn get_substitution( result.push_str("$<"); } else { // a. Assert: Type(namedCaptures) is Object. + let named_captures = named_captures + .as_object() + .expect("should be an object according to spec"); // b. Scan until the next > U+003E (GREATER-THAN SIGN). let mut group_name = StdString::new(); @@ -2089,7 +2092,7 @@ pub(crate) fn get_substitution( } else { // i. Let groupName be the enclosed substring. // ii. Let capture be ? Get(namedCaptures, groupName). - let capture = named_captures.get_field(group_name, context)?; + let capture = named_captures.get(group_name, context)?; // iii. If capture is undefined, replace the text through > with the empty String. // iv. Otherwise, replace the text through > with ? ToString(capture). diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index 02db55f6e16..bec8ff14525 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -449,12 +449,14 @@ impl JsObject { } // 4. If Type(C) is not Object, throw a TypeError exception. - if !c.is_object() { + let c = if let Some(c) = c.as_object() { + c + } else { return context.throw_type_error("property 'constructor' is not an object"); - } + }; // 5. Let S be ? Get(C, @@species). - let s = c.get_field(WellKnownSymbols::species(), context)?; + let s = c.get(WellKnownSymbols::species(), context)?; // 6. If S is either undefined or null, return defaultConstructor. if s.is_null_or_undefined() { diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index d73e5a0621b..dac15380641 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -339,51 +339,6 @@ impl JsValue { } } - /// Set the field in the value - /// - /// Similar to `7.3.4 Set ( O, P, V, Throw )`, but returns the value instead of a boolean. - /// - /// More information: - /// - [ECMAScript][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw - #[inline] - pub(crate) fn set_field( - &self, - key: K, - value: V, - throw: bool, - context: &mut Context, - ) -> JsResult - where - K: Into, - V: Into, - { - // 1. Assert: Type(O) is Object. - // TODO: Currently the value may not be an object. - // In that case this function does nothing. - // 2. Assert: IsPropertyKey(P) is true. - // 3. Assert: Type(Throw) is Boolean. - - let key = key.into(); - let value = value.into(); - let _timer = Profiler::global().start_event("Value::set_field", "value"); - if let Self::Object(ref obj) = *self { - // 4. Let success be ? O.[[Set]](P, V, O). - let success = obj - .clone() - .__set__(key, value.clone(), obj.clone().into(), context)?; - - // 5. If success is false and Throw is true, throw a TypeError exception. - // 6. Return success. - if !success && throw { - return context.throw_type_error("Cannot assign value to property"); - } - return Ok(value); - } - Ok(value) - } - /// Set the kind of an object. #[inline] pub fn set_data(&self, data: ObjectData) { @@ -392,18 +347,6 @@ impl JsValue { } } - /// Set the property in the value. - #[inline] - pub(crate) fn set_property(&self, key: K, property: P) - where - K: Into, - P: Into, - { - if let Some(object) = self.as_object() { - object.insert(key.into(), property.into()); - } - } - /// The abstract operation `ToPrimitive` takes an input argument and an optional argumen`PreferredType`pe. /// /// diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index d8593bbe38d..98c57ff642a 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -176,7 +176,12 @@ impl Context { Opcode::PushValueToArray => { let value = self.vm.pop(); let array = self.vm.pop(); - let array = Array::add_to_array_object(&array, &[value], self)?; + let o = array.as_object().expect("should be an object"); + let len = o + .length_of_array_like(self) + .expect("should have 'length' property"); + o.create_data_property_or_throw(len, value, self) + .expect("should be able to create new data property"); self.vm.push(array); } Opcode::PushIteratorToArray => { @@ -191,7 +196,7 @@ impl Context { if next.done { break; } - Array::add_to_array_object(&array, &[next.value], self)?; + Array::push(&array, &[next.value], self)?; } self.vm.push(array); @@ -1169,10 +1174,7 @@ impl Context { values.push(next.value); } - let array = Array::array_create(0, None, self) - .expect("Array creation with 0 length should never fail"); - - Array::add_to_array_object(&array.clone().into(), &values, self)?; + let array = Array::create_array_from_list(values, self); self.vm.push(iterator); self.vm.push(next_function); @@ -1235,13 +1237,14 @@ impl Context { for _ in 0..rest_count { args.push(self.vm.pop()); } - let array = Array::new_array(self); - Array::add_to_array_object(&array, &args, self) - .expect("could not add to array object"); + let array: _ = Array::create_array_from_list(args, self); + self.vm.push(array); } else { self.vm.pop(); - let array = Array::new_array(self); + + let array = Array::array_create(0, None, self) + .expect("could not create an empty array"); self.vm.push(array); } }