From 43a82cddb9a2f0948c526f883c1dc80a1852f6f8 Mon Sep 17 00:00:00 2001 From: kisbg Date: Fri, 7 Aug 2020 15:08:12 +0200 Subject: [PATCH] Implement Object.prototype.fromEntries (#4065) JerryScript-DCO-1.0-Signed-off-by: bence gabor kis kisbg@inf.u-szeged.hu --- .../builtin-objects/ecma-builtin-object.c | 145 ++++++++++ .../builtin-objects/ecma-builtin-object.inc.h | 1 + jerry-core/lit/lit-magic-strings.inc.h | 1 + jerry-core/lit/lit-magic-strings.ini | 1 + tests/jerry/es.next/object-fromEntries.js | 267 ++++++++++++++++++ tests/test262-esnext-excludelist.xml | 24 -- 6 files changed, 415 insertions(+), 24 deletions(-) create mode 100644 tests/jerry/es.next/object-fromEntries.js diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-object.c b/jerry-core/ecma/builtin-objects/ecma-builtin-object.c index 7b4138011..fa7b026e8 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object.c @@ -27,6 +27,10 @@ #include "ecma-objects-general.h" #include "jrt.h" #include "ecma-builtin-object.h" +#if ENABLED (JERRY_ESNEXT) +#include "ecma-iterator-object.h" +#include "ecma-function-object.h" +#endif /* ENABLED (JERRY_ESNEXT) */ #define ECMA_BUILTINS_INTERNAL #include "ecma-builtins-internal.h" @@ -56,6 +60,7 @@ enum ECMA_OBJECT_ROUTINE_GET_OWN_PROPERTY_DESCRIPTOR, ECMA_OBJECT_ROUTINE_GET_OWN_PROPERTY_DESCRIPTORS, ECMA_OBJECT_ROUTINE_GET_PROTOTYPE_OF, + ECMA_OBJECT_ROUTINE_FROM_ENTRIES, ECMA_OBJECT_ROUTINE_KEYS, ECMA_OBJECT_ROUTINE_VALUES, ECMA_OBJECT_ROUTINE_ENTRIES, @@ -1151,6 +1156,141 @@ ecma_builtin_object_object_is (ecma_value_t arg1, /**< routine's first argument return ecma_op_same_value (arg1, arg2) ? ECMA_VALUE_TRUE : ECMA_VALUE_FALSE; } /* ecma_builtin_object_object_is */ +/** + * The Object object's 'fromEntries' routine + * + * See also: + * ECMA-262 v10, 19.1.2.7 + * @return ecma value + * Returned value must be freed with ecma_free_value. + */ +static ecma_value_t +ecma_builtin_object_from_entries (ecma_value_t iterator) /**< object's iterator */ +{ + JERRY_ASSERT (ecma_op_check_object_coercible (iterator)); + /* 2 */ + ecma_object_t *object_prototype_p = ecma_builtin_get (ECMA_BUILTIN_ID_OBJECT_PROTOTYPE); + ecma_object_t *obj_p = ecma_create_object (object_prototype_p, 0, ECMA_OBJECT_TYPE_GENERAL); + + /* 6.a */ + ecma_value_t next_method; + ecma_value_t result = ecma_op_get_iterator (iterator, ECMA_VALUE_SYNC_ITERATOR, &next_method); + + if (ECMA_IS_VALUE_ERROR (result)) + { + ecma_deref_object (obj_p); + return result; + } + + const ecma_value_t original_iterator = result; + + /* 6.b */ + while (true) + { + /* 6.a.i */ + result = ecma_op_iterator_step (original_iterator, next_method); + + if (ECMA_IS_VALUE_ERROR (result)) + { + goto cleanup_iterator; + } + + /* 6.a.ii */ + if (ecma_is_value_false (result)) + { + break; + } + + /* 6.a.iii */ + const ecma_value_t next = result; + result = ecma_op_iterator_value (next); + ecma_free_value (next); + + if (ECMA_IS_VALUE_ERROR (result)) + { + goto cleanup_iterator; + } + + /* 6.a.iv */ + if (!ecma_is_value_object (result)) + { + ecma_free_value (result); + ecma_raise_type_error (ECMA_ERR_MSG ("Iterator value is not an object.")); + result = ecma_op_iterator_close (original_iterator); + JERRY_ASSERT (ECMA_IS_VALUE_ERROR (result)); + goto cleanup_iterator; + } + + /* 6.a.v-vi */ + ecma_object_t *next_object_p = ecma_get_object_from_value (result); + + result = ecma_op_object_get_by_index (next_object_p, 0); + + if (ECMA_IS_VALUE_ERROR (result)) + { + ecma_deref_object (next_object_p); + ecma_op_iterator_close (original_iterator); + goto cleanup_iterator; + } + + const ecma_value_t key = result; + + result = ecma_op_object_get_by_index (next_object_p, 1); + + if (ECMA_IS_VALUE_ERROR (result)) + { + ecma_deref_object (next_object_p); + ecma_free_value (key); + ecma_op_iterator_close (original_iterator); + goto cleanup_iterator; + } + + /* 6.a.vii */ + const ecma_value_t value = result; + ecma_string_t *property_key = ecma_op_to_property_key (key); + + if (property_key == NULL) + { + ecma_deref_object (next_object_p); + ecma_free_value (key); + ecma_op_iterator_close (original_iterator); + result = ECMA_VALUE_ERROR; + goto cleanup_iterator; + } + + ecma_property_value_t *prop; + ecma_property_t *property_p = ecma_find_named_property (obj_p, property_key); + + if (property_p == NULL) + { + prop = ecma_create_named_data_property (obj_p, + property_key, + ECMA_PROPERTY_CONFIGURABLE_ENUMERABLE_WRITABLE, + NULL); + prop->value = ecma_copy_value_if_not_object (value); + } + else + { + ecma_named_data_property_assign_value (obj_p, ECMA_PROPERTY_VALUE_PTR (property_p), value); + } + + ecma_deref_ecma_string (property_key); + ecma_free_value (key); + ecma_free_value (value); + ecma_deref_object (next_object_p); + } + + ecma_ref_object (obj_p); + result = ecma_make_object_value (obj_p); + +cleanup_iterator: + ecma_free_value (original_iterator); + ecma_free_value (next_method); + ecma_deref_object (obj_p); + + return result; +} /* ecma_builtin_object_from_entries */ + #endif /* ENABLED (JERRY_ESNEXT) */ /** @@ -1359,6 +1499,11 @@ ecma_builtin_object_dispatch_routine (uint16_t builtin_routine_id, /**< built-in result = ecma_builtin_object_object_get_own_property_descriptors (obj_p); break; } + case ECMA_OBJECT_ROUTINE_FROM_ENTRIES: + { + result = ecma_builtin_object_from_entries (arg1); + break; + } #endif /* ENABLED (JERRY_ESNEXT) */ default: { diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-object.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-object.inc.h index 735d450b2..63df72fbc 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object.inc.h @@ -61,6 +61,7 @@ ROUTINE (LIT_MAGIC_STRING_KEYS, ECMA_OBJECT_ROUTINE_KEYS, 1, 1) ROUTINE (LIT_MAGIC_STRING_GET_OWN_PROPERTY_DESCRIPTOR_UL, ECMA_OBJECT_ROUTINE_GET_OWN_PROPERTY_DESCRIPTOR, 2, 2) #if ENABLED (JERRY_ESNEXT) ROUTINE (LIT_MAGIC_STRING_GET_OWN_PROPERTY_DESCRIPTORS_UL, ECMA_OBJECT_ROUTINE_GET_OWN_PROPERTY_DESCRIPTORS, 1, 1) +ROUTINE (LIT_MAGIC_STRING_OBJECT_FROM_ENTRIES, ECMA_OBJECT_ROUTINE_FROM_ENTRIES, 1, 1) #endif /* ENABLED (JERRY_ESNEXT) */ ROUTINE (LIT_MAGIC_STRING_CREATE, ECMA_OBJECT_ROUTINE_CREATE, 2, 2) ROUTINE (LIT_MAGIC_STRING_DEFINE_PROPERTIES_UL, ECMA_OBJECT_ROUTINE_DEFINE_PROPERTIES, 2, 2) diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index 0f2a66bc2..12b35397d 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -740,6 +740,7 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_CODE_POINT_AT, "codePointAt") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_CONSTRUCTOR, "constructor") #if ENABLED (JERRY_ESNEXT) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_DESCRIPTION, "description") +LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_OBJECT_FROM_ENTRIES, "fromEntries") #endif #if ENABLED (JERRY_BUILTIN_DATE) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_GET_FULL_YEAR_UL, "getFullYear") diff --git a/jerry-core/lit/lit-magic-strings.ini b/jerry-core/lit/lit-magic-strings.ini index d3247d76c..8c3ccb635 100644 --- a/jerry-core/lit/lit-magic-strings.ini +++ b/jerry-core/lit/lit-magic-strings.ini @@ -297,6 +297,7 @@ LIT_MAGIC_STRING_UINT16_ARRAY_UL = "Uint16Array" LIT_MAGIC_STRING_UINT32_ARRAY_UL = "Uint32Array" LIT_MAGIC_STRING_CONSTRUCTOR = "constructor" LIT_MAGIC_STRING_CODE_POINT_AT = "codePointAt" +LIT_MAGIC_STRING_OBJECT_FROM_ENTRIES = "fromEntries" LIT_MAGIC_STRING_GET_FULL_YEAR_UL = "getFullYear" LIT_MAGIC_STRING_GET_UTC_HOURS_UL = "getUTCHours" LIT_MAGIC_STRING_GET_UTC_MONTH_UL = "getUTCMonth" diff --git a/tests/jerry/es.next/object-fromEntries.js b/tests/jerry/es.next/object-fromEntries.js new file mode 100644 index 000000000..48972e0d2 --- /dev/null +++ b/tests/jerry/es.next/object-fromEntries.js @@ -0,0 +1,267 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// helper function - simple implementation +Array.prototype.equals = function (array) { + if (this.length != array.length) + return false; + + for (var i = 0; i < this.length; i++) { + if (this[i] instanceof Array && array[i] instanceof Array) { + if (!this[i].equals (array[i])) + return false; + } + else if (this[i] != array[i]) { + return false; + } + } + + return true; +} + +// converting map to object +const map = new Map ([ ['foo', 'bar'], ['baz', 42] ]); +const obj_map = Object.fromEntries (map); +assert (Object.values (obj_map).equals (["bar", 42])); +assert (Object.keys (obj_map).equals (["foo", 'baz'])); + +// converting array to object +const arr = [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]; +const obj_arr = Object.fromEntries (arr); +assert (Object.values (obj_arr).equals (["a", "b", "c"])); +assert (Object.keys (obj_arr).equals (["0", '1', '2'])); + +// transfrom object to other object +const object1 = { a: 1, b: 2, c: 3 }; +const object2 = Object.fromEntries ( + Object.entries (object1) + .map (([ key, val ]) => [ key, val * 2 ]) +); +assert (Object.keys (object2).equals (["a", "b", "c"])); +assert (Object.values (object2).equals ([2, 4, 6])); + +// map with undefined or null member +const map2 = new Map ([ ['foo', undefined], ['baz', null] ]); +const obj_map2 = Object.fromEntries (map2); +assert (Object.values (obj_map2).equals ([undefined, null])); +assert (Object.keys(obj_map2).equals (["foo", 'baz'])) + +// don't have a value +const map3 = new Map ([ ['foo'], ['baz'] ]); +const obj_map3 = Object.fromEntries (map3); +assert (Object.values(obj_map3).equals ([undefined, undefined])); +assert (Object.keys(obj_map3).equals (["foo", 'baz'])); + +// empty map +const map4 = new Map ([]); +const obj_map4 = Object.fromEntries (map4); +assert (Object.values(obj_map4).equals ([])); +assert (Object.keys(obj_map4).equals ([])); + +// few invalid argument +function check_iterator (iterator) { + try { + Object.fromEntries (iterator); + assert (false); + } catch (e) { + assert (e instanceof TypeError); + } +} + +check_iterator (null); +check_iterator (undefined); +check_iterator (5); +check_iterator () + +// closed iterator +var returned = false; +var closed_iterable = { + [Symbol.iterator]: function () { + var advanced = false; + return { + next: function () { + if (advanced) { + throw 42 // meaning of life; + } + advanced = true; + return { + done: false, + value: 'ab', + }; + }, + return: function () { + if (returned) { + throw 42 // meaning of life; + } + returned = true; + }, + }; + }, +}; + +check_iterator (closed_iterable) +assert (returned); + +var next_iterable = { + [Symbol.iterator]: function () { + return { + next: function () { + return null; + }, + return: function () { + throw 42 // meaning of life; + }, + }; + }, +}; + +check_iterator (next_iterable) + +// uncallable next +var next_iterable_2 = { + [Symbol.iterator]: function () { + return { + next: null, + return: function () { + throw 42 // meaning of life; + }, + }; + }, +}; + +check_iterator (next_iterable_2) + +// get '0' error +returned = false; +var iterable_0 = { + [Symbol.iterator]: function () { + var advanced = false; + return { + next: function () { + if (advanced) { + throw 42 // meaning of life; + } + advanced = true; + return { + done: false, + value: { + get '0' () { + throw new TypeError (); + }, + get '1' () { + return "value"; + }, + }, + }; + }, + return: function () { + if (returned) { + throw 42 // meaning of life; + } + returned = true; + }, + }; + }, +}; + +check_iterator (iterable_0) +assert (returned); + +// error in toPropertyKey +returned = false; +var iterable = { + [Symbol.iterator]: function () { + var advanced = false; + return { + next: function () { + if (advanced) { + throw 42 // meaning of life; + } + advanced = true; + return { + done: false, + value: { + 0: { + get toString () { throw new TypeError } + }, + get '1' () { + return "value"; + }, + }, + }; + }, + return: function () { + if (returned) { + throw 42 // meaning of life; + } + returned = true; + }, + }; + }, +}; + +check_iterator (iterable) +assert (returned); + +// get '1' error +returned = false; +var iterable = { + [Symbol.iterator]: function () { + var advanced = false; + return { + next: function () { + if (advanced) { + throw 42 // meaning of life; + } + advanced = true; + return { + done: false, + value: { + get '0' () { + return 'key'; + }, + get '1' () { + throw new TypeError; + }, + }, + }; + }, + return: function () { + if (returned) { + throw 42 // meaning of life; + } + returned = true; + }, + }; + }, +}; + +check_iterator (iterable) +assert (returned); + +// next value is error +var iterable = { + [Symbol.iterator] () { + return { + next () { + return { + get value () { + throw new TypeError } + } + } + } + } + } + +check_iterator (iterable) diff --git a/tests/test262-esnext-excludelist.xml b/tests/test262-esnext-excludelist.xml index 0b15c7565..5b4a6fbca 100644 --- a/tests/test262-esnext-excludelist.xml +++ b/tests/test262-esnext-excludelist.xml @@ -1069,30 +1069,6 @@ - - - - - - - - - - - - - - - - - - - - - - - -