diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-object.c b/jerry-core/ecma/builtin-objects/ecma-builtin-object.c index 87c027977..3729df886 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object.c @@ -922,6 +922,126 @@ ecma_builtin_object_object_define_property (ecma_value_t this_arg, /**< 'this' a return ret_value; } /* ecma_builtin_object_object_define_property */ +#ifndef CONFIG_DISABLE_ES2015_BUILTIN +/** + * The Object object's 'assign' routine + * + * See also: + * ECMA-262 v6, 19.1.2.1 + * + * @return ecma value + * Returned value must be freed with ecma_free_value. + */ +static ecma_value_t +ecma_builtin_object_object_assign (ecma_value_t this_arg, /**< 'this' argument */ + const ecma_value_t arguments_list_p[], /**< arguments list */ + ecma_length_t arguments_list_len) /**< number of arguments */ +{ + JERRY_UNUSED (this_arg); + + ecma_value_t target = arguments_list_len > 0 ? arguments_list_p[0] : ECMA_VALUE_UNDEFINED; + + /* 1. */ + ecma_value_t to_value = ecma_op_to_object (target); + + if (ECMA_IS_VALUE_ERROR (to_value)) + { + return to_value; + } + + ecma_object_t *to_obj_p = ecma_get_object_from_value (to_value); + + /* 2. */ + if (arguments_list_len == 1) + { + return to_value; + } + + ecma_value_t ret_value = ECMA_VALUE_EMPTY; + + /* 4-5. */ + for (uint32_t i = 1; i < arguments_list_len && ecma_is_value_empty (ret_value); i++) + { + ecma_value_t next_source = arguments_list_p[i]; + + /* 5.a */ + if (ecma_is_value_undefined (next_source) || ecma_is_value_null (next_source)) + { + continue; + } + + /* 5.b.i */ + ecma_value_t from_value = ecma_op_to_object (next_source); + /* null and undefied cases are handled above, so this must be a valid object */ + JERRY_ASSERT (!ECMA_IS_VALUE_ERROR (from_value)); + + ecma_object_t *from_obj_p = ecma_get_object_from_value (from_value); + + /* 5.b.iii */ + /* TODO: extends this collection if symbols will be supported */ + ecma_collection_header_t *props_p = ecma_op_object_get_property_names (from_obj_p, ECMA_LIST_ENUMERABLE); + + ecma_value_t *ecma_value_p = ecma_collection_iterator_init (props_p); + + while (ecma_value_p != NULL && ecma_is_value_empty (ret_value)) + { + ecma_string_t *property_name_p = ecma_get_string_from_value (*ecma_value_p); + + /* 5.c.i-ii */ + ecma_property_descriptor_t prop_desc; + + if (!ecma_op_object_get_own_property_descriptor (from_obj_p, property_name_p, &prop_desc)) + { + continue; + } + + /* 5.c.iii */ + if (prop_desc.is_enumerable + && ((prop_desc.is_value_defined && !ecma_is_value_undefined (prop_desc.value)) + || prop_desc.is_get_defined)) + { + /* 5.c.iii.1 */ + ecma_value_t prop_value = ecma_op_object_get (from_obj_p, property_name_p); + + /* 5.c.iii.2 */ + if (ECMA_IS_VALUE_ERROR (prop_value)) + { + ret_value = prop_value; + } + else + { + /* 5.c.iii.3 */ + ecma_value_t status = ecma_op_object_put (to_obj_p, property_name_p, prop_value, true); + + /* 5.c.iii.4 */ + if (ECMA_IS_VALUE_ERROR (status)) + { + ret_value = status; + } + } + + ecma_free_value (prop_value); + ecma_free_property_descriptor (&prop_desc); + } + + ecma_value_p = ecma_collection_iterator_next (ecma_value_p); + } + + ecma_deref_object (from_obj_p); + ecma_free_values_collection (props_p, 0); + } + + /* 6. */ + if (ecma_is_value_empty (ret_value)) + { + return to_value; + } + + ecma_deref_object (to_obj_p); + return ret_value; +} /* ecma_builtin_object_object_assign */ +#endif /* !CONFIG_DISABLE_ES2015_BUILTIN */ + /** * @} * @} 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 8de27d420..03cd56280 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object.inc.h @@ -53,6 +53,7 @@ ROUTINE (LIT_MAGIC_STRING_DEFINE_PROPERTY_UL, ecma_builtin_object_object_define_ #ifndef CONFIG_DISABLE_ES2015_BUILTIN ROUTINE (LIT_MAGIC_STRING_SET_PROTOTYPE_OF_UL, ecma_builtin_object_object_set_prototype_of, 2, 2) +ROUTINE (LIT_MAGIC_STRING_ASSIGN, ecma_builtin_object_object_assign, NON_FIXED, 2) #endif /* !CONFIG_DISABLE_ES2015_BUILTIN */ #include "ecma-builtin-helpers-macro-undefs.inc.h" diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index 59e325311..845bddd1f 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -216,6 +216,9 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_NUMBER_UL, "Number") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_OBJECT_UL, "Object") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_REGEXP_UL, "RegExp") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_STRING_UL, "String") +#if !defined (CONFIG_DISABLE_ES2015_BUILTIN) +LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_ASSIGN, "assign") +#endif #if !defined (CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_BUFFER, "buffer") #endif diff --git a/jerry-core/lit/lit-magic-strings.ini b/jerry-core/lit/lit-magic-strings.ini index 7e9e34bc9..f1e458737 100644 --- a/jerry-core/lit/lit-magic-strings.ini +++ b/jerry-core/lit/lit-magic-strings.ini @@ -110,6 +110,7 @@ LIT_MAGIC_STRING_NUMBER_UL = "Number" LIT_MAGIC_STRING_OBJECT_UL = "Object" LIT_MAGIC_STRING_REGEXP_UL = "RegExp" LIT_MAGIC_STRING_STRING_UL = "String" +LIT_MAGIC_STRING_ASSIGN = "assign" LIT_MAGIC_STRING_BUFFER = "buffer" LIT_MAGIC_STRING_CALLEE = "callee" LIT_MAGIC_STRING_CALLER = "caller" diff --git a/tests/jerry/es2015/object-assign.js b/tests/jerry/es2015/object-assign.js new file mode 100644 index 000000000..27c7406f3 --- /dev/null +++ b/tests/jerry/es2015/object-assign.js @@ -0,0 +1,158 @@ +// 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. + +var object1 = { + a: 1, + b: 2, + c: 3 +}; + +var object2 = Object.assign ({c: 4, d: 5}, object1); + +assert (JSON.stringify (object2) === '{"c":3,"d":5,"a":1,"b":2}'); +assert (object2.c === 3); +assert (object2.d === 5); + +// Cloning an object +var obj = { a: 1 }; +var copy = Object.assign ({}, obj); +assert (JSON.stringify (copy) === '{"a":1}'); // { a: 1 } + +// Warning for Deep Clone +function deepClone() { + 'use strict'; + + var obj1 = { a: 0 , b: { c: 0}}; + var obj2 = Object.assign ({}, obj1); + assert (JSON.stringify (obj2) === '{"a":0,"b":{"c":0}}'); + + obj1.a = 1; + assert (JSON.stringify (obj1) === '{"a":1,"b":{"c":0}}'); + assert (JSON.stringify (obj2) === '{"a":0,"b":{"c":0}}'); + + obj2.a = 2; + assert (JSON.stringify (obj1) === '{"a":1,"b":{"c":0}}'); + assert (JSON.stringify (obj2) === '{"a":2,"b":{"c":0}}'); + + obj2.b.c = 3; + assert (JSON.stringify (obj1) === '{"a":1,"b":{"c":3}}'); + assert (JSON.stringify (obj2) === '{"a":2,"b":{"c":3}}'); + + // Deep Clone + obj1 = { a: 0 , b: { c: 0}}; + var obj3 = JSON.parse (JSON.stringify (obj1)); + obj1.a = 4; + obj1.b.c = 4; + assert (JSON.stringify (obj3) === '{"a":0,"b":{"c":0}}'); +} + +deepClone(); + +// Merging objects +var o1 = { a: 1 }; +var o2 = { b: 2 }; +var o3 = { c: 3 }; + +var obj = Object.assign (o1, o2, o3); +assert (JSON.stringify (obj) === '{"a":1,"b":2,"c":3}'); +assert (JSON.stringify (o1) === '{"a":1,"b":2,"c":3}'); //target object itself is changed. + +//Merging objects with same properties +var o1 = { a: 1, b: 1, c: 1 }; +var o2 = { b: 2, c: 2 }; +var o3 = { c: 3 }; + +var obj = Object.assign ({}, o1, o2, o3); +assert (JSON.stringify (obj) === '{"a":1,"b":2,"c":3}'); + +// Properties on the prototype chain and non-enumerable properties cannot be copied +var obj = Object.create({ foo: 1 }, { // foo is on obj's prototype chain. + bar: { + value: 2 // bar is a non-enumerable property. + }, + baz: { + value: 3, + enumerable: true // baz is an own enumerable property. + } +}); + +var copy = Object.assign ({}, obj); +assert (JSON.stringify (copy) === '{"baz":3}'); + +// Primitives will be wrapped to objects +var v1 = 'abc'; +var v2 = true; +var v3 = 10; + +var obj = Object.assign ({}, v1, null, v2, undefined, v3); +// Primitives will be wrapped, null and undefined will be ignored. +// Note, only string wrappers can have own enumerable properties. +assert (JSON.stringify (obj) === '{"0":"a","1":"b","2":"c"}'); + +//Exceptions will interrupt the ongoing copying task +var target = Object.defineProperty ({}, 'foo', { + value: 1, + writable: false +}); // target.foo is a read-only property + +try { + // TypeError: "foo" is read-only,the Exception is thrown when assigning target.foo + Object.assign (target, { bar: 2 }, { foo2: 3, foo: 3, foo3: 3 }, { baz: 4 }); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +assert (target.bar === 2); // the first source was copied successfully. +assert (target.foo2 === 3); // the first property of the second source was copied successfully. +assert (target.foo === 1); // exception is thrown here. +assert (target.foo3 === undefined); // assign method has finished, foo3 will not be copied. +assert (target.baz === undefined); // the third source will not be copied either. + +// Copying accessors +var obj = { + foo: 1, + get bar() { + return 2; + } +}; + +var copy = Object.assign ({}, obj); +assert (JSON.stringify (copy) === '{"foo":1,"bar":2}'); +assert (copy.bar === 2); // the value of copy.bar is obj.bar's getter's return value. + +// This is an assign function that copies full descriptors +function completeAssign (target, sources) { + sources.forEach (source => { + var descriptors = Object.keys (source).reduce ((descriptors, key) => { + descriptors[key] = Object.getOwnPropertyDescriptor (source, key); + return descriptors; + }, {}); + + Object.defineProperties (target, descriptors); + }); + return target; +} + +var copy = completeAssign ({}, [obj]); +assert (JSON.stringify (copy) === '{"foo":1}'); +assert (copy.bar === 2); + +// Test when target is not coercible to object +try { + Object.assign.call (undefined); + assert (false); +} catch (e) { + assert (e instanceof TypeError) +}