Implement Object.prototype.fromEntries (#4065)

JerryScript-DCO-1.0-Signed-off-by: bence gabor kis kisbg@inf.u-szeged.hu
This commit is contained in:
kisbg 2020-08-07 15:08:12 +02:00 committed by GitHub
parent cebb6aee5c
commit 43a82cddb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 415 additions and 24 deletions

View File

@ -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:
{

View File

@ -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)

View File

@ -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")

View File

@ -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"

View File

@ -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)

View File

@ -1069,30 +1069,6 @@
<test id="built-ins/Number/prototype/toFixed/exactness.js"><reason></reason></test>
<test id="built-ins/Number/prototype/toPrecision/exponential.js"><reason></reason></test>
<test id="built-ins/Object/entries/order-after-define-property.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/empty-iterable.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/evaluation-order.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/iterator-closed-for-null-entry.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/iterator-closed-for-string-entry.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-key-accessor.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-key-tostring.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-value-accessor.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/iterator-not-closed-for-next-returning-non-object.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/iterator-not-closed-for-throwing-done-accessor.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/iterator-not-closed-for-throwing-next.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/iterator-not-closed-for-uncallable-next.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/key-order.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/length.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/name.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/prototype.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/requires-argument.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/simple-properties.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/string-entry-object-succeeds.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/string-entry-primitive-throws.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/string-entry-string-object-succeeds.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/supports-symbols.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/to-property-key.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/uses-define-semantics.js"><reason></reason></test>
<test id="built-ins/Object/fromEntries/uses-keys-not-iterator.js"><reason></reason></test>
<test id="built-ins/Object/keys/order-after-define-property.js"><reason></reason></test>
<test id="built-ins/Object/proto-from-ctor-realm.js"><reason></reason></test>
<test id="built-ins/Object/prototype/toString/proxy-function.js"><reason></reason></test>