From a0a71da0255dbaf55e94bb9e6c467ddc439505a1 Mon Sep 17 00:00:00 2001 From: Szilagyi Adam Date: Wed, 20 Nov 2019 11:51:02 +0100 Subject: [PATCH] Implement toPrimitive method and Date.prototype.toPrimitve (#3287) The algorithms are based on ECMA-262 v6 7.1.1 and 20.3.4.45 JerryScript-DCO-1.0-Signed-off-by: Adam Szilagyi aszilagy@inf.u-szeged.hu --- .../ecma-builtin-date-prototype.c | 53 +++++++++ .../ecma-builtin-date-prototype.inc.h | 3 + jerry-core/ecma/operations/ecma-conversion.h | 2 +- .../ecma/operations/ecma-objects-general.c | 110 +++++++++++++++--- .../ecma/operations/ecma-objects-general.h | 1 + jerry-core/lit/lit-magic-strings.inc.h | 3 +- .../es2015/date-prototype-toprimitive.js | 51 ++++++++ .../jerry/es2015/symbol-prototype-tostring.js | 8 +- .../es2015/symbol-prototype.toprimitive.js | 30 +++++ 9 files changed, 243 insertions(+), 18 deletions(-) create mode 100644 tests/jerry/es2015/date-prototype-toprimitive.js create mode 100644 tests/jerry/es2015/symbol-prototype.toprimitive.js diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-date-prototype.c b/jerry-core/ecma/builtin-objects/ecma-builtin-date-prototype.c index 95b83a0c7..2a43a7309 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-date-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-date-prototype.c @@ -22,6 +22,7 @@ #include "ecma-globals.h" #include "ecma-helpers.h" #include "ecma-objects.h" +#include "ecma-objects-general.h" #include "ecma-try-catch-macro.h" #if ENABLED (JERRY_BUILTIN_DATE) @@ -98,6 +99,10 @@ enum ECMA_DATE_PROTOTYPE_GET_TIME, /* ECMA-262 v5, 15.9.5.9 */ ECMA_DATE_PROTOTYPE_SET_TIME, /* ECMA-262 v5, 15.9.5.27 */ ECMA_DATE_PROTOTYPE_TO_JSON, /* ECMA-262 v5, 15.9.5.44 */ + +#if ENABLED (JERRY_ES2015) + ECMA_DATE_PROTOTYPE_TO_PRIMITIVE, /* ECMA-262 v6 20.3.4.45 */ +#endif /* ENABLED (JERRY_ES2015) */ }; #define BUILTIN_INC_HEADER_NAME "ecma-builtin-date-prototype.inc.h" @@ -179,6 +184,46 @@ ecma_builtin_date_prototype_to_json (ecma_value_t this_arg) /**< this argument * return ret_value; } /* ecma_builtin_date_prototype_to_json */ +#if ENABLED (JERRY_ES2015) +/** + * The Date.prototype object's toPrimitive routine + * + * See also: + * ECMA-262 v6, 20.3.4.45 + * + * @return ecma value + * Returned value must be freed with ecma_free_value. + */ +static ecma_value_t +ecma_builtin_date_prototype_to_primitive (ecma_value_t this_arg, /**< this argument */ + ecma_value_t hint_arg) /**< {"default", "number", "string"} */ +{ + if (ecma_is_value_object (this_arg) && ecma_is_value_string (hint_arg)) + { + ecma_string_t *hint_str_p = ecma_get_string_from_value (hint_arg); + + ecma_preferred_type_hint_t hint = ECMA_PREFERRED_TYPE_NUMBER; + + if (hint_str_p == ecma_get_magic_string (LIT_MAGIC_STRING_STRING) + || hint_str_p == ecma_get_magic_string (LIT_MAGIC_STRING_DEFAULT)) + { + hint = ECMA_PREFERRED_TYPE_STRING; + } + else if (hint_str_p == ecma_get_magic_string (LIT_MAGIC_STRING_NUMBER)) + { + hint = ECMA_PREFERRED_TYPE_NUMBER; + } + + if (hint != ECMA_PREFERRED_TYPE_NO) + { + return ecma_op_general_object_ordinary_value (ecma_get_object_from_value (this_arg), hint); + } + } + + return ecma_raise_type_error (ECMA_ERR_MSG ("Invalid argument type in toPrimitive.")); +} /* ecma_builtin_date_prototype_to_primitive */ +#endif /* ENABLED (JERRY_ES2015) */ + /** * Dispatch get date functions * @@ -560,6 +605,14 @@ ecma_builtin_date_prototype_dispatch_routine (uint16_t builtin_routine_id, /**< return ecma_builtin_date_prototype_to_json (this_arg); } +#if ENABLED (JERRY_ES2015) + if (JERRY_UNLIKELY (builtin_routine_id == ECMA_DATE_PROTOTYPE_TO_PRIMITIVE)) + { + ecma_value_t argument = arguments_number > 0 ? arguments_list[0] : ECMA_VALUE_UNDEFINED; + return ecma_builtin_date_prototype_to_primitive (this_arg, argument); + } +#endif /* ENABLED (JERRY_ES2015) */ + if (!ecma_is_value_object (this_arg) || !ecma_object_class_is (ecma_get_object_from_value (this_arg), LIT_MAGIC_STRING_DATE_UL)) { diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-date-prototype.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-date-prototype.inc.h index 543080149..765fe4b63 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-date-prototype.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-date-prototype.inc.h @@ -68,6 +68,9 @@ ROUTINE (LIT_MAGIC_STRING_SET_UTC_FULL_YEAR_UL, ECMA_DATE_PROTOTYPE_SET_UTC_FULL ROUTINE (LIT_MAGIC_STRING_TO_UTC_STRING_UL, ECMA_DATE_PROTOTYPE_TO_UTC_STRING, 0, 0) ROUTINE (LIT_MAGIC_STRING_TO_ISO_STRING_UL, ECMA_DATE_PROTOTYPE_TO_ISO_STRING, 0, 0) ROUTINE (LIT_MAGIC_STRING_TO_JSON_UL, ECMA_DATE_PROTOTYPE_TO_JSON, 1, 1) +#if ENABLED (JERRY_ES2015) +ROUTINE_CONFIGURABLE_ONLY (LIT_GLOBAL_SYMBOL_TO_PRIMITIVE, ECMA_DATE_PROTOTYPE_TO_PRIMITIVE, 1, 1) +#endif /* ENABLED (JERRY_ES2015) */ #if ENABLED (JERRY_BUILTIN_ANNEXB) diff --git a/jerry-core/ecma/operations/ecma-conversion.h b/jerry-core/ecma/operations/ecma-conversion.h index 6967718c9..3ee22ca1d 100644 --- a/jerry-core/ecma/operations/ecma-conversion.h +++ b/jerry-core/ecma/operations/ecma-conversion.h @@ -32,7 +32,7 @@ */ typedef enum { - ECMA_PREFERRED_TYPE_NO, /**< no preferred type is specified */ + ECMA_PREFERRED_TYPE_NO = 0, /**< no preferred type is specified */ ECMA_PREFERRED_TYPE_NUMBER, /**< Number */ ECMA_PREFERRED_TYPE_STRING /**< String */ } ecma_preferred_type_hint_t; diff --git a/jerry-core/ecma/operations/ecma-objects-general.c b/jerry-core/ecma/operations/ecma-objects-general.c index b1ea9aa40..cd442c450 100644 --- a/jerry-core/ecma/operations/ecma-objects-general.c +++ b/jerry-core/ecma/operations/ecma-objects-general.c @@ -184,6 +184,36 @@ ecma_op_general_object_delete (ecma_object_t *obj_p, /**< the object */ return ECMA_VALUE_FALSE; } /* ecma_op_general_object_delete */ +/** + * Property invocation order during [[DefaultValue]] operation with string hint + */ +static const lit_magic_string_id_t to_primitive_string_hint_method_names[2] = +{ + LIT_MAGIC_STRING_TO_STRING_UL, /**< toString operation */ + LIT_MAGIC_STRING_VALUE_OF_UL, /**< valueOf operation */ +}; + +/** + * Property invocation order during [[DefaultValue]] operation with non string hint + */ +static const lit_magic_string_id_t to_primitive_non_string_hint_method_names[2] = +{ + LIT_MAGIC_STRING_VALUE_OF_UL, /**< valueOf operation */ + LIT_MAGIC_STRING_TO_STRING_UL, /**< toString operation */ +}; + +#if ENABLED (JERRY_ES2015) +/** + * Hints for the ecma general object's toPrimitve operation + */ +static const lit_magic_string_id_t hints[3] = +{ + LIT_MAGIC_STRING_DEFAULT, /**< "default" hint */ + LIT_MAGIC_STRING_NUMBER, /**< "number" hint */ + LIT_MAGIC_STRING_STRING, /**< "string" hint */ +}; +#endif /* ENABLED (JERRY_ES2015) */ + /** * [[DefaultValue]] ecma general object's operation * @@ -201,6 +231,47 @@ ecma_op_general_object_default_value (ecma_object_t *obj_p, /**< the object */ JERRY_ASSERT (obj_p != NULL && !ecma_is_lexical_environment (obj_p)); +#if ENABLED (JERRY_ES2015) + ecma_value_t obj_value = ecma_make_object_value (obj_p); + + ecma_value_t exotic_to_prim = ecma_op_get_method_by_symbol_id (obj_value, + LIT_MAGIC_STRING_TO_PRIMITIVE); + + if (ECMA_IS_VALUE_ERROR (exotic_to_prim)) + { + return exotic_to_prim; + } + + if (!ecma_is_value_undefined (exotic_to_prim)) + { + ecma_object_t *call_func_p = ecma_get_object_from_value (exotic_to_prim); + ecma_value_t argument = ecma_make_magic_string_value (hints[hint]); + + ecma_value_t result = ecma_op_function_call (call_func_p, + obj_value, + &argument, + 1); + + ecma_free_value (exotic_to_prim); + + if (ECMA_IS_VALUE_ERROR (result) + || !ecma_is_value_object (result)) + { + return result; + } + + ecma_free_value (result); + + return ecma_raise_type_error (ECMA_ERR_MSG ("Invalid argument type in [[DefaultValue]].")); + } + + ecma_free_value (exotic_to_prim); + + if (hint == ECMA_PREFERRED_TYPE_NO) + { + hint = ECMA_PREFERRED_TYPE_NUMBER; + } +#else /* !ENABLED (JERRY_ES2015) */ if (hint == ECMA_PREFERRED_TYPE_NO) { if (ecma_object_class_is (obj_p, LIT_MAGIC_STRING_DATE_UL)) @@ -212,22 +283,31 @@ ecma_op_general_object_default_value (ecma_object_t *obj_p, /**< the object */ hint = ECMA_PREFERRED_TYPE_NUMBER; } } +#endif /* ENABLED (JERRY_ES2015) */ - for (uint32_t i = 1; i <= 2; i++) + return ecma_op_general_object_ordinary_value (obj_p, hint); +} /* ecma_op_general_object_default_value */ + +/** + * Ecma general object's OrdinaryToPrimitive operation + * + * See also: + * ECMA-262 v6 7.1.1 + * + * @return ecma value + * Returned value must be freed with ecma_free_value + */ +ecma_value_t +ecma_op_general_object_ordinary_value (ecma_object_t *obj_p, /**< the object */ + ecma_preferred_type_hint_t hint) /**< hint on preferred result type */ +{ + const lit_magic_string_id_t *function_name_ids_p = (hint == ECMA_PREFERRED_TYPE_STRING + ? to_primitive_string_hint_method_names + : to_primitive_non_string_hint_method_names); + + for (uint32_t i = 0; i < 2; i++) { - lit_magic_string_id_t function_name_id; - - if ((i == 1 && hint == ECMA_PREFERRED_TYPE_STRING) - || (i == 2 && hint == ECMA_PREFERRED_TYPE_NUMBER)) - { - function_name_id = LIT_MAGIC_STRING_TO_STRING_UL; - } - else - { - function_name_id = LIT_MAGIC_STRING_VALUE_OF_UL; - } - - ecma_value_t function_value = ecma_op_object_get_by_magic_id (obj_p, function_name_id); + ecma_value_t function_value = ecma_op_object_get_by_magic_id (obj_p, function_name_ids_p[i]); if (ECMA_IS_VALUE_ERROR (function_value)) { @@ -259,7 +339,7 @@ ecma_op_general_object_default_value (ecma_object_t *obj_p, /**< the object */ } return ecma_raise_type_error (ECMA_ERR_MSG ("Invalid argument type in [[DefaultValue]].")); -} /* ecma_op_general_object_default_value */ +} /* ecma_op_general_object_ordinary_value */ /** * Special type for ecma_op_general_object_define_own_property. diff --git a/jerry-core/ecma/operations/ecma-objects-general.h b/jerry-core/ecma/operations/ecma-objects-general.h index 74c1f3a4e..35a0e02d6 100644 --- a/jerry-core/ecma/operations/ecma-objects-general.h +++ b/jerry-core/ecma/operations/ecma-objects-general.h @@ -33,6 +33,7 @@ ecma_object_t *ecma_op_create_object_object_noarg_and_set_prototype (ecma_object ecma_value_t ecma_op_general_object_delete (ecma_object_t *obj_p, ecma_string_t *property_name_p, bool is_throw); ecma_value_t ecma_op_general_object_default_value (ecma_object_t *obj_p, ecma_preferred_type_hint_t hint); +ecma_value_t ecma_op_general_object_ordinary_value (ecma_object_t *obj_p, ecma_preferred_type_hint_t hint); ecma_value_t ecma_op_general_object_define_own_property (ecma_object_t *object_p, ecma_string_t *property_name_p, const ecma_property_descriptor_t *property_desc_p); diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index eeefd538d..8196cd63d 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -380,7 +380,8 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_BOOLEAN, "boolean") #if ENABLED (JERRY_BUILTIN_ANNEXB) && ENABLED (JERRY_BUILTIN_REGEXP) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_COMPILE, "compile") #endif -#if ENABLED (JERRY_ES2015_MODULE_SYSTEM) +#if ENABLED (JERRY_ES2015) \ +|| ENABLED (JERRY_ES2015_MODULE_SYSTEM) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_DEFAULT, "default") #endif #if ENABLED (JERRY_BUILTIN_ARRAY) && ENABLED (JERRY_ES2015_BUILTIN_ITERATOR) \ diff --git a/tests/jerry/es2015/date-prototype-toprimitive.js b/tests/jerry/es2015/date-prototype-toprimitive.js new file mode 100644 index 000000000..7ee4509f9 --- /dev/null +++ b/tests/jerry/es2015/date-prototype-toprimitive.js @@ -0,0 +1,51 @@ +/* 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 dateObj = new Date("1997-04-10"); +var dateNaN = new Date(NaN); + +// Test with default hint +var result = dateObj[Symbol.toPrimitive]("default"); +assert(result.toString().substring(0,15) === "Thu Apr 10 1997"); +result = dateNaN[Symbol.toPrimitive]("default"); +assert(dateNaN == "Invalid Date"); + +// Test with number hint +result = dateObj[Symbol.toPrimitive]("number"); +assert(result.toString() === "860630400000"); +result = dateNaN[Symbol.toPrimitive]("number"); +assert(isNaN(result) === true); + +// Test with string hint +result = dateObj[Symbol.toPrimitive]("string"); +assert(result.toString().substring(0,15) === "Thu Apr 10 1997"); +result = dateNaN[Symbol.toPrimitive]("string"); +assert(result == "Invalid Date"); + +// Test with invalid hint +try { + result = dateObj[Symbol.toPrimitive](90); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +// Test when unable to call toPrimitive +try { + Date.prototype[Symbol.toPrimitive].call(undefined); + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} diff --git a/tests/jerry/es2015/symbol-prototype-tostring.js b/tests/jerry/es2015/symbol-prototype-tostring.js index 7c16c07ad..43a6c5c1b 100644 --- a/tests/jerry/es2015/symbol-prototype-tostring.js +++ b/tests/jerry/es2015/symbol-prototype-tostring.js @@ -33,4 +33,10 @@ assert (String (foo) === "Symbol(foo)"); var fooObj = Object (foo); assert (fooObj.toString () === "Symbol(foo)"); -assert (String (fooObj) === "Symbol(foo)"); + +try { + String (fooObj); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} diff --git a/tests/jerry/es2015/symbol-prototype.toprimitive.js b/tests/jerry/es2015/symbol-prototype.toprimitive.js new file mode 100644 index 000000000..31fbe525d --- /dev/null +++ b/tests/jerry/es2015/symbol-prototype.toprimitive.js @@ -0,0 +1,30 @@ +/* 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 obj2 = { + [Symbol.toPrimitive](hint) { + if (hint == 'number') { + return 10; + } + if (hint == 'string') { + return 'hello'; + } + return true; + } + }; + + assert(+obj2 === 10); + //assert(`${obj2}` === "hello"); //FIXME: template literals requires String hint during concatenation + assert(obj2 + '' === "true");