From 82cd2c9be27272f3f96272abe6c3efe5dab0269c Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Wed, 21 Jul 2021 23:15:16 +0200 Subject: [PATCH] Refactor internal object methods and make some builtins spec compliant. --- boa/src/builtins/array/array_iterator.rs | 2 +- boa/src/builtins/array/mod.rs | 790 +++++++++++------- boa/src/builtins/array/tests.rs | 2 +- boa/src/builtins/boolean/mod.rs | 2 +- boa/src/builtins/console/mod.rs | 10 +- boa/src/builtins/date/mod.rs | 4 +- boa/src/builtins/error/eval.rs | 4 +- boa/src/builtins/error/mod.rs | 4 +- boa/src/builtins/error/range.rs | 4 +- boa/src/builtins/error/reference.rs | 4 +- boa/src/builtins/error/syntax.rs | 4 +- boa/src/builtins/error/type.rs | 4 +- boa/src/builtins/error/uri.rs | 4 +- boa/src/builtins/function/mod.rs | 6 +- boa/src/builtins/json/mod.rs | 2 +- boa/src/builtins/map/map_iterator.rs | 2 +- boa/src/builtins/map/mod.rs | 4 +- boa/src/builtins/number/mod.rs | 2 +- boa/src/builtins/object/for_in_iterator.rs | 4 +- boa/src/builtins/object/mod.rs | 102 ++- boa/src/builtins/reflect/mod.rs | 31 +- boa/src/builtins/regexp/mod.rs | 114 +-- .../builtins/regexp/regexp_string_iterator.rs | 2 +- boa/src/builtins/set/mod.rs | 4 +- boa/src/builtins/set/set_iterator.rs | 2 +- boa/src/builtins/string/mod.rs | 32 +- boa/src/builtins/string/string_iterator.rs | 2 +- boa/src/context.rs | 2 +- boa/src/object/gcobject.rs | 125 +-- boa/src/object/internal_methods.rs | 308 +++++-- boa/src/property/mod.rs | 10 + .../syntax/ast/node/operator/unary_op/mod.rs | 4 +- boa/src/value/conversions.rs | 32 +- boa/src/value/display.rs | 4 +- boa/src/value/mod.rs | 17 +- boa/src/value/tests.rs | 4 +- boa/src/vm/mod.rs | 12 +- 37 files changed, 1047 insertions(+), 618 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 47ba98bcc8a..8c38d4f8161 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -123,7 +123,7 @@ impl ArrayIterator { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); // Create prototype - let mut array_iterator = context.construct_object(); + let array_iterator = context.construct_object(); make_builtin_fn(Self::next, "next", &array_iterator, 0, context); array_iterator.set_prototype_instance(iterator_prototype); diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 291af4e9487..d3f06165ebb 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -21,7 +21,7 @@ use crate::{ property::{Attribute, DataDescriptor}, symbol::WellKnownSymbols, value::{IntegerOrInfinity, Value}, - BoaProfiler, Context, Result, + BoaProfiler, Context, JsString, Result, }; use num_traits::*; use std::{ @@ -126,78 +126,77 @@ impl Array { const LENGTH: usize = 1; fn constructor(new_target: &Value, args: &[Value], context: &mut Context) -> Result { + // If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + // 2. Let proto be ? GetPrototypeFromConstructor(newTarget, "%Array.prototype%"). let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or_else(|| context.standard_objects().array_object().prototype()); - // Delegate to the appropriate constructor based on the number of arguments - match args.len() { - 0 => Ok(Array::construct_array_empty(prototype, context)), - 1 => Array::construct_array_length(prototype, &args[0], context), - _ => Array::construct_array_values(prototype, args, context), - } - } - - /// No argument constructor for `Array`. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-array-constructor-array - fn construct_array_empty(proto: GcObject, context: &mut Context) -> Value { - Array::array_create(0, Some(proto), context) - } - - /// By length constructor for `Array`. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-array-len - fn construct_array_length( - prototype: GcObject, - length: &Value, - context: &mut Context, - ) -> Result { - let array = Array::array_create(0, Some(prototype), context); - if !length.is_number() { - array.set_property(0, DataDescriptor::new(length, Attribute::all())); - array.set_field("length", 1, true, context)?; + // 3. Let numberOfArgs be the number of elements in values. + let number_of_args = args.len(); + + // 4. If numberOfArgs = 0, then + if number_of_args == 0 { + // 4.a. Return ! ArrayCreate(0, proto). + Ok(Array::array_create(0, Some(prototype), context) + .unwrap() + .into()) + // 5. Else if numberOfArgs = 1, then + } else if number_of_args == 1 { + // 5.a. Let len be values[0]. + let len = &args[0]; + // 5.b. Let array be ! ArrayCreate(0, proto). + let array = Array::array_create(0, Some(prototype), context).unwrap(); + // 5.c. If Type(len) is not Number, then + let int_len = if !len.is_number() { + // 5.c.i. Perform ! CreateDataPropertyOrThrow(array, "0", len). + array + .create_data_property_or_throw(0, len, context) + .unwrap(); + // 5.c.ii. Let intLen be 1๐”ฝ. + 1 + // 5.d. Else, + } else { + // 5.d.i. Let intLen be ! ToUint32(len). + let int_len = len.to_u32(context).unwrap(); + // 5.d.ii. If SameValueZero(intLen, len) is false, throw a RangeError exception. + if !Value::same_value_zero(&int_len.into(), &len) { + return Err(context.construct_range_error("invalid array length")); + } + int_len + }; + // 5.e. Perform ! Set(array, "length", intLen, true). + array.set("length", int_len, true, context).unwrap(); + // 5.f. Return array. + Ok(array.into()) + // 6. Else, } else { - if length.is_double() { - return context.throw_range_error("Invalid array length"); + // 6.a. Assert: numberOfArgs โ‰ฅ 2. + debug_assert!(number_of_args >= 2); + + // 6.b. Let array be ? ArrayCreate(numberOfArgs, proto). + let array = Array::array_create(number_of_args as u64, Some(prototype), context)?; + // 6.c. Let k be 0. + // 6.d. Repeat, while k < numberOfArgs, + for (i, item) in args.iter().cloned().enumerate() { + // 6.d.i. Let Pk be ! ToString(๐”ฝ(k)). + // 6.d.ii. Let itemK be values[k]. + // 6.d.iii. Perform ! CreateDataPropertyOrThrow(array, Pk, itemK). + array + .create_data_property_or_throw(i, item, context) + .unwrap(); + // 6.d.iii.iv. Set k to k + 1. } - array.set_field("length", length.to_u32(context).unwrap(), true, context)?; - } - - Ok(array) - } - - /// From items constructor for `Array`. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-array-items - fn construct_array_values( - prototype: GcObject, - items: &[Value], - context: &mut Context, - ) -> Result { - let items_len = items.len().try_into().map_err(interror_to_value)?; - let array = Array::array_create(items_len, Some(prototype), context); - - for (k, item) in items.iter().enumerate() { - array.set_property(k, DataDescriptor::new(item.clone(), Attribute::all())); + // 6.e. Assert: The mathematical value of array's "length" property is numberOfArgs. + // 6.f. Return array. + Ok(array.into()) } - - Ok(array) } /// Utility for constructing `Array` objects. @@ -207,31 +206,38 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-arraycreate pub(crate) fn array_create( - length: u32, + length: u64, prototype: Option, context: &mut Context, - ) -> Value { + ) -> Result { + // 1. If length > 2^32 - 1, throw a RangeError exception. + if length > 2u64.pow(32) - 1 { + return Err(context.construct_range_error("array exceeded max size")); + } + // 7. Return A. + // 2. If proto is not present, set proto to %Array.prototype%. + // 3. Let A be ! MakeBasicObject(ยซ [[Prototype]], [[Extensible]] ยป). + // 4. Set A.[[Prototype]] to proto. + // 5. Set A.[[DefineOwnProperty]] as specified in 10.4.2.1. let prototype = match prototype { Some(prototype) => prototype, None => context.standard_objects().array_object().prototype(), }; - let array = Value::new_object(context); + let array = context.construct_object(); - array - .as_object() - .expect("'array' should be an object") - .set_prototype_instance(prototype.into()); + array.set_prototype_instance(prototype.into()); // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - array.set_data(ObjectData::Array); + array.borrow_mut().data = ObjectData::Array; + // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: ๐”ฝ(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). let length = DataDescriptor::new( - length, + length as f64, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, ); - array.set_property("length", length); + array.ordinary_define_own_property("length".into(), length.into()); - array + Ok(array) } /// Creates a new `Array` instance. @@ -301,24 +307,29 @@ impl Array { /// see: pub(crate) fn array_species_create( original_array: &GcObject, - length: u32, + length: u64, context: &mut Context, - ) -> Result { + ) -> Result { + // 1. Let isArray be ? IsArray(originalArray). + // 2. If isArray is false, return ? ArrayCreate(length). if !original_array.is_array() { - return Ok(Self::array_create(length, None, context)); + return Self::array_create(length, None, context); } - let c = original_array.get( - &"constructor".into(), - original_array.clone().into(), - context, - )?; - // Step 4 is ignored, as there are no different realms for now + // 3. Let C be ? Get(originalArray, "constructor"). + let c = original_array.get("constructor", context)?; + + // 4. If IsConstructor(C) is true, then + // a. Let thisRealm be the current Realm Record. + // b. Let realmC be ? GetFunctionRealm(C). + // c. If thisRealm and realmC are not the same Realm Record, then + // i. If SameValue(C, realmC.[[Intrinsics]].[[%Array%]]) is true, set C to undefined. + // TODO: Step 4 is ignored, as there are no different realms for now + + // 5. If Type(C) is Object, then let c = if let Some(c) = c.as_object() { - let c = c.get( - &WellKnownSymbols::species().into(), - c.clone().into(), - context, - )?; + // 5.a. Set C to ? Get(C, @@species). + let c = c.get(WellKnownSymbols::species(), context)?; + // 5.b. If C is null, set C to undefined. if c.is_null_or_undefined() { Value::undefined() } else { @@ -327,16 +338,25 @@ impl Array { } else { c }; + + // 6. If C is undefined, return ? ArrayCreate(length). if c.is_undefined() { - return Ok(Self::array_create(length, None, context)); + return Self::array_create(length, None, context); } + + // 7. If IsConstructor(C) is false, throw a TypeError exception. if let Some(c) = c.as_object() { if !c.is_constructable() { - return context.throw_type_error("Symbol.species must be a constructor"); + return Err(context.construct_type_error("Symbol.species must be a constructor")); } - c.construct(&[Value::from(length)], &c.clone().into(), context) + // 8. Return ? Construct(C, ยซ ๐”ฝ(length) ยป). + Ok( + c.construct(&[Value::from(length)], &c.clone().into(), context)? + .as_object() + .unwrap(), + ) } else { - context.throw_type_error("Symbol.species must be a constructor") + Err(context.construct_type_error("Symbol.species must be a constructor")) } } @@ -382,7 +402,7 @@ impl Array { } } - /// `Array.of(...arguments)` + /// `Array.of(...items)` /// /// The Array.of method creates a new Array instance from a variable number of arguments, /// regardless of the number or type of arguments. @@ -394,22 +414,38 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.of /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of pub(crate) fn of(this: &Value, args: &[Value], context: &mut Context) -> Result { - let array = match this.as_object() { - Some(object) if object.is_constructable() => { - object.construct(&[args.len().into()], this, context)? - } - _ => Array::array_create(args.len() as u32, None, context), + // 1. Let len be the number of elements in items. + // 2. Let lenNumber be ๐”ฝ(len). + let len = args.len(); + + // 3. Let C be the this value. + // 4. If IsConstructor(C) is true, then + // a. Let A be ? Construct(C, ยซ lenNumber ยป). + // 5. Else, + // a. Let A be ? ArrayCreate(len). + let a = match this.as_object() { + Some(object) if object.is_constructable() => object + .construct(&[len.into()], this, context)? + .as_object() + .unwrap(), + _ => Array::array_create(len as u64, None, context)?, }; - // add properties - for (i, value) in args.iter().enumerate() { - array.set_property(i, DataDescriptor::new(value, Attribute::all())); + // 6. Let k be 0. + // 7. Repeat, while k < len, + for (k, value) in args.iter().enumerate() { + // 7.a. Let kValue be items[k]. + // 7.b. Let Pk be ! ToString(๐”ฝ(k)). + // 7.c. Perform ? CreateDataPropertyOrThrow(A, Pk, kValue). + a.create_data_property_or_throw(k, value, context)?; + // 7.d. Set k to k + 1. } - // set length - array.set_field("length", args.len(), true, context)?; + // 8. Perform ? Set(A, "length", lenNumber, true). + a.set("length", len, true, context)?; - Ok(array) + // 9. Return A. + Ok(a.into()) } /// `Array.prototype.concat(...arguments)` @@ -464,17 +500,29 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.push /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push pub(crate) fn push(this: &Value, args: &[Value], context: &mut Context) -> Result { - let length = this.get_field("length", context)?.to_length(context)?; - let arg_count = args.len(); - - if length + arg_count > Number::MAX_SAFE_INTEGER as usize { + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + // 2. Let len be ? LengthOfArrayLike(O). + let mut len = o.length_of_array_like(context)? as u64; + // 3. Let argCount be the number of elements in items. + let arg_count = args.len() as u64; + // 4. If len + argCount > 2^53 - 1, throw a TypeError exception. + if len + arg_count > 2u64.pow(53) - 1 { return context.throw_type_error( "the length + the number of arguments exceed the maximum safe integer limit", ); } - - let new_array = Self::add_to_array_object(this, args, context)?; - new_array.get_field("length", context) + // 5. For each element E of items, do + for element in args.iter().cloned() { + // 5.a. Perform ? Set(O, ! ToString(๐”ฝ(len)), E, true). + o.set(len, element, true, context)?; + // 5.b. Set len to len + 1. + len += 1; + } + // 6. Perform ? Set(O, "length", ๐”ฝ(len), true). + o.set("length", len, true, context)?; + // 7. Return ๐”ฝ(len). + Ok(len.into()) } /// `Array.prototype.pop()` @@ -488,16 +536,32 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop pub(crate) fn pop(this: &Value, _: &[Value], context: &mut Context) -> Result { - let curr_length = this.get_field("length", context)?.to_length(context)?; - - if curr_length < 1 { - return Ok(Value::undefined()); + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + // 2. Let len be ? LengthOfArrayLike(O). + let len = o.length_of_array_like(context)?; + // 3. If len = 0, then + if len == 0 { + // 3.a. Perform ? Set(O, "length", +0๐”ฝ, true). + o.set("length", 0, true, context)?; + // 3.b. Return undefined. + Ok(Value::undefined()) + // 4. Else, + } else { + // 4.a. Assert: len > 0. + // 4.b. Let newLen be ๐”ฝ(len - 1). + let new_len = len - 1; + // 4.c. Let index be ! ToString(newLen). + let index = new_len; + // 4.d. Let element be ? Get(O, index). + let element = o.get(index, context)?; + // 4.e. Perform ? DeletePropertyOrThrow(O, index). + o.delete_property_or_throw(index, context)?; + // 4.f. Perform ? Set(O, "length", newLen, true). + o.set("length", new_len, true, context)?; + // 4.g. Return element. + Ok(element) } - let pop_index = curr_length.wrapping_sub(1); - let pop_value: Value = this.get_field(pop_index.to_string(), context)?; - this.remove_property(pop_index); - this.set_field("length", Value::from(pop_index), true, context)?; - Ok(pop_value) } /// `Array.prototype.forEach( callbackFn [ , thisArg ] )` @@ -511,22 +575,38 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.foreach /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach pub(crate) fn for_each(this: &Value, args: &[Value], context: &mut Context) -> Result { - if args.is_empty() { - return Err(Value::from("Missing argument for Array.prototype.forEach")); - } - - let callback_arg = args.get(0).expect("Could not get `callbackFn` argument."); - let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); - - let length = this.get_field("length", context)?.to_length(context)?; - - for i in 0..length { - let element = this.get_field(i, context)?; - let arguments = [element, Value::from(i), this.clone()]; - - context.call(callback_arg, &this_arg, &arguments)?; + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + // 2. Let len be ? LengthOfArrayLike(O). + let len = o.length_of_array_like(context)?; + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback = if let Some(arg) = args + .get(0) + .and_then(Value::as_object) + .filter(GcObject::is_callable) + { + arg + } else { + return context.throw_type_error("Array.prototype.forEach: invalid callback function"); + }; + // 4. Let k be 0. + // 5. Repeat, while k < len, + for k in 0..len { + // 5.a. Let Pk be ! ToString(๐”ฝ(k)). + let pk = k; + // 5.b. Let kPresent be ? HasProperty(O, Pk). + let present = o.has_property(pk); + // 5.c. If kPresent is true, then + if present { + // 5.c.i. Let kValue be ? Get(O, Pk). + let k_value = o.get(pk, context)?; + // 5.c.ii. Perform ? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป). + let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); + callback.call(&this_arg, &[k_value, k.into(), o.clone().into()], context)?; + } + // 5.d. Set k to k + 1. } - + // 6. Return undefined. Ok(Value::undefined()) } @@ -543,23 +623,41 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join pub(crate) fn join(this: &Value, args: &[Value], context: &mut Context) -> Result { - let separator = if args.is_empty() { - String::from(",") + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + // 2. Let len be ? LengthOfArrayLike(O). + let len = o.length_of_array_like(context)?; + // 3. If separator is undefined, let sep be the single-element String ",". + // 4. Else, let sep be ? ToString(separator). + let separator = if let Some(separator) = args.get(0) { + separator.to_string(context)? } else { - args.get(0) - .expect("Could not get argument") - .to_string(context)? - .to_string() + JsString::new(",") }; - let mut elem_strs = Vec::new(); - let length = this.get_field("length", context)?.to_length(context)?; - for n in 0..length { - let elem_str = this.get_field(n, context)?.to_string(context)?.to_string(); - elem_strs.push(elem_str); + // 5. Let R be the empty String. + let mut r = String::new(); + // 6. Let k be 0. + // 7. Repeat, while k < len, + for k in 0..len { + // 7.a. If k > 0, set R to the string-concatenation of R and sep. + if k > 0 { + r.push_str(&separator); + } + // 7.b. Let element be ? Get(O, ! ToString(๐”ฝ(k))). + let element = o.get(k, context)?; + // 7.c. If element is undefined or null, let next be the empty String; otherwise, let next be ? ToString(element). + let next = if element.is_null_or_undefined() { + JsString::new("") + } else { + element.to_string(context)? + }; + // 7.d. Set R to the string-concatenation of R and next. + r.push_str(&next); + // 7.e. Set k to k + 1. } - - Ok(Value::from(elem_strs.join(&separator))) + // 8. Return R. + Ok(r.into()) } /// `Array.prototype.toString( separator )` @@ -576,31 +674,17 @@ impl Array { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString #[allow(clippy::wrong_self_convention)] pub(crate) fn to_string(this: &Value, _: &[Value], context: &mut Context) -> Result { - let method_name = "join"; - let mut arguments = vec![Value::from(",")]; - // 2. - let mut method = this.get_field(method_name, context)?; - // 3. - if !method.is_function() { - let object_prototype: Value = context - .standard_objects() - .object_object() - .prototype() - .into(); - method = object_prototype.get_field("toString", context)?; - - arguments = Vec::new(); - } - // 4. - let join = context.call(&method, this, &arguments)?; - - let string = if let Value::String(ref s) = join { - Value::from(s.as_str()) + // 1. Let array be ? ToObject(this value). + let array = this.to_object(context)?; + // 2. Let func be ? Get(array, "join"). + let func = array.get("join", context)?; + // 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%. + // 4. Return ? Call(func, array). + if let Some(func) = func.as_object().filter(GcObject::is_callable) { + func.call(&array.into(), &[], context) } else { - Value::from("") - }; - - Ok(string) + crate::builtins::object::Object::to_string(&array.into(), &[], context) + } } /// `Array.prototype.reverse()` @@ -616,32 +700,70 @@ impl Array { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse #[allow(clippy::else_if_without_else)] pub(crate) fn reverse(this: &Value, _: &[Value], context: &mut Context) -> Result { - let len = this.get_field("length", context)?.to_length(context)?; - - let middle = len.wrapping_div(2); - - for lower in 0..middle { - let upper = len.wrapping_sub(lower).wrapping_sub(1); - - let upper_exists = this.has_field(upper); - let lower_exists = this.has_field(lower); - - let upper_value = this.get_field(upper, context)?; - let lower_value = this.get_field(lower, context)?; - - if upper_exists && lower_exists { - this.set_property(upper, DataDescriptor::new(lower_value, Attribute::all())); - this.set_property(lower, DataDescriptor::new(upper_value, Attribute::all())); - } else if upper_exists { - this.set_property(lower, DataDescriptor::new(upper_value, Attribute::all())); - this.remove_property(upper); - } else if lower_exists { - this.set_property(upper, DataDescriptor::new(lower_value, Attribute::all())); - this.remove_property(lower); + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + // 2. Let len be ? LengthOfArrayLike(O). + let len = o.length_of_array_like(context)?; + // 3. Let middle be floor(len / 2). + let middle = len / 2; + // 4. Let lower be 0. + let mut lower = 0; + // 5. Repeat, while lower โ‰  middle, + while lower != middle { + // a. Let upper be len - lower - 1. + let upper = len - lower - 1; + // Skiped: b. Let upperP be ! ToString(๐”ฝ(upper)). + // Skiped: c. Let lowerP be ! ToString(๐”ฝ(lower)). + // d. Let lowerExists be ? HasProperty(O, lowerP). + let lower_exists = o.has_property(lower); + // e. If lowerExists is true, then + let mut lower_value = Value::undefined(); + if lower_exists { + // i. Let lowerValue be ? Get(O, lowerP). + lower_value = o.get(lower, context)?; + } + // f. Let upperExists be ? HasProperty(O, upperP). + let upper_exists = o.has_property(upper); + // g. If upperExists is true, then + let mut upper_value = Value::undefined(); + if upper_exists { + // i. Let upperValue be ? Get(O, upperP). + upper_value = o.get(upper, context)?; + } + match (lower_exists, upper_exists) { + // h. If lowerExists is true and upperExists is true, then + (true, true) => { + // i. Perform ? Set(O, lowerP, upperValue, true). + o.set(lower, upper_value, true, context)?; + // ii. Perform ? Set(O, upperP, lowerValue, true). + o.set(upper, lower_value, true, context)?; + } + // i. Else if lowerExists is false and upperExists is true, then + (false, true) => { + // i. Perform ? Set(O, lowerP, upperValue, true). + o.set(lower, upper_value, true, context)?; + // ii. Perform ? DeletePropertyOrThrow(O, upperP). + o.delete_property_or_throw(upper, context)?; + } + // j. Else if lowerExists is true and upperExists is false, then + (true, false) => { + // i. Perform ? DeletePropertyOrThrow(O, lowerP). + o.delete_property_or_throw(lower, context)?; + // ii. Perform ? Set(O, upperP, lowerValue, true). + o.set(upper, lower_value, true, context)?; + } + // k. Else, + (false, false) => { + // i. Assert: lowerExists and upperExists are both false. + // ii. No action is required. + } } - } - Ok(this.clone()) + // l. Set lower to lower + 1. + lower += 1; + } + // 6. Return O. + Ok(o.into()) } /// `Array.prototype.shift()` @@ -655,31 +777,47 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift pub(crate) fn shift(this: &Value, _: &[Value], context: &mut Context) -> Result { - let len = this.get_field("length", context)?.to_length(context)?; - + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + // 2. Let len be ? LengthOfArrayLike(O). + let len = o.length_of_array_like(context)?; + // 3. If len = 0, then if len == 0 { - this.set_field("length", 0, true, context)?; + // a. Perform ? Set(O, "length", +0๐”ฝ, true). + o.set("length", 0, true, context)?; + // b. Return undefined. return Ok(Value::undefined()); } - - let first: Value = this.get_field(0, context)?; - + // 4. Let first be ? Get(O, "0"). + let first = o.get(0, context)?; + // 5. Let k be 1. + // 6. Repeat, while k < len, for k in 1..len { + // a. Let from be ! ToString(๐”ฝ(k)). let from = k; - let to = k.wrapping_sub(1); - - let from_value = this.get_field(from, context)?; - if from_value.is_undefined() { - this.remove_property(to); + // b. Let to be ! ToString(๐”ฝ(k - 1)). + let to = k - 1; + // c. Let fromPresent be ? HasProperty(O, from). + let from_present = o.has_property(from); + // d. If fromPresent is true, then + if from_present { + // i. Let fromVal be ? Get(O, from). + let from_val = o.get(from, context)?; + // ii. Perform ? Set(O, to, fromVal, true). + o.set(to, from_val, true, context)?; + // e. Else, } else { - this.set_property(to, DataDescriptor::new(from_value, Attribute::all())); + // i. Assert: fromPresent is false. + // ii. Perform ? DeletePropertyOrThrow(O, to). + o.delete_property_or_throw(to, context)?; } + // f. Set k to k + 1. } - - let final_index = len.wrapping_sub(1); - this.remove_property(final_index); - this.set_field("length", Value::from(final_index), true, context)?; - + // 7. Perform ? DeletePropertyOrThrow(O, ! ToString(๐”ฝ(len - 1))). + o.delete_property_or_throw(len - 1, context)?; + // 8. Perform ? Set(O, "length", ๐”ฝ(len - 1), true). + o.set("length", len - 1, true, context)?; + // 9. Return first. Ok(first) } @@ -696,41 +834,57 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift pub(crate) fn unshift(this: &Value, args: &[Value], context: &mut Context) -> Result { - let len = this.get_field("length", context)?.to_length(context)?; - - let arg_c = args.len(); - - if arg_c > 0 { - if len + arg_c > Number::MAX_SAFE_INTEGER as usize { + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + // 2. Let len be ? LengthOfArrayLike(O). + let len = o.length_of_array_like(context)? as u64; + // 3. Let argCount be the number of elements in items. + let arg_count = args.len() as u64; + // 4. If argCount > 0, then + if arg_count > 0 { + // a. If len + argCount > 2^53 - 1, throw a TypeError exception. + if len + arg_count > 2u64.pow(53) - 1 { return context.throw_type_error( - "the length + the number of arguments exceed the maximum safe integer limit", + "length + number of arguments exceeds the max safe integer limit", ); } - for k in (1..=len).rev() { - let from = k.wrapping_sub(1); - let to = k.wrapping_add(arg_c).wrapping_sub(1); - - let from_value = this.get_field(from, context)?; - if from_value.is_undefined() { - this.remove_property(to); + // b. Let k be len. + let mut k = len; + // c. Repeat, while k > 0, + while k > 0 { + // i. Let from be ! ToString(๐”ฝ(k - 1)). + let from = k - 1; + // ii. Let to be ! ToString(๐”ฝ(k + argCount - 1)). + let to = k + arg_count - 1; + // iii. Let fromPresent be ? HasProperty(O, from). + let from_present = o.has_property(from); + // iv. If fromPresent is true, then + if from_present { + // 1. Let fromValue be ? Get(O, from). + let from_value = o.get(from, context)?; + // 2. Perform ? Set(O, to, fromValue, true). + o.set(to, from_value, true, context)?; + // v. Else, } else { - this.set_property(to, DataDescriptor::new(from_value, Attribute::all())); + // 1. Assert: fromPresent is false. + // 2. Perform ? DeletePropertyOrThrow(O, to). + o.delete_property_or_throw(to, context)?; } + // vi. Set k to k - 1. + k -= 1; } - for j in 0..arg_c { - this.set_property( - j, - DataDescriptor::new( - args.get(j).expect("Could not get argument").clone(), - Attribute::all(), - ), - ); + // d. Let j be +0๐”ฝ. + // e. For each element E of items, do + for (j, e) in args.iter().enumerate() { + // i. Perform ? Set(O, ! ToString(j), E, true). + o.set(j, e, true, context)?; + // ii. Set j to j + 1๐”ฝ. } } - - let temp = len.wrapping_add(arg_c); - this.set_field("length", Value::from(temp), true, context)?; - Ok(Value::from(temp)) + // 5. Perform ? Set(O, "length", ๐”ฝ(len + argCount), true). + o.set("length", len + arg_count, true, context)?; + // 6. Return ๐”ฝ(len + argCount). + Ok((len + arg_count).into()) } /// `Array.prototype.every( callback, [ thisArg ] )` @@ -747,33 +901,44 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.every /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every pub(crate) fn every(this: &Value, args: &[Value], context: &mut Context) -> Result { - if args.is_empty() { - return Err(Value::from( - "missing callback when calling function Array.prototype.every", - )); - } - let callback = &args[0]; - let this_arg = if args.len() > 1 { - args[1].clone() + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + // 2. Let len be ? LengthOfArrayLike(O). + let len = o.length_of_array_like(context)?; + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback = if let Some(arg) = args + .get(0) + .and_then(Value::as_object) + .filter(GcObject::is_callable) + { + arg } else { - Value::undefined() + return context.throw_type_error("Array.prototype.every: callback is not callable"); }; - let mut i = 0; - let max_len = this.get_field("length", context)?.to_length(context)?; - let mut len = max_len; - while i < len { - let element = this.get_field(i, context)?; - let arguments = [element, Value::from(i), this.clone()]; - let result = context.call(callback, &this_arg, &arguments)?; - if !result.to_boolean() { - return Ok(Value::from(false)); + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(๐”ฝ(k)). + // b. Let kPresent be ? HasProperty(O, Pk). + let k_present = o.has_property(k); + // c. If kPresent is true, then + if k_present { + // i. Let kValue be ? Get(O, Pk). + let k_value = o.get(k, context)?; + // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)). + let this_arg = args.get(1).cloned().unwrap_or_default(); + let test_result = callback + .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? + .to_boolean(); + // iii. If testResult is false, return false. + if !test_result { + return Ok(Value::from(false)); + } } - len = min( - max_len, - this.get_field("length", context)?.to_length(context)?, - ); - i += 1; + // d. Set k to k + 1. } + // 6. Return true. Ok(Value::from(true)) } @@ -1351,11 +1516,13 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.filter /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter pub(crate) fn filter(this: &Value, args: &[Value], context: &mut Context) -> Result { + // 1. Let O be ? ToObject(this value). let o = this.to_object(context)?; - let length = o - .get(&"length".into(), Value::from(o.clone()), context)? - .to_length(context)?; + // 2. Let len be ? LengthOfArrayLike(O). + let length = o.length_of_array_like(context)?; + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback = args .get(0) .map(|a| a.to_object(context)) @@ -1371,36 +1538,37 @@ impl Array { return context.throw_type_error("the callback must be callable"); } - let mut a = Self::array_species_create(&o, 0, context)? - .as_object() - .expect("array_species_create must create an object"); + // 4. Let A be ? ArraySpeciesCreate(O, 0). + let a = Self::array_species_create(&o, 0, context)?; + // 5. Let k be 0. + // 6. Let to be 0. let mut to = 0u32; + // 7. Repeat, while k < len, for idx in 0..length { - if o.has_property(&idx.into()) { - let element = o.get(&idx.into(), Value::from(o.clone()), context)?; + // 7.a. Let Pk be ! ToString(๐”ฝ(k)). + // 7.b. Let kPresent be ? HasProperty(O, Pk). + // 7.c. If kPresent is true, then + if o.has_property(idx) { + // 7.c.i. Let kValue be ? Get(O, Pk). + let element = o.get(idx, context)?; let args = [element.clone(), Value::from(idx), Value::from(o.clone())]; - let callback_result = callback.call(&this_val, &args, context)?; + // 7.c.ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)). + let selected = callback.call(&this_val, &args, context)?.to_boolean(); - if callback_result.to_boolean() { - if !a.define_own_property( - to, - DataDescriptor::new( - element, - Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, - ) - .into(), - context, - )? { - return context.throw_type_error("cannot set property in array"); - } + // 7.c.iii. If selected is true, then + if selected { + // 7.c.iii.1. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(to)), kValue). + a.create_data_property_or_throw(to, element, context)?; + // 7.c.iii.2. Set to to to + 1. to += 1; } } } + // 8. Return A. Ok(a.into()) } @@ -1420,34 +1588,44 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.some /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some pub(crate) fn some(this: &Value, args: &[Value], context: &mut Context) -> Result { - if args.is_empty() { - return Err(Value::from( - "missing callback when calling function Array.prototype.some", - )); - } - let callback = &args[0]; - let this_arg = if args.len() > 1 { - args[1].clone() + // 1. Let O be ? ToObject(this value). + let o = this.to_object(context)?; + // 2. Let len be ? LengthOfArrayLike(O). + let len = o.length_of_array_like(context)?; + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback = if let Some(arg) = args + .get(0) + .and_then(Value::as_object) + .filter(GcObject::is_callable) + { + arg } else { - Value::undefined() + return context.throw_type_error("Array.prototype.some: callback is not callable"); }; - let mut i = 0; - let max_len = this.get_field("length", context)?.to_length(context)?; - let mut len = max_len; - while i < len { - let element = this.get_field(i, context)?; - let arguments = [element, Value::from(i), this.clone()]; - let result = context.call(callback, &this_arg, &arguments)?; - if result.to_boolean() { - return Ok(Value::from(true)); + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(๐”ฝ(k)). + // b. Let kPresent be ? HasProperty(O, Pk). + let k_present = o.has_property(k); + // c. If kPresent is true, then + if k_present { + // i. Let kValue be ? Get(O, Pk). + let k_value = o.get(k, context)?; + // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)). + let this_arg = args.get(1).cloned().unwrap_or_default(); + let test_result = callback + .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? + .to_boolean(); + // iii. If testResult is true, return true. + if test_result { + return Ok(Value::from(true)); + } } - // the length of the array must be updated because the callback can mutate it. - len = min( - max_len, - this.get_field("length", context)?.to_length(context)?, - ); - i += 1; + // d. Set k to k + 1. } + // 6. Return false. Ok(Value::from(false)) } diff --git a/boa/src/builtins/array/tests.rs b/boa/src/builtins/array/tests.rs index fb88bb90dc3..ab354188b7e 100644 --- a/boa/src/builtins/array/tests.rs +++ b/boa/src/builtins/array/tests.rs @@ -711,7 +711,7 @@ fn fill() { assert_eq!( forward(&mut context, "a.fill().join()"), - String::from("\"undefined,undefined,undefined\"") + String::from("\",,\"") ); // test object reference diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 4c8f1b67c62..8dc1af259d0 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -69,7 +69,7 @@ impl Boolean { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) diff --git a/boa/src/builtins/console/mod.rs b/boa/src/builtins/console/mod.rs index efbc5b78e21..9eae0298cb3 100644 --- a/boa/src/builtins/console/mod.rs +++ b/boa/src/builtins/console/mod.rs @@ -35,14 +35,6 @@ pub enum LogMessage { Error(String), } -/// Helper function that returns the argument at a specified index. -fn get_arg_at_index<'a, T>(args: &'a [Value], index: usize) -> Option -where - T: From<&'a Value> + Default, -{ - args.get(index).map(|s| T::from(s)) -} - /// Helper function for logging messages. pub(crate) fn logger(msg: LogMessage, console_state: &Console) { let indent = 2 * console_state.groups.len(); @@ -193,7 +185,7 @@ impl Console { /// [spec]: https://console.spec.whatwg.org/#assert /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/assert pub(crate) fn assert(_: &Value, args: &[Value], context: &mut Context) -> Result { - let assertion = get_arg_at_index::(args, 0).unwrap_or_default(); + let assertion = args.get(0).map(Value::to_boolean).unwrap_or(false); if !assertion { let mut args: Vec = args.iter().skip(1).cloned().collect(); diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 6ccc166bd3c..8153076fc86 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -376,13 +376,13 @@ impl Date { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or_else(|| context.standard_objects().object_object().prototype()); - let mut obj = context.construct_object(); + let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let this = obj.into(); if args.is_empty() { diff --git a/boa/src/builtins/error/eval.rs b/boa/src/builtins/error/eval.rs index b714535b6a4..b65dbe86859 100644 --- a/boa/src/builtins/error/eval.rs +++ b/boa/src/builtins/error/eval.rs @@ -65,13 +65,13 @@ impl EvalError { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or_else(|| context.standard_objects().error_object().prototype()); - let mut obj = context.construct_object(); + let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let this = Value::from(obj); if let Some(message) = args.get(0) { diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index eb3dee08a30..48be4ad6e06 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -81,13 +81,13 @@ impl Error { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or_else(|| context.standard_objects().error_object().prototype()); - let mut obj = context.construct_object(); + let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let this = Value::from(obj); if let Some(message) = args.get(0) { diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 60c4ed92934..c2c4e046e13 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -62,13 +62,13 @@ impl RangeError { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or_else(|| context.standard_objects().error_object().prototype()); - let mut obj = context.construct_object(); + let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let this = Value::from(obj); if let Some(message) = args.get(0) { diff --git a/boa/src/builtins/error/reference.rs b/boa/src/builtins/error/reference.rs index 64157cbb5e4..47e9cd525d2 100644 --- a/boa/src/builtins/error/reference.rs +++ b/boa/src/builtins/error/reference.rs @@ -61,13 +61,13 @@ impl ReferenceError { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or_else(|| context.standard_objects().error_object().prototype()); - let mut obj = context.construct_object(); + let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let this = Value::from(obj); if let Some(message) = args.get(0) { diff --git a/boa/src/builtins/error/syntax.rs b/boa/src/builtins/error/syntax.rs index 9a488fb4ceb..2fc96af0419 100644 --- a/boa/src/builtins/error/syntax.rs +++ b/boa/src/builtins/error/syntax.rs @@ -64,13 +64,13 @@ impl SyntaxError { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or_else(|| context.standard_objects().error_object().prototype()); - let mut obj = context.construct_object(); + let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let this = Value::from(obj); if let Some(message) = args.get(0) { diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index 5ca6f166936..e1069d5a1ed 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -67,13 +67,13 @@ impl TypeError { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or_else(|| context.standard_objects().error_object().prototype()); - let mut obj = context.construct_object(); + let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let this = Value::from(obj); if let Some(message) = args.get(0) { diff --git a/boa/src/builtins/error/uri.rs b/boa/src/builtins/error/uri.rs index 2455bf004ad..aabe6284042 100644 --- a/boa/src/builtins/error/uri.rs +++ b/boa/src/builtins/error/uri.rs @@ -63,13 +63,13 @@ impl UriError { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or_else(|| context.standard_objects().error_object().prototype()); - let mut obj = context.construct_object(); + let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let this = Value::from(obj); if let Some(message) = args.get(0) { diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 036f0c324a0..584359ea6b1 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -176,14 +176,14 @@ impl Function { /// pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { let len = arguments_list.len(); - let mut obj = GcObject::new(Object::default()); + let obj = GcObject::new(Object::default()); // Set length let length = DataDescriptor::new( len, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); // Define length as a property - obj.ordinary_define_own_property("length", length.into()); + obj.ordinary_define_own_property("length".into(), length.into()); let mut index: usize = 0; while index < len { let val = arguments_list.get(index).expect("Could not get argument"); @@ -258,7 +258,7 @@ impl BuiltInFunctionObject { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index d5d828e0c31..0fb9526c833 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -197,7 +197,7 @@ impl Json { .map(|obj| { let object_to_return = Value::object(Object::default()); for key in obj.borrow().keys() { - let val = obj.get(&key, obj.clone().into(), context)?; + let val = obj.__get__(&key, obj.clone().into(), context)?; let this_arg = object.clone(); object_to_return.set_property( key.to_owned(), diff --git a/boa/src/builtins/map/map_iterator.rs b/boa/src/builtins/map/map_iterator.rs index aba39df0893..b3c600193e8 100644 --- a/boa/src/builtins/map/map_iterator.rs +++ b/boa/src/builtins/map/map_iterator.rs @@ -149,7 +149,7 @@ impl MapIterator { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); // Create prototype - let mut map_iterator = context.construct_object(); + let map_iterator = context.construct_object(); make_builtin_fn(Self::next, "next", &map_iterator, 0, context); map_iterator.set_prototype_instance(iterator_prototype); diff --git a/boa/src/builtins/map/mod.rs b/boa/src/builtins/map/mod.rs index 31aee094665..7629ee0d5b6 100644 --- a/boa/src/builtins/map/mod.rs +++ b/boa/src/builtins/map/mod.rs @@ -118,14 +118,14 @@ impl Map { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or(map_prototype); - let mut obj = context.construct_object(); + let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let this = Value::from(obj); diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index d50ce2f8dbb..b4d52bf9e04 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -169,7 +169,7 @@ impl Number { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) diff --git a/boa/src/builtins/object/for_in_iterator.rs b/boa/src/builtins/object/for_in_iterator.rs index f0193be21c1..fa00592275d 100644 --- a/boa/src/builtins/object/for_in_iterator.rs +++ b/boa/src/builtins/object/for_in_iterator.rs @@ -87,7 +87,7 @@ impl ForInIterator { while let Some(r) = iterator.remaining_keys.pop_front() { if !iterator.visited_keys.contains(&r) { if let Some(desc) = - object.get_own_property(&PropertyKey::from(r.clone())) + object.__get_own_property__(&PropertyKey::from(r.clone())) { iterator.visited_keys.insert(r.clone()); if desc.enumerable() { @@ -129,7 +129,7 @@ impl ForInIterator { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); // Create prototype - let mut for_in_iterator = context.construct_object(); + let for_in_iterator = context.construct_object(); make_builtin_fn(Self::next, "next", &for_in_iterator, 0, context); for_in_iterator.set_prototype_instance(iterator_prototype); diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index a0f685c90a7..8f0df2f56fe 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -87,7 +87,7 @@ impl Object { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) @@ -165,7 +165,7 @@ impl Object { if let Some(key) = args.get(1) { let key = key.to_property_key(context)?; - if let Some(desc) = object.get_own_property(&key) { + if let Some(desc) = object.__get_own_property__(&key) { return Ok(Self::from_property_descriptor(desc, context)); } } @@ -197,7 +197,7 @@ impl Object { for key in object.borrow().keys() { let descriptor = { let desc = object - .get_own_property(&key) + .__get_own_property__(&key) .expect("Expected property to be on object."); Self::from_property_descriptor(desc, context) }; @@ -318,7 +318,7 @@ impl Object { let status = obj .as_object() .expect("obj was not an object") - .set_prototype_of(proto); + .__set_prototype_of__(proto); // 5. If status is false, throw a TypeError exception. if !status { @@ -411,38 +411,52 @@ impl Object { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString #[allow(clippy::wrong_self_convention)] pub fn to_string(this: &Value, _: &[Value], context: &mut Context) -> Result { + // 1. If the this value is undefined, return "[object Undefined]". if this.is_undefined() { - Ok("[object Undefined]".into()) - } else if this.is_null() { - Ok("[object Null]".into()) - } else { - let o = this.to_object(context)?; - let builtin_tag = { - let o = o.borrow(); - match &o.data { - ObjectData::Array => "Array", - // TODO: Arguments Exotic Objects are currently not supported - ObjectData::Function(_) => "Function", - ObjectData::Error => "Error", - ObjectData::Boolean(_) => "Boolean", - ObjectData::Number(_) => "Number", - ObjectData::String(_) => "String", - ObjectData::Date(_) => "Date", - ObjectData::RegExp(_) => "RegExp", - _ => "Object", - } - }; + return Ok("[object Undefined]".into()); + } + // 2. If the this value is null, return "[object Null]". + if this.is_null() { + return Ok("[object Null]".into()); + } + // 3. Let O be ! ToObject(this value). + let o = this.to_object(context)?; + // TODO: 4. Let isArray be ? IsArray(O). + // TODO: 5. If isArray is true, let builtinTag be "Array". + + // 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments". + // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function". + // 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error". + // 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean". + // 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number". + // 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String". + // 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date". + // 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp". + // 14. Else, let builtinTag be "Object". + let builtin_tag = { + let o = o.borrow(); + match &o.data { + ObjectData::Array => "Array", + // TODO: Arguments Exotic Objects are currently not supported + ObjectData::Function(_) => "Function", + ObjectData::Error => "Error", + ObjectData::Boolean(_) => "Boolean", + ObjectData::Number(_) => "Number", + ObjectData::String(_) => "String", + ObjectData::Date(_) => "Date", + ObjectData::RegExp(_) => "RegExp", + _ => "Object", + } + }; - let tag = o.get( - &WellKnownSymbols::to_string_tag().into(), - o.clone().into(), - context, - )?; + // 15. Let tag be ? Get(O, @@toStringTag). + let tag = o.get(WellKnownSymbols::to_string_tag(), context)?; - let tag_str = tag.as_string().map(|s| s.as_str()).unwrap_or(builtin_tag); + // 16. If Type(tag) is not String, set tag to builtinTag. + let tag_str = tag.as_string().map(|s| s.as_str()).unwrap_or(builtin_tag); - Ok(format!("[object {}]", tag_str).into()) - } + // 17. Return the string-concatenation of "[object ", tag, and "]". + Ok(format!("[object {}]", tag_str).into()) } /// `Object.prototype.hasOwnPrototype( property )` @@ -488,7 +502,7 @@ impl Object { }; let key = key.to_property_key(context)?; - let own_property = this.to_object(context)?.get_own_property(&key); + let own_property = this.to_object(context)?.__get_own_property__(&key); Ok(own_property.map_or(Value::from(false), |own_prop| { Value::from(own_prop.enumerable()) @@ -507,31 +521,45 @@ impl Object { /// [spec]: https://tc39.es/ecma262/#sec-object.assign /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign pub fn assign(_: &Value, args: &[Value], context: &mut Context) -> Result { - let mut to = args + // + // + // 1. Let to be ? ToObject(target). + let to = args .get(0) .cloned() .unwrap_or_default() .to_object(context)?; + // 2. If only one argument was passed, return to. if args.len() == 1 { return Ok(to.into()); } + // 3. For each element nextSource of sources, do for source in &args[1..] { + // 3.a. If nextSource is neither undefined nor null, then if !source.is_null_or_undefined() { + // 3.a.i. Let from be ! ToObject(nextSource). let from = source.to_object(context).unwrap(); + // 3.a.ii. Let keys be ? from.[[OwnPropertyKeys]](). let keys = from.own_property_keys(); + // 3.a.iii. For each element nextKey of keys, do for key in keys { - if let Some(desc) = from.get_own_property(&key) { + // 3.a.iii.1. Let desc be ? from.[[GetOwnProperty]](nextKey). + if let Some(desc) = from.__get_own_property__(&key) { + // 3.a.iii.2. If desc is not undefined and desc.[[Enumerable]] is true, then if desc.enumerable() { - let property = from.get(&key, from.clone().into(), context)?; - to.set(key, property, to.clone().into(), context)?; + // 3.a.iii.2.a. Let propValue be ? Get(from, nextKey). + let property = from.get(key.clone(), context)?; + // 3.a.iii.2.b. Perform ? Set(to, nextKey, propValue, true). + to.set(key, property, true, context)?; } } } } } + // 4. Return to. Ok(to.into()) } } diff --git a/boa/src/builtins/reflect/mod.rs b/boa/src/builtins/reflect/mod.rs index a67f6d3b6fd..de17acc59f1 100644 --- a/boa/src/builtins/reflect/mod.rs +++ b/boa/src/builtins/reflect/mod.rs @@ -142,7 +142,7 @@ impl Reflect { context: &mut Context, ) -> Result { let undefined = Value::undefined(); - let mut target = args + let target = args .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; @@ -154,7 +154,7 @@ impl Reflect { .to_property_descriptor(context)?; target - .define_own_property(key, prop_desc, context) + .__define_own_property__(key, prop_desc, context) .map(|b| b.into()) } @@ -172,13 +172,13 @@ impl Reflect { context: &mut Context, ) -> Result { let undefined = Value::undefined(); - let mut target = args + let target = args .get(0) .and_then(|v| v.as_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)?; - Ok(target.delete(&key).into()) + Ok(target.__delete__(&key).into()) } /// Gets a property of an object. @@ -191,17 +191,22 @@ impl Reflect { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get pub(crate) fn get(_: &Value, args: &[Value], context: &mut Context) -> Result { let undefined = Value::undefined(); + // 1. If Type(target) is not Object, throw a TypeError exception. let target = args .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; + // 2. Let key be ? ToPropertyKey(propertyKey). let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?; + // 3. If receiver is not present, then let receiver = if let Some(receiver) = args.get(2).cloned() { receiver } else { + // 3.a. Set receiver to target. target.clone().into() }; - target.get(&key, receiver, context) + // 4. Return ? target.[[Get]](key, receiver). + target.__get__(&key, receiver, context) } /// Gets a property of an object. @@ -243,7 +248,7 @@ impl Reflect { .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; - Ok(target.get_prototype_of()) + Ok(target.__get_prototype_of__()) } /// Returns `true` if the object has the property, `false` otherwise. @@ -263,7 +268,7 @@ impl Reflect { .get(1) .unwrap_or(&Value::undefined()) .to_property_key(context)?; - Ok(target.has_property(&key).into()) + Ok(target.__has_property__(&key).into()) } /// Returns `true` if the object is extensible, `false` otherwise. @@ -279,7 +284,7 @@ impl Reflect { .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; - Ok(target.is_extensible().into()) + Ok(target.__is_extensible__().into()) } /// Returns an array of object own property keys. @@ -332,7 +337,7 @@ impl Reflect { .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; - Ok(target.prevent_extensions().into()) + Ok(target.__prevent_extensions__().into()) } /// Sets a property of an object. @@ -345,7 +350,7 @@ impl Reflect { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set pub(crate) fn set(_: &Value, args: &[Value], context: &mut Context) -> Result { let undefined = Value::undefined(); - let mut target = args + let target = args .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; @@ -356,7 +361,9 @@ impl Reflect { } else { target.clone().into() }; - Ok(target.set(key, value.clone(), receiver, context)?.into()) + Ok(target + .__set__(key, value.clone(), receiver, context)? + .into()) } /// Sets the prototype of an object. @@ -381,6 +388,6 @@ impl Reflect { if !proto.is_null() && !proto.is_object() { return context.throw_type_error("proto must be an object or null"); } - Ok(target.set_prototype_of(proto.clone()).into()) + Ok(target.__set_prototype_of__(proto.clone()).into()) } } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index a20a9595ce0..b3584772647 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -15,7 +15,7 @@ use crate::{ builtins::{array::Array, BuiltIn}, gc::{empty_trace, Finalize, Trace}, object::{ConstructorBuilder, FunctionBuilder, GcObject, ObjectData, PROTOTYPE}, - property::{Attribute, DataDescriptor}, + property::Attribute, symbol::WellKnownSymbols, value::{IntegerOrInfinity, Value}, BoaProfiler, Context, JsString, Result, @@ -197,7 +197,7 @@ impl RegExp { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), ctx) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), ctx) .map(|o| o.as_object()) .transpose() }) @@ -454,45 +454,46 @@ impl RegExp { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags /// [flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2 pub(crate) fn get_flags(this: &Value, _: &[Value], context: &mut Context) -> Result { + // 1. Let R be the this value. + // 2. If Type(R) is not Object, throw a TypeError exception. if let Some(object) = this.as_object() { + // 3. Let result be the empty String. let mut result = String::new(); - if object - .get(&"global".into(), this.clone(), context)? - .to_boolean() - { + // 4. Let global be ! ToBoolean(? Get(R, "global")). + // 5. If global is true, append the code unit 0x0067 (LATIN SMALL LETTER G) as the last code unit of result. + if object.get("global", context)?.to_boolean() { result.push('g'); } - if object - .get(&"ignoreCase".into(), this.clone(), context)? - .to_boolean() - { + // 6. Let ignoreCase be ! ToBoolean(? Get(R, "ignoreCase")). + // 7. If ignoreCase is true, append the code unit 0x0069 (LATIN SMALL LETTER I) as the last code unit of result. + if object.get("ignoreCase", context)?.to_boolean() { result.push('i'); } - if object - .get(&"multiline".into(), this.clone(), context)? - .to_boolean() - { + + // 8. Let multiline be ! ToBoolean(? Get(R, "multiline")). + // 9. If multiline is true, append the code unit 0x006D (LATIN SMALL LETTER M) as the last code unit of result. + if object.get("multiline", context)?.to_boolean() { result.push('m'); } - if object - .get(&"dotAll".into(), this.clone(), context)? - .to_boolean() - { + + // 10. Let dotAll be ! ToBoolean(? Get(R, "dotAll")). + // 11. If dotAll is true, append the code unit 0x0073 (LATIN SMALL LETTER S) as the last code unit of result. + if object.get("dotAll", context)?.to_boolean() { result.push('s'); } - if object - .get(&"unicode".into(), this.clone(), context)? - .to_boolean() - { + // 12. Let unicode be ! ToBoolean(? Get(R, "unicode")). + // 13. If unicode is true, append the code unit 0x0075 (LATIN SMALL LETTER U) as the last code unit of result. + if object.get("unicode", context)?.to_boolean() { result.push('u'); } - if object - .get(&"sticky".into(), this.clone(), context)? - .to_boolean() - { + + // 14. Let sticky be ! ToBoolean(? Get(R, "sticky")). + // 15. If sticky is true, append the code unit 0x0079 (LATIN SMALL LETTER Y) as the last code unit of result. + if object.get("sticky", context)?.to_boolean() { result.push('y'); } + // 16. Return result. return Ok(result.into()); } @@ -819,24 +820,21 @@ impl RegExp { } // 16. Let n be the number of elements in r's captures List. (This is the same value as 22.2.2.1's NcapturingParens.) + let n = match_value.captures.len() as u64; // 17. Assert: n < 23^2 - 1. - let n: u32 = match_value.captures.len() as u32; + assert!(n < 23u64.pow(2) - 1); // 18. Let A be ! ArrayCreate(n + 1). // 19. Assert: The mathematical value of A's "length" property is n + 1. - let a = Array::array_create(n + 1, None, context); + let a = Array::array_create(n + 1, None, context)?; // 20. Perform ! CreateDataPropertyOrThrow(A, "index", ๐”ฝ(lastIndex)). - a.set_property( - "index", - DataDescriptor::new(match_value.start(), Attribute::all()), - ); + a.create_data_property_or_throw("index", match_value.start(), context) + .unwrap(); // 21. Perform ! CreateDataPropertyOrThrow(A, "input", S). - a.set_property( - "input", - DataDescriptor::new(input.clone(), Attribute::all()), - ); + a.create_data_property_or_throw("input", input.clone(), context) + .unwrap(); // 22. Let matchedSubstr be the substring of S from lastIndex to e. let matched_substr = if let Some(s) = input.get(match_value.range()) { @@ -846,7 +844,8 @@ impl RegExp { }; // 23. Perform ! CreateDataPropertyOrThrow(A, "0", matchedSubstr). - a.set_property("0", DataDescriptor::new(matched_substr, Attribute::all())); + a.create_data_property_or_throw(0, matched_substr, context) + .unwrap(); // TODO: named capture groups // 24. If R contains any GroupName, then @@ -856,7 +855,8 @@ impl RegExp { let groups = Value::undefined(); // 26. Perform ! CreateDataPropertyOrThrow(A, "groups", groups). - a.set_property("groups", DataDescriptor::new(groups, Attribute::all())); + a.create_data_property_or_throw("groups", groups, context) + .unwrap(); // 27. For each integer i such that i โ‰ฅ 1 and i โ‰ค n, in ascending order, do for i in 1..=n { @@ -878,7 +878,8 @@ impl RegExp { }; // e. Perform ! CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(i)), capturedValue). - a.set_property(i, DataDescriptor::new(captured_value, Attribute::all())); + a.create_data_property_or_throw(i, captured_value, context) + .unwrap(); // TODO: named capture groups // f. If the ith capture of R was defined with a GroupName, then @@ -887,7 +888,7 @@ impl RegExp { } // 28. Return A. - Ok(a) + Ok(a.into()) } /// `RegExp.prototype[ @@match ]( string )` @@ -933,7 +934,7 @@ impl RegExp { this.set_field("lastIndex", Value::from(0), true, context)?; // d. Let A be ! ArrayCreate(0). - let a = Array::array_create(0, None, context); + let a = Array::array_create(0, None, context).unwrap(); // e. Let n be 0. let mut n = 0; @@ -951,14 +952,15 @@ impl RegExp { if n == 0 { return Ok(Value::null()); } else { - return Ok(a); + return Ok(a.into()); } } else { // 1. Let matchStr be ? ToString(? Get(result, "0")). let match_str = result.get_field("0", context)?.to_string(context)?; // 2. Perform ! CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(n)), matchStr). - Array::add_to_array_object(&a, &[match_str.clone().into()], context)?; + a.create_data_property_or_throw(n, match_str.clone(), context) + .unwrap(); // 3. If matchStr is the empty String, then if match_str.is_empty() { @@ -1412,7 +1414,7 @@ impl RegExp { RegExp::constructor(&constructor, &[this.clone(), new_flags.into()], context)?; // 11. Let A be ! ArrayCreate(0). - let a = Array::array_create(0, None, context); + let a = Array::array_create(0, None, context).unwrap(); // 12. Let lengthA be 0. let mut length_a = 0; @@ -1427,7 +1429,7 @@ impl RegExp { // 14. If lim is 0, return A. if lim == 0 { - return Ok(a); + return Ok(a.into()); } // 15. Let size be the length of S. @@ -1440,14 +1442,15 @@ impl RegExp { // b. If z is not null, return A. if !result.is_null() { - return Ok(a); + return Ok(a.into()); } // c. Perform ! CreateDataPropertyOrThrow(A, "0", S). - Array::add_to_array_object(&a, &[Value::from(arg_str)], context)?; + a.create_data_property_or_throw(0, arg_str, context) + .unwrap(); // d. Return A. - return Ok(a); + return Ok(a.into()); } // 17. Let p be 0. @@ -1488,14 +1491,15 @@ impl RegExp { let arg_str_substring: String = arg_str.chars().skip(p).take(q - p).collect(); // 2. Perform ! CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(lengthA)), T). - Array::add_to_array_object(&a, &[Value::from(arg_str_substring)], context)?; + a.create_data_property_or_throw(length_a, arg_str_substring, context) + .unwrap(); // 3. Set lengthA to lengthA + 1. length_a += 1; // 4. If lengthA = lim, return A. if length_a == lim { - return Ok(a); + return Ok(a.into()); } // 5. Set p to e. @@ -1519,14 +1523,15 @@ impl RegExp { let next_capture = result.get_field(i.to_string(), context)?; // b. Perform ! CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(lengthA)), nextCapture). - Array::add_to_array_object(&a, &[next_capture], context)?; + a.create_data_property_or_throw(length_a, next_capture, context) + .unwrap(); // d. Set lengthA to lengthA + 1. length_a += 1; // e. If lengthA = lim, return A. if length_a == lim { - return Ok(a); + return Ok(a.into()); } } @@ -1540,10 +1545,11 @@ impl RegExp { let arg_str_substring: String = arg_str.chars().skip(p).take(size - p).collect(); // 21. Perform ! CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(lengthA)), T). - Array::add_to_array_object(&a, &[Value::from(arg_str_substring)], context)?; + a.create_data_property_or_throw(length_a, arg_str_substring, context) + .unwrap(); // 22. Return A. - Ok(a) + Ok(a.into()) } } diff --git a/boa/src/builtins/regexp/regexp_string_iterator.rs b/boa/src/builtins/regexp/regexp_string_iterator.rs index 87195e326a3..a52913bd1a4 100644 --- a/boa/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa/src/builtins/regexp/regexp_string_iterator.rs @@ -154,7 +154,7 @@ impl RegExpStringIterator { let _timer = BoaProfiler::global().start_event("RegExp String Iterator", "init"); // Create prototype - let mut result = context.construct_object(); + let result = context.construct_object(); make_builtin_fn(Self::next, "next", &result, 0, context); result.set_prototype_instance(iterator_prototype); diff --git a/boa/src/builtins/set/mod.rs b/boa/src/builtins/set/mod.rs index 8f7543a063d..556d2d7de80 100644 --- a/boa/src/builtins/set/mod.rs +++ b/boa/src/builtins/set/mod.rs @@ -128,14 +128,14 @@ impl Set { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) .transpose()? .unwrap_or(set_prototype); - let mut obj = context.construct_object(); + let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let set = Value::from(obj); diff --git a/boa/src/builtins/set/set_iterator.rs b/boa/src/builtins/set/set_iterator.rs index 9e105a03223..b2dbefc4a28 100644 --- a/boa/src/builtins/set/set_iterator.rs +++ b/boa/src/builtins/set/set_iterator.rs @@ -139,7 +139,7 @@ impl SetIterator { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); // Create prototype - let mut set_iterator = context.construct_object(); + let set_iterator = context.construct_object(); make_builtin_fn(Self::next, "next", &set_iterator, 0, context); set_iterator.set_prototype_instance(iterator_prototype); diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index b6829c933ec..7663e61bb1c 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -178,7 +178,7 @@ impl String { let prototype = new_target .as_object() .and_then(|obj| { - obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + obj.__get__(&PROTOTYPE.into(), obj.clone().into(), context) .map(|o| o.as_object()) .transpose() }) @@ -1156,7 +1156,7 @@ impl String { /// `String.prototype.split ( separator, limit )` /// /// The split() method divides a String into an ordered list of substrings, puts these substrings into an array, and returns the array. - /// The division is done by searching for a pattern; where the pattern is provided as the first parameter in the method's call. + /// The division is done by searching for a pattern; where the pattern is provided as the first parameter in the method's call. /// /// More information: /// - [ECMAScript reference][spec] @@ -1189,7 +1189,7 @@ impl String { let this_str = this.to_string(context)?; // 4. Let A be ! ArrayCreate(0). - let a = Array::array_create(0, None, context); + let a = Array::array_create(0, None, context).unwrap(); // 5. Let lengthA be 0. let mut length_a = 0; @@ -1206,16 +1206,17 @@ impl String { // 8. If lim = 0, return A. if lim == 0 { - return Ok(a); + return Ok(a.into()); } // 9. If separator is undefined, then if separator.is_undefined() { // a. Perform ! CreateDataPropertyOrThrow(A, "0", S). - Array::add_to_array_object(&a, &[Value::from(this_str)], context)?; + a.create_data_property_or_throw(0, this_str, context) + .unwrap(); // b. Return A. - return Ok(a); + return Ok(a.into()); } // 10. Let s be the length of S. @@ -1226,11 +1227,12 @@ impl String { // a. If R is not the empty String, then if !separator_str.is_empty() { // i. Perform ! CreateDataPropertyOrThrow(A, "0", S). - Array::add_to_array_object(&a, &[Value::from(this_str)], context)?; + a.create_data_property_or_throw(0, this_str, context) + .unwrap(); } // b. Return A. - return Ok(a); + return Ok(a.into()); } // 12. Let p be 0. @@ -1264,18 +1266,15 @@ impl String { ); // 2. Perform ! CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(lengthA)), T). - Array::add_to_array_object( - &a, - &[Value::from(this_str_substring)], - context, - )?; + a.create_data_property_or_throw(length_a, this_str_substring, context) + .unwrap(); // 3. Set lengthA to lengthA + 1. length_a += 1; // 4. If lengthA = lim, return A. if length_a == lim { - return Ok(a); + return Ok(a.into()); } // 5. Set p to e. @@ -1298,10 +1297,11 @@ impl String { ); // 16. Perform ! CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(lengthA)), T). - Array::add_to_array_object(&a, &[Value::from(this_str_substring)], context)?; + a.create_data_property_or_throw(length_a, this_str_substring, context) + .unwrap(); // 17. Return A. - Ok(a) + Ok(a.into()) } /// String.prototype.valueOf() diff --git a/boa/src/builtins/string/string_iterator.rs b/boa/src/builtins/string/string_iterator.rs index 89380783eef..95a4f2558a8 100644 --- a/boa/src/builtins/string/string_iterator.rs +++ b/boa/src/builtins/string/string_iterator.rs @@ -74,7 +74,7 @@ impl StringIterator { let _timer = BoaProfiler::global().start_event("String Iterator", "init"); // Create prototype - let mut array_iterator = context.construct_object(); + let array_iterator = context.construct_object(); make_builtin_fn(Self::next, "next", &array_iterator, 0, context); array_iterator.set_prototype_instance(iterator_prototype); diff --git a/boa/src/context.rs b/boa/src/context.rs index 568367a5519..718931d2298 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -530,7 +530,7 @@ impl Context { #[inline] pub(crate) fn has_property(&self, obj: &Value, key: &PropertyKey) -> bool { if let Some(obj) = obj.as_object() { - obj.has_property(key) + obj.__has_property__(key) } else { false } diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index a138d7e098d..1bc61f66317 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -135,7 +135,7 @@ impl GcObject { let body = if let Some(function) = self.borrow().as_function() { if construct && !function.is_constructable() { let name = self - .get(&"name".into(), self.clone().into(), context)? + .__get__(&"name".into(), self.clone().into(), context)? .display() .to_string(); return context.throw_type_error(format!("{} is not a constructor", name)); @@ -161,7 +161,7 @@ impl GcObject { // prototype as prototype for the new object // see // see - let proto = this_target.as_object().unwrap().get( + let proto = this_target.as_object().unwrap().__get__( &PROTOTYPE.into(), this_target.clone(), context, @@ -458,50 +458,61 @@ impl GcObject { /// /// Panics if the object is currently mutably borrowed. pub fn to_property_descriptor(&self, context: &mut Context) -> Result { + // 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(); - let enumerable_key = PropertyKey::from("enumerable"); - if self.has_property(&enumerable_key) - && self - .get(&enumerable_key, self.clone().into(), context)? - .to_boolean() - { + // 3. Let hasEnumerable be ? HasProperty(Obj, "enumerable"). + let has_enumerable = self.has_property("enumerable"); + // 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; } - let configurable_key = PropertyKey::from("configurable"); - if self.has_property(&configurable_key) - && self - .get(&configurable_key, self.clone().into(), context)? - .to_boolean() - { + // 5. Let hasConfigurable be ? HasProperty(Obj, "configurable"). + let has_configurable = self.has_property("configurable"); + // 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; - let value_key = PropertyKey::from("value"); - if self.has_property(&value_key) { - value = Some(self.get(&value_key, self.clone().into(), context)?); + // 7. Let hasValue be ? HasProperty(Obj, "value"). + let has_value = self.has_property("value"); + // 8. If hasValue is true, then + if has_value { + // 8.a. Let value be ? Get(Obj, "value"). + // 8.b. Set desc.[[Value]] to value. + value = Some(self.get("value", context)?); } - let mut has_writable = false; - let writable_key = PropertyKey::from("writable"); - if self.has_property(&writable_key) { - has_writable = true; - if self - .get(&writable_key, self.clone().into(), context)? - .to_boolean() - { + // 9. Let hasWritable be ? HasProperty(Obj, ). + let has_writable = self.has_property("writable"); + // 10. If hasWritable is true, then + if has_writable { + // 10.a. Let writable be ! ToBoolean(? Get(Obj, "writable")). + if self.get("writable", context)?.to_boolean() { + // 10.b. Set desc.[[Writable]] to writable. attribute |= Attribute::WRITABLE; } } + // 11. Let hasGet be ? HasProperty(Obj, "get"). + let has_get = self.has_property("get"); + // 12. If hasGet is true, then let mut get = None; - let get_key = PropertyKey::from("get"); - if self.has_property(&get_key) { - let getter = self.get(&get_key, self.clone().into(), context)?; + if has_get { + // 12.a. Let getter be ? Get(Obj, "get"). + let getter = self.get("get", context)?; + // 12.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() => { + // 12.c. Set desc.[[Get]] to getter. get = Some(object.clone()); } _ => { @@ -512,12 +523,17 @@ impl GcObject { } } + // 13. Let hasSet be ? HasProperty(Obj, "set"). + let has_set = self.has_property("set"); + // 14. If hasSet is true, then let mut set = None; - let set_key = PropertyKey::from("set"); - if self.has_property(&set_key) { - let setter = self.get(&set_key, self.clone().into(), context)?; + 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()); } _ => { @@ -528,7 +544,10 @@ impl GcObject { }; } + // 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")); } @@ -612,7 +631,7 @@ impl GcObject { /// or if th prototype is not an object or undefined. #[inline] #[track_caller] - pub fn set_prototype_instance(&mut self, prototype: Value) -> bool { + pub fn set_prototype_instance(&self, prototype: Value) -> bool { self.borrow_mut().set_prototype_instance(prototype) } @@ -770,13 +789,17 @@ impl GcObject { where K: Into, { - let key = key.into(); - let value = self.get(&key, self.clone().into(), context)?; + // 1. Assert: IsPropertyKey(P) is true. + // 2. Let func be ? GetV(V, P). + let value = self.get(key, context)?; + // 3. If func is either undefined or null, return undefined. if value.is_null_or_undefined() { return Ok(None); } + // 4. If IsCallable(func) is false, throw a TypeError exception. + // 5. Return func. match value.as_object() { Some(object) if object.is_callable() => Ok(Some(object)), _ => Err(context @@ -796,25 +819,31 @@ impl GcObject { context: &mut Context, value: &Value, ) -> Result { + // 1. If IsCallable(C) is false, return false. if !self.is_callable() { return Ok(false); } - // TODO: If C has a [[BoundTargetFunction]] internal slot, then - // Let BC be C.[[BoundTargetFunction]]. - // Return ? InstanceofOperator(O, BC). + // TODO: 2. If C has a [[BoundTargetFunction]] internal slot, then + // a. Let BC be C.[[BoundTargetFunction]]. + // b. Return ? InstanceofOperator(O, BC). + // 3. If Type(O) is not Object, return false. if let Some(object) = value.as_object() { - if let Some(prototype) = self - .get(&"prototype".into(), self.clone().into(), context)? - .as_object() - { - let mut object = object.get_prototype_of(); + // 4. Let P be ? Get(C, "prototype"). + // 5. If Type(P) is not Object, throw a TypeError exception. + if let Some(prototype) = self.get("prototype", context)?.as_object() { + // 6. Repeat, + // 6.a. Set O to ? O.[[GetPrototypeOf]](). + // 6.b. If O is null, return false. + let mut object = object.__get_prototype_of__(); while let Some(object_prototype) = object.as_object() { + // c. If SameValue(P, O) is true, return true. if GcObject::equals(&prototype, &object_prototype) { return Ok(true); } - object = object_prototype.get_prototype_of(); + // 6.a. Set O to ? O.[[GetPrototypeOf]](). + object = object_prototype.__get_prototype_of__(); } Ok(false) @@ -833,7 +862,7 @@ impl GcObject { K: Into, { let key = key.into(); - self.get_own_property(&key).is_some() + self.__get_own_property__(&key).is_some() } /// Defines the property or throws a `TypeError` if the operation fails. @@ -856,7 +885,7 @@ impl GcObject { let key = key.into(); let desc = desc.into(); - let success = self.define_own_property(key.clone(), desc, context)?; + let success = self.__define_own_property__(key.clone(), desc, context)?; if !success { Err(context.construct_type_error(format!("Cannot redefine property: {}", key))) } else { @@ -882,11 +911,7 @@ impl GcObject { // 1. Assert: Type(O) is Object. // 2. Let C be ? Get(O, "constructor"). - let c = self.clone().get( - &PropertyKey::from("constructor"), - self.clone().into(), - context, - )?; + let c = self.clone().get("constructor", context)?; // 3. If C is undefined, return defaultConstructor. if c.is_undefined() { diff --git a/boa/src/object/internal_methods.rs b/boa/src/object/internal_methods.rs index 3a6a1891b67..2e55136cf5e 100644 --- a/boa/src/object/internal_methods.rs +++ b/boa/src/object/internal_methods.rs @@ -18,14 +18,165 @@ impl GcObject { /// More information: /// - [ECMAScript reference][spec] /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p + /// [spec]: https://tc39.es/ecma262/#sec-hasproperty + pub fn has_property(&self, key: K) -> bool + where + K: Into, + { + // 1. Assert: Type(O) is Object. + // 2. Assert: IsPropertyKey(P) is true. + // 3. Return ? O.[[HasProperty]](P). + self.__has_property__(&key.into()) + } + + /// Check if it is extensible. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-isextensible-o + #[inline] + pub fn is_extensible(&self) -> bool { + // 1. Assert: Type(O) is Object. + // 2. Return ? O.[[IsExtensible]](). + self.__is_extensible__() + } + + /// Delete property, if deleted return `true`. + #[inline] + pub fn delete(&self, key: K) -> bool + where + K: Into, + { + self.__delete__(&key.into()) + } + + /// Delete property. + /// + /// It throws an exception if the property is not configurable. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow + #[inline] + pub fn delete_property_or_throw(&self, key: K, context: &mut Context) -> Result + where + K: Into, + { + // 1. Assert: Type(O) is Object. + // 2. Assert: IsPropertyKey(P) is true. + // 3. Let success be ? O.[[Delete]](P). + let success = self.__delete__(&key.into()); + // 4. If success is false, throw a TypeError exception. + if !success { + return Err(context.construct_type_error("property is not configurable")); + } + // 5. Return success. + Ok(success) + } + + pub fn get(&self, key: K, context: &mut Context) -> Result + where + K: Into, + { + // 1. Assert: Type(O) is Object. + // 2. Assert: IsPropertyKey(P) is true. + // 3. Return ? O.[[Get]](P, O). + self.__get__(&key.into(), self.clone().into(), context) + } + + pub fn set(&self, key: K, value: V, throw: bool, context: &mut Context) -> Result + where + K: Into, + V: Into, + { + // 1. Assert: Type(O) is Object. + // 2. Assert: IsPropertyKey(P) is true. + // 3. Assert: Type(Throw) is Boolean. + // 4. Let success be ? O.[[Set]](P, V, O). + let success = self.__set__(key.into(), value.into(), self.clone().into(), context)?; + // 5. If success is false and Throw is true, throw a TypeError exception. + if !success && throw { + return Err(context.construct_type_error("...")); + } + // 6. Return success. + Ok(success) + } + + pub fn define_own_property( + &mut self, + key: K, + desc: P, + context: &mut Context, + ) -> Result + where + K: Into, + P: Into, + { + // 1. Assert: Type(O) is Object. + // 2. Assert: IsPropertyKey(P) is true. + // 3. Let success be ? O.[[DefineOwnProperty]](P, desc). + let success = self.__define_own_property__(key.into(), desc.into(), context)?; + // 4. If success is false, throw a TypeError exception. + if !success { + return Err(context.construct_type_error("...")); + } + // 5. Return success. + Ok(success) + } + + pub fn create_data_property( + &self, + key: K, + value: V, + context: &mut Context, + ) -> Result + where + K: Into, + V: Into, + { + // 1. Assert: Type(O) is Object. + // 2. Assert: IsPropertyKey(P) is true. + // 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. + let new_desc = DataDescriptor::new( + value, + Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, + ); + // 4. Return ? O.[[DefineOwnProperty]](P, newDesc). + self.__define_own_property__(key.into(), new_desc.into(), context) + } + + pub fn create_data_property_or_throw( + &self, + key: K, + value: V, + context: &mut Context, + ) -> Result + where + K: Into, + V: Into, + { + // 1. Assert: Type(O) is Object. + // 2. Assert: IsPropertyKey(P) is true. + // 3. Let success be ? CreateDataProperty(O, P, V). + let success = self.create_data_property(key, value, context)?; + // 4. If success is false, throw a TypeError exception. + if !success { + return Err(context.construct_type_error("...")); + } + // 5. Return success. + Ok(success) + } + + /// `[[hasProperty]]` #[inline] - pub fn has_property(&self, key: &PropertyKey) -> bool { - let prop = self.get_own_property(key); + pub(crate) fn __has_property__(&self, key: &PropertyKey) -> bool { + let prop = self.__get_own_property__(key); if prop.is_none() { - let parent = self.get_prototype_of(); + let parent = self.__get_prototype_of__(); return if let Value::Object(ref object) = parent { - object.has_property(key) + object.__has_property__(key) } else { false }; @@ -40,7 +191,7 @@ impl GcObject { /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible #[inline] - pub fn is_extensible(&self) -> bool { + pub(crate) fn __is_extensible__(&self) -> bool { self.borrow().extensible } @@ -51,15 +202,15 @@ impl GcObject { /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions #[inline] - pub fn prevent_extensions(&mut self) -> bool { + pub fn __prevent_extensions__(&mut self) -> bool { self.borrow_mut().extensible = false; true } /// Delete property. #[inline] - pub fn delete(&mut self, key: &PropertyKey) -> bool { - match self.get_own_property(key) { + pub(crate) fn __delete__(&self, key: &PropertyKey) -> bool { + match self.__get_own_property__(key) { Some(desc) if desc.configurable() => { self.remove(&key); true @@ -70,13 +221,17 @@ impl GcObject { } /// `[[Get]]` - /// - pub fn get(&self, key: &PropertyKey, receiver: Value, context: &mut Context) -> Result { - match self.get_own_property(key) { + pub fn __get__( + &self, + key: &PropertyKey, + receiver: Value, + context: &mut Context, + ) -> Result { + match self.__get_own_property__(key) { None => { // parent will either be null or an Object - if let Some(parent) = self.get_prototype_of().as_object() { - Ok(parent.get(key, receiver, context)?) + if let Some(parent) = self.__get_prototype_of__().as_object() { + Ok(parent.__get__(key, receiver, context)?) } else { Ok(Value::undefined()) } @@ -92,9 +247,8 @@ impl GcObject { } /// `[[Set]]` - /// - pub fn set( - &mut self, + pub fn __set__( + &self, key: PropertyKey, value: Value, receiver: Value, @@ -103,10 +257,10 @@ impl GcObject { let _timer = BoaProfiler::global().start_event("Object::set", "object"); // Fetch property key - let own_desc = if let Some(desc) = self.get_own_property(&key) { + let own_desc = if let Some(desc) = self.__get_own_property__(&key) { desc - } else if let Some(ref mut parent) = self.get_prototype_of().as_object() { - return parent.set(key, value, receiver, context); + } else if let Some(ref mut parent) = self.__get_prototype_of__().as_object() { + return parent.__set__(key, value, receiver, context); } else { DataDescriptor::new(Value::undefined(), Attribute::all()).into() }; @@ -117,14 +271,14 @@ impl GcObject { return Ok(false); } if let Some(ref mut receiver) = receiver.as_object() { - if let Some(ref existing_desc) = receiver.get_own_property(&key) { + if let Some(ref existing_desc) = receiver.__get_own_property__(&key) { match existing_desc { PropertyDescriptor::Accessor(_) => Ok(false), PropertyDescriptor::Data(existing_data_desc) => { if !existing_data_desc.writable() { return Ok(false); } - receiver.define_own_property( + receiver.__define_own_property__( key, DataDescriptor::new(value, existing_data_desc.attributes()) .into(), @@ -133,7 +287,7 @@ impl GcObject { } } } else { - receiver.define_own_property( + receiver.__define_own_property__( key, DataDescriptor::new(value, Attribute::all()).into(), context, @@ -151,21 +305,13 @@ impl GcObject { } } - /// Define an own property. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc - pub fn define_own_property( - &mut self, - key: K, + /// `[[defineOwnProperty]]` + pub fn __define_own_property__( + &self, + key: PropertyKey, desc: PropertyDescriptor, context: &mut Context, - ) -> Result - where - K: Into, - { + ) -> Result { if self.is_array() { self.array_define_own_property(key, desc, context) } else { @@ -179,16 +325,12 @@ impl GcObject { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty - pub fn ordinary_define_own_property(&mut self, key: K, desc: PropertyDescriptor) -> bool - where - K: Into, - { + pub fn ordinary_define_own_property(&self, key: PropertyKey, desc: PropertyDescriptor) -> bool { let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object"); - let key = key.into(); - let extensible = self.is_extensible(); + let extensible = self.__is_extensible__(); - let current = if let Some(desc) = self.get_own_property(&key) { + let current = if let Some(desc) = self.__get_own_property__(&key) { desc } else { if !extensible { @@ -279,7 +421,7 @@ impl GcObject { /// /// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc fn array_define_own_property( - &mut self, + &self, key: K, desc: PropertyDescriptor, context: &mut Context, @@ -292,11 +434,11 @@ impl GcObject { PropertyKey::String(ref s) if s == "length" => { match desc { PropertyDescriptor::Accessor(_) => { - return Ok(self.ordinary_define_own_property("length", desc)) + return Ok(self.ordinary_define_own_property("length".into(), desc)) } PropertyDescriptor::Data(ref d) => { if d.value().is_undefined() { - return Ok(self.ordinary_define_own_property("length", desc)); + return Ok(self.ordinary_define_own_property("length".into(), desc)); } let new_len = d.value().to_u32(context)?; let number_len = d.value().to_number(context)?; @@ -306,11 +448,13 @@ impl GcObject { } let mut new_len_desc = PropertyDescriptor::Data(DataDescriptor::new(new_len, d.attributes())); - 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_desc = old_len_desc.as_data_descriptor().unwrap(); let old_len = old_len_desc.value(); if new_len >= old_len.to_u32(context)? { - return Ok(self.ordinary_define_own_property("length", new_len_desc)); + return Ok( + self.ordinary_define_own_property("length".into(), new_len_desc) + ); } if !old_len_desc.writable() { return Ok(false); @@ -326,7 +470,8 @@ impl GcObject { )); false }; - if !self.ordinary_define_own_property("length", new_len_desc.clone()) { + if !self.ordinary_define_own_property("length".into(), new_len_desc.clone()) + { return Ok(false); } let keys_to_delete = { @@ -340,7 +485,7 @@ impl GcObject { keys }; for key in keys_to_delete.into_iter().rev() { - if !self.delete(&key.into()) { + 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); @@ -349,7 +494,7 @@ impl GcObject { key + 1, new_len_desc_attribute, )); - self.ordinary_define_own_property("length", new_len_desc); + self.ordinary_define_own_property("length".into(), new_len_desc); return Ok(false); } } @@ -360,14 +505,14 @@ impl GcObject { new_len, new_desc_attr, )); - self.ordinary_define_own_property("length", new_desc); + self.ordinary_define_own_property("length".into(), new_desc); } } } Ok(true) } 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_data_desc.value().to_u32(context)?; if index >= old_len && !old_len_data_desc.writable() { @@ -379,7 +524,7 @@ impl GcObject { index + 1, old_len_data_desc.attributes(), )); - self.ordinary_define_own_property("length", desc); + self.ordinary_define_own_property("length".into(), desc); } Ok(true) } else { @@ -393,7 +538,7 @@ impl GcObject { /// Gets own property of 'Object' /// #[inline] - pub fn get_own_property(&self, key: &PropertyKey) -> Option { + pub fn __get_own_property__(&self, key: &PropertyKey) -> Option { let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); let object = self.borrow(); @@ -500,9 +645,9 @@ impl GcObject { let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new(); 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() { - let desc_obj = props.get(&next_key, props.clone().into(), context)?; + let desc_obj = props.__get__(&next_key, props.clone().into(), context)?; let desc = desc_obj.to_property_descriptor(context)?; descriptors.push((next_key, desc)); } @@ -510,7 +655,7 @@ impl GcObject { } for (p, d) in descriptors { - self.define_own_property(p, d, context)?; + self.__define_own_property__(p, d, context)?; } Ok(()) @@ -528,13 +673,13 @@ impl GcObject { /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf #[inline] - pub fn set_prototype_of(&mut self, val: Value) -> bool { + pub fn __set_prototype_of__(&mut self, val: Value) -> bool { debug_assert!(val.is_object() || val.is_null()); - let current = self.get_prototype_of(); + let current = self.__get_prototype_of__(); if Value::same_value(¤t, &val) { return true; } - if !self.is_extensible() { + if !self.__is_extensible__() { return false; } let mut p = val.clone(); @@ -548,7 +693,7 @@ impl GcObject { let prototype = p .as_object() .expect("prototype should be null or object") - .get_prototype_of(); + .__get_prototype_of__(); p = prototype; } } @@ -566,14 +711,14 @@ impl GcObject { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf #[inline] #[track_caller] - pub fn get_prototype_of(&self) -> Value { + pub fn __get_prototype_of__(&self) -> Value { self.borrow().prototype.clone() } /// Helper function for property insertion. #[inline] #[track_caller] - pub(crate) fn insert(&mut self, key: K, property: P) -> Option + pub(crate) fn insert(&self, key: K, property: P) -> Option where K: Into, P: Into, @@ -584,7 +729,7 @@ impl GcObject { /// Helper function for property removal. #[inline] #[track_caller] - pub(crate) fn remove(&mut self, key: &PropertyKey) -> Option { + pub(crate) fn remove(&self, key: &PropertyKey) -> Option { self.borrow_mut().remove(key) } @@ -594,7 +739,7 @@ impl GcObject { /// with that field, otherwise None is returned. #[inline] pub fn insert_property( - &mut self, + &self, key: K, value: V, attribute: Attribute, @@ -647,6 +792,7 @@ impl GcObject { element_types: &[Type], context: &mut Context, ) -> Result> { + // 1. If elementTypes is not present, set elementTypes to ยซ Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object ยป. let types = if element_types.is_empty() { &[ Type::Undefined, @@ -656,24 +802,44 @@ impl GcObject { Type::Symbol, Type::Number, Type::BigInt, - Type::Symbol, + Type::Object, ] } else { element_types }; - let len = self - .get(&"length".into(), self.clone().into(), context)? - .to_length(context)?; + + // TODO: 2. If Type(obj) is not Object, throw a TypeError exception. + + // 3. Let len be ? LengthOfArrayLike(obj). + let len = self.length_of_array_like(context)?; + + // 4. Let list be a new empty List. let mut list = Vec::with_capacity(len); + + // 5. Let index be 0. + // 6. Repeat, while index < len, for index in 0..len { - let next = self.get(&index.into(), self.clone().into(), context)?; + // 6.a. Let indexName be ! ToString(๐”ฝ(index)). + // 6.b. Let next be ? Get(obj, indexName). + let next = self.get(index, context)?; + // 6.c. If Type(next) is not an element of elementTypes, throw a TypeError exception. if !types.contains(&next.get_type()) { return Err(context.construct_type_error("bad type")); } + // 6.d. Append next as the last element of list. list.push(next.clone()); + // 6.e. Set index to index + 1. } + + // 7. Return list. Ok(list) } + + pub(crate) fn length_of_array_like(&self, context: &mut Context) -> Result { + // 1. Assert: Type(obj) is Object. + // 2. Return โ„(? ToLength(? Get(obj, "length"))). + self.get("length", context)?.to_length(context) + } } impl Object { diff --git a/boa/src/property/mod.rs b/boa/src/property/mod.rs index b5411c6645d..1acc45bca5d 100644 --- a/boa/src/property/mod.rs +++ b/boa/src/property/mod.rs @@ -434,6 +434,16 @@ impl From for PropertyKey { } } +impl From for PropertyKey { + fn from(value: u64) -> Self { + if let Ok(index) = u32::try_from(value) { + PropertyKey::Index(index) + } else { + PropertyKey::String(JsString::from(value.to_string())) + } + } +} + impl From for PropertyKey { fn from(value: isize) -> Self { if let Ok(index) = u32::try_from(value) { diff --git a/boa/src/syntax/ast/node/operator/unary_op/mod.rs b/boa/src/syntax/ast/node/operator/unary_op/mod.rs index a9eae76aa06..542f1703424 100644 --- a/boa/src/syntax/ast/node/operator/unary_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/unary_op/mod.rs @@ -94,14 +94,14 @@ impl Executable for UnaryOp { .obj() .run(context)? .to_object(context)? - .delete(&get_const_field.field().into()), + .__delete__(&get_const_field.field().into()), ), Node::GetField(ref get_field) => { let obj = get_field.obj().run(context)?; let field = &get_field.field().run(context)?; let res = obj .to_object(context)? - .delete(&field.to_property_key(context)?); + .__delete__(&field.to_property_key(context)?); return Ok(Value::boolean(res)); } Node::Identifier(_) => Value::boolean(false), diff --git a/boa/src/value/conversions.rs b/boa/src/value/conversions.rs index 137c33eb773..ab4caa403dc 100644 --- a/boa/src/value/conversions.rs +++ b/boa/src/value/conversions.rs @@ -68,6 +68,7 @@ impl Display for TryFromCharError { } impl From for Value { + #[inline] fn from(value: f64) -> Self { Self::rational(value) } @@ -85,32 +86,45 @@ impl From for Value { } impl From for Value { + #[inline] fn from(value: i32) -> Value { Value::integer(value) } } impl From for Value { + #[inline] fn from(value: JsBigInt) -> Self { Value::BigInt(value) } } impl From for Value { + #[inline] fn from(value: usize) -> Value { - Value::integer(value as i32) + if let Ok(value) = i32::try_from(value) { + Value::integer(value) + } else { + Value::rational(value as f64) + } } } -impl From for Value { - fn from(value: bool) -> Self { - Value::boolean(value) +impl From for Value { + #[inline] + fn from(value: u64) -> Value { + if let Ok(value) = i32::try_from(value) { + Value::integer(value) + } else { + Value::rational(value as f64) + } } } -impl From<&Value> for bool { - fn from(value: &Value) -> Self { - value.to_boolean() +impl From for Value { + #[inline] + fn from(value: bool) -> Self { + Value::boolean(value) } } @@ -148,6 +162,7 @@ impl From for Value { } impl From for Value { + #[inline] fn from(object: GcObject) -> Self { let _timer = BoaProfiler::global().start_event("From", "value"); Value::Object(object) @@ -158,12 +173,14 @@ impl From for Value { pub struct TryFromObjectError; impl Display for TryFromObjectError { + #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Could not convert value to an Object type") } } impl From<()> for Value { + #[inline] fn from(_: ()) -> Self { Value::null() } @@ -173,6 +190,7 @@ impl From> for Value where T: Into, { + #[inline] fn from(value: Option) -> Self { match value { Some(value) => value.into(), diff --git a/boa/src/value/display.rs b/boa/src/value/display.rs index cb7a0d9e93d..157e328b054 100644 --- a/boa/src/value/display.rs +++ b/boa/src/value/display.rs @@ -102,7 +102,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children: } ObjectData::Array => { let len = v - .get_own_property(&PropertyKey::from("length")) + .__get_own_property__(&PropertyKey::from("length")) // TODO: do this in a better way `unwrap` .unwrap() // FIXME: handle accessor descriptors @@ -123,7 +123,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children: // Introduce recursive call to stringify any objects // which are part of the Array log_string_from( - &v.get_own_property(&i.into()) + &v.__get_own_property__(&i.into()) // FIXME: handle accessor descriptors .and_then(|p| p.as_data_descriptor().map(|d| d.value())) .unwrap_or_default(), diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index 95ba30773eb..56e3dcbd16b 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -426,9 +426,7 @@ impl Value { where Key: Into, { - self.as_object() - .map(|mut x| x.remove(&key.into())) - .is_some() + self.as_object().map(|x| x.remove(&key.into())).is_some() } /// Resolve the property in the object. @@ -442,7 +440,7 @@ impl Value { let _timer = BoaProfiler::global().start_event("Value::get_property", "value"); match self { Self::Object(ref object) => { - let property = object.get_own_property(&key); + let property = object.__get_own_property__(&key); if property.is_some() { return property; } @@ -461,7 +459,8 @@ impl Value { { let _timer = BoaProfiler::global().start_event("Value::get_field", "value"); if let Self::Object(ref obj) = *self { - obj.clone().get(&key.into(), obj.clone().into(), context) + obj.clone() + .__get__(&key.into(), obj.clone().into(), context) } else { Ok(Value::undefined()) } @@ -475,7 +474,7 @@ impl Value { { let _timer = BoaProfiler::global().start_event("Value::has_field", "value"); self.as_object() - .map(|object| object.has_property(&key.into())) + .map(|object| object.__has_property__(&key.into())) .unwrap_or(false) } @@ -512,7 +511,7 @@ impl Value { // 4. Let success be ? O.[[Set]](P, V, O). let success = obj .clone() - .set(key, value.clone(), obj.clone().into(), context)?; + .__set__(key, value.clone(), obj.clone().into(), context)?; // 5. If success is false and Throw is true, throw a TypeError exception. // 6. Return success. @@ -540,7 +539,7 @@ impl Value { K: Into, P: Into, { - if let Some(mut object) = self.as_object() { + if let Some(object) = self.as_object() { object.insert(key.into(), property.into()); } } @@ -699,7 +698,7 @@ impl Value { Value::String(ref string) => { let prototype = context.standard_objects().string_object().prototype(); - let mut object = GcObject::new(Object::with_prototype( + let object = GcObject::new(Object::with_prototype( prototype.into(), ObjectData::String(string.clone()), )); diff --git a/boa/src/value/tests.rs b/boa/src/value/tests.rs index d5cf20e3de6..ed0ef7da5af 100644 --- a/boa/src/value/tests.rs +++ b/boa/src/value/tests.rs @@ -267,7 +267,7 @@ fn string_length_is_not_enumerable() { let object = Value::from("foo").to_object(&mut context).unwrap(); let length_desc = object - .get_own_property(&PropertyKey::from("length")) + .__get_own_property__(&PropertyKey::from("length")) .unwrap(); assert!(!length_desc.enumerable()); } @@ -279,7 +279,7 @@ fn string_length_is_in_utf16_codeunits() { // ๐Ÿ˜€ is one Unicode code point, but 2 UTF-16 code units let object = Value::from("๐Ÿ˜€").to_object(&mut context).unwrap(); let length_desc = object - .get_own_property(&PropertyKey::from("length")) + .__get_own_property__(&PropertyKey::from("length")) .unwrap(); assert_eq!( length_desc diff --git a/boa/src/vm/mod.rs b/boa/src/vm/mod.rs index 810ec17b30e..741f9847ab8 100644 --- a/boa/src/vm/mod.rs +++ b/boa/src/vm/mod.rs @@ -370,7 +370,7 @@ impl<'a> Vm<'a> { }; let name = self.code.names[index as usize].clone(); - let result = object.get(&name.into(), value, self.context)?; + let result = object.get(name, self.context)?; self.push(result) } @@ -384,7 +384,7 @@ impl<'a> Vm<'a> { }; let key = key.to_property_key(self.context)?; - let result = object.get(&key, value, self.context)?; + let result = object.get(key, self.context)?; self.push(result) } @@ -393,7 +393,7 @@ impl<'a> Vm<'a> { let object = self.pop(); let value = self.pop(); - let mut object = if let Some(object) = object.as_object() { + let object = if let Some(object) = object.as_object() { object } else { object.to_object(self.context)? @@ -401,20 +401,20 @@ impl<'a> Vm<'a> { let name = self.code.names[index as usize].clone(); - object.set(name.into(), value, object.clone().into(), self.context)?; + object.set(name, value, true, self.context)?; } Opcode::SetPropertyByValue => { let object = self.pop(); let key = self.pop(); let value = self.pop(); - let mut object = if let Some(object) = object.as_object() { + let object = if let Some(object) = object.as_object() { object } else { object.to_object(self.context)? }; let key = key.to_property_key(self.context)?; - object.set(key, value, object.clone().into(), self.context)?; + object.set(key, value, true, self.context)?; } Opcode::Throw => { let value = self.pop();