diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 605392bcc3a..a0ea312bc13 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -291,10 +291,7 @@ impl Array { return Ok(spreadable.to_boolean()); } // 4. Return ? IsArray(O). - match this.as_object() { - Some(obj) => Ok(obj.is_array()), - _ => Ok(false), - } + this.is_array(context) } /// `get Array [ @@species ]` @@ -323,7 +320,7 @@ impl Array { ) -> JsResult { // 1. Let isArray be ? IsArray(originalArray). // 2. If isArray is false, return ? ArrayCreate(length). - if !original_array.is_array() { + if !original_array.is_array_abstract(context)? { return Self::array_create(length, None, context); } // 3. Let C be ? Get(originalArray, "constructor"). @@ -411,13 +408,15 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.isarray /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray - pub(crate) fn is_array(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { - Ok(args - .get_or_undefined(0) - .as_object() - .map(|obj| obj.borrow().is_array()) - .unwrap_or_default() - .into()) + pub(crate) fn is_array( + _: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Return ? IsArray(arg). + args.get_or_undefined(0) + .is_array(context) + .map(|ok| ok.into()) } /// `Array.of(...items)` diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 0f3cbc42dfc..60778c7cf0e 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -135,7 +135,7 @@ impl Json { if let Some(obj) = val.as_object() { // a. Let isArray be ? IsArray(val). // b. If isArray is true, then - if obj.is_array() { + if obj.is_array_abstract(context)? { // i. Let I be 0. // ii. Let len be ? LengthOfArrayLike(val). // iii. Repeat, while I < len, @@ -237,7 +237,7 @@ impl Json { } else { // i. Let isArray be ? IsArray(replacer). // ii. If isArray is true, then - if replacer_obj.is_array() { + if replacer_obj.is_array_abstract(context)? { // 1. Set PropertyList to a new empty List. let mut property_set = indexmap::IndexSet::new(); @@ -457,7 +457,7 @@ impl Json { // a. Let isArray be ? IsArray(value). // b. If isArray is true, return ? SerializeJSONArray(state, value). // c. Return ? SerializeJSONObject(state, value). - return if obj.is_array() { + return if obj.is_array_abstract(context)? { Ok(Some(Self::serialize_json_array(state, obj, context)?)) } else { Ok(Some(Self::serialize_json_object(state, obj, context)?)) diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 0998b8f55f5..ae007eea7b2 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -55,10 +55,10 @@ impl BuiltIn for Object { .length(Self::LENGTH) .inherit(None) .method(Self::has_own_property, "hasOwnProperty", 1) - .method(Self::property_is_enumerable, "propertyIsEnumerable", 0) + .method(Self::property_is_enumerable, "propertyIsEnumerable", 1) .method(Self::to_string, "toString", 0) .method(Self::value_of, "valueOf", 0) - .method(Self::is_prototype_of, "isPrototypeOf", 0) + .method(Self::is_prototype_of, "isPrototypeOf", 1) .static_method(Self::create, "create", 2) .static_method(Self::set_prototype_of, "setPrototypeOf", 2) .static_method(Self::get_prototype_of, "getPrototypeOf", 1) @@ -482,20 +482,22 @@ impl Object { 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 = this.to_object(context).expect("toObject cannot fail here"); + + // 4. Let isArray be ? IsArray(O). + // 5. If isArray is true, let builtinTag be "Array". + let builtin_tag = if JsValue::from(o.clone()).is_array(context)? { + "Array" + } else { + // 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 o = o.borrow(); match o.kind() { ObjectKind::Array => "Array", diff --git a/boa/src/object/operations.rs b/boa/src/object/operations.rs index 3e90002939d..2a2b2c0a991 100644 --- a/boa/src/object/operations.rs +++ b/boa/src/object/operations.rs @@ -559,6 +559,38 @@ impl JsObject { } } + /// Abstract operation `IsArray ( argument )` + /// + /// Check if a value is an array. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-isarray + pub(crate) fn is_array_abstract(&self, context: &mut Context) -> JsResult { + // Note: The spec specifies this function for JsValue. + // It is implemented for JsObject for convenience. + + // 2. If argument is an Array exotic object, return true. + if self.is_array() { + return Ok(true); + } + + // 3. If argument is a Proxy exotic object, then + let object = self.borrow(); + if let Some(proxy) = object.as_proxy() { + // a. If argument.[[ProxyHandler]] is null, throw a TypeError exception. + // b. Let target be argument.[[ProxyTarget]]. + let (target, _) = proxy.try_data(context)?; + + // c. Return ? IsArray(target). + return target.is_array_abstract(context); + } + + // 4. Return false. + Ok(false) + } + // todo: GetFunctionRealm // todo: CopyDataProperties diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index ea3f99893cd..e7799d595cb 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -1026,24 +1026,24 @@ impl JsValue { .into() } - /// Check if it is an array. + /// Abstract operation `IsArray ( argument )` + /// + /// Check if a value is an array. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-isarray - pub(crate) fn is_array(&self, _context: &mut Context) -> JsResult { + pub(crate) fn is_array(&self, context: &mut Context) -> JsResult { + // Note: The spec specifies this function for JsValue. + // The main part of the function is implemented for JsObject. + // 1. If Type(argument) is not Object, return false. if let Some(object) = self.as_object() { - // 2. If argument is an Array exotic object, return true. - // a. If argument.[[ProxyHandler]] is null, throw a TypeError exception. - // 3. If argument is a Proxy exotic object, then - // b. Let target be argument.[[ProxyTarget]]. - // c. Return ? IsArray(target). - // TODO: handle ProxyObjects - Ok(object.is_array()) - } else { - // 4. Return false. + object.is_array_abstract(context) + } + // 4. Return false. + else { Ok(false) } }