From d2931c6e40193f63d143d5abf9a7c64ab8653dd9 Mon Sep 17 00:00:00 2001 From: Robert Fancsik Date: Wed, 17 Apr 2019 14:50:03 +0200 Subject: [PATCH] Rework Map object (#2760) This patch reworks the core of the builtin Map object. Advantages: - Provide sublinear access time for the elements via Lcache and property hashmap - This implementation is suitable for the builtin Set object as well Also add the missing 'forEach' routine for the builtin object as well. JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik frobert@inf.u-szeged.hu --- jerry-core/ecma/base/ecma-gc.c | 51 -- jerry-core/ecma/base/ecma-globals.h | 7 +- jerry-core/ecma/base/ecma-helpers-string.c | 50 ++ jerry-core/ecma/base/ecma-helpers.h | 4 + .../ecma-builtin-map-prototype.c | 18 + .../ecma-builtin-map-prototype.inc.h | 1 + jerry-core/ecma/operations/ecma-conversion.c | 48 ++ jerry-core/ecma/operations/ecma-conversion.h | 3 + jerry-core/ecma/operations/ecma-map-object.c | 601 ++++++++---------- jerry-core/ecma/operations/ecma-map-object.h | 1 + jerry-core/lit/lit-magic-strings.h | 1 + jerry-core/lit/lit-magic-strings.inc.h | 1 + tests/jerry/es2015/map-prototype-foreach.js | 108 ++++ tests/jerry/es2015/map.js | 37 ++ 14 files changed, 525 insertions(+), 406 deletions(-) create mode 100644 tests/jerry/es2015/map-prototype-foreach.js diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index 7da0af05c..ec7e6d344 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -246,50 +246,6 @@ ecma_gc_mark_promise_object (ecma_extended_object_t *ext_object_p) /**< extended #endif /* ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */ -#if ENABLED (JERRY_ES2015_BUILTIN_MAP) - -/** - * Mark objects referenced by Map built-in. - */ -static void -ecma_gc_mark_map_object (ecma_extended_object_t *ext_object_p) /**< extended object */ -{ - ecma_map_object_t *map_object_p = (ecma_map_object_t *) ext_object_p; - - jmem_cpointer_t first_chunk_cp = map_object_p->first_chunk_cp; - - if (JERRY_UNLIKELY (first_chunk_cp == ECMA_NULL_POINTER)) - { - return; - } - - ecma_value_t *item_p = ECMA_GET_NON_NULL_POINTER (ecma_map_object_chunk_t, first_chunk_cp)->items; - - while (true) - { - ecma_value_t item = *item_p++; - - if (!ecma_is_value_pointer (item)) - { - if (ecma_is_value_object (item)) - { - ecma_gc_set_object_visited (ecma_get_object_from_value (item)); - } - } - else - { - item_p = (ecma_value_t *) ecma_get_pointer_from_value (item); - - if (item_p == NULL) - { - return; - } - } - } -} /* ecma_gc_mark_map_object */ - -#endif /* ENABLED (JERRY_ES2015_BUILTIN_MAP) */ - /** * Mark objects as visited starting from specified object as root */ @@ -340,13 +296,6 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */ break; } #endif /* ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */ -#if ENABLED (JERRY_ES2015_BUILTIN_MAP) - case LIT_MAGIC_STRING_MAP_UL: - { - ecma_gc_mark_map_object (ext_object_p); - break; - } -#endif /* ENABLED (JERRY_ES2015_BUILTIN_MAP) */ default: { break; diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 99dac1dc6..28ea8cc31 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -968,8 +968,7 @@ typedef struct typedef struct { ecma_extended_object_t header; /**< header part */ - jmem_cpointer_t first_chunk_cp; /**< first chunk of item list */ - jmem_cpointer_t last_chunk_cp; /**< last chunk of item list */ + uint32_t size; /**< size of the map object */ } ecma_map_object_t; /** @@ -1273,6 +1272,7 @@ typedef enum ECMA_DIRECT_STRING_PTR = 0, /**< string is a string pointer, only used by property names */ ECMA_DIRECT_STRING_MAGIC = 1, /**< string is a magic string */ ECMA_DIRECT_STRING_UINT = 2, /**< string is an unsigned int */ + ECMA_DIRECT_STRING_ECMA_INTEGER = 3, /**< string is an ecma-integer */ } ecma_direct_string_type_t; /** @@ -1351,6 +1351,8 @@ typedef enum ECMA_STRING_CONTAINER_SYMBOL, /**< the ecma-string is a symbol */ + ECMA_STRING_CONTAINER_MAP_KEY, /**< the ecma-string is a map key string */ + ECMA_STRING_LITERAL_NUMBER, /**< a literal number which is used solely by the literal storage * so no string processing function supports this type except * the ecma_deref_ecma_string function. */ @@ -1422,6 +1424,7 @@ typedef struct ecma_value_t lit_number; /**< number (see ECMA_STRING_LITERAL_NUMBER) */ uint32_t common_uint32_field; /**< for zeroing and comparison in some cases */ ecma_value_t symbol_descriptor; /**< symbol descriptor string-value */ + ecma_value_t value; /**< original key value corresponds to the map key string */ } u; } ecma_string_t; diff --git a/jerry-core/ecma/base/ecma-helpers-string.c b/jerry-core/ecma/base/ecma-helpers-string.c index b4eac3758..93c81c783 100644 --- a/jerry-core/ecma/base/ecma-helpers-string.c +++ b/jerry-core/ecma/base/ecma-helpers-string.c @@ -14,6 +14,7 @@ */ #include "ecma-alloc.h" +#include "ecma-conversion.h" #include "ecma-gc.h" #include "ecma-globals.h" #include "ecma-helpers.h" @@ -236,6 +237,41 @@ ecma_prop_name_is_symbol (ecma_string_t *string_p) /**< ecma-string */ } /* ecma_prop_name_is_symbol */ #endif /* ENABLED (JERRY_ES2015_BUILTIN_SYMBOL) */ +#if ENABLED (JERRY_ES2015_BUILTIN_MAP) +/** + * Allocate new ecma-string and fill it with reference to the map key descriptor + * + * @return pointer to ecma-string descriptor + */ +ecma_string_t * +ecma_new_map_key_string (ecma_value_t value) /**< non prop-name ecma-value */ +{ + JERRY_ASSERT (!ecma_is_value_prop_name (value)); + + ecma_string_t *string_p = ecma_alloc_string (); + string_p->refs_and_container = ECMA_STRING_REF_ONE | ECMA_STRING_CONTAINER_MAP_KEY; + string_p->u.value = ecma_copy_value_if_not_object (value); + string_p->hash = ecma_is_value_simple (value) ? (uint16_t) value : 0; + + return string_p; +} /* ecma_new_map_key_string */ + +/** + * Check whether an ecma-string contains a map key string + * + * @return true - if the ecma-string contains a map key string + * false - otherwise + */ +bool +ecma_prop_name_is_map_key (ecma_string_t *string_p) /**< ecma-string */ +{ + JERRY_ASSERT (string_p != NULL); + + return (!ECMA_IS_DIRECT_STRING (string_p) + && ECMA_STRING_GET_CONTAINER (string_p) == ECMA_STRING_CONTAINER_MAP_KEY); +} /* ecma_prop_name_is_map_key */ +#endif /* ENABLED (JERRY_ES2015_BUILTIN_MAP) */ + /** * Allocate new ecma-string and fill it with characters from the utf8 string * @@ -984,6 +1020,13 @@ ecma_deref_ecma_string (ecma_string_t *string_p) /**< ecma-string */ break; } #endif /* ENABLED (JERRY_ES2015_BUILTIN_SYMBOL) */ +#if ENABLED (JERRY_ES2015_BUILTIN_MAP) + case ECMA_STRING_CONTAINER_MAP_KEY: + { + ecma_free_value_if_not_object (string_p->u.value); + break; + } +#endif /* ENABLED (JERRY_ES2015_BUILTIN_MAP) */ default: { JERRY_ASSERT (ECMA_STRING_GET_CONTAINER (string_p) == ECMA_STRING_CONTAINER_UINT32_IN_DESC @@ -1865,6 +1908,13 @@ ecma_compare_ecma_non_direct_strings (const ecma_string_t *string1_p, /**< ecma- return false; } +#if ENABLED (JERRY_ES2015_BUILTIN_MAP) + if (string1_container == ECMA_STRING_CONTAINER_MAP_KEY) + { + return ecma_op_same_value_zero (string1_p->u.value, string2_p->u.value); + } +#endif /* ENABLED (JERRY_ES2015_BUILTIN_MAP) */ + if (string1_container >= ECMA_STRING_CONTAINER_UINT32_IN_DESC) { return string1_p->u.common_uint32_field == string2_p->u.common_uint32_field; diff --git a/jerry-core/ecma/base/ecma-helpers.h b/jerry-core/ecma/base/ecma-helpers.h index eb6a0d2ae..3d2b04c1f 100644 --- a/jerry-core/ecma/base/ecma-helpers.h +++ b/jerry-core/ecma/base/ecma-helpers.h @@ -222,6 +222,10 @@ lit_magic_string_id_t ecma_get_typeof_lit_id (ecma_value_t value); ecma_string_t *ecma_new_symbol_from_descriptor_string (ecma_value_t string_desc); bool ecma_prop_name_is_symbol (ecma_string_t *string_p); #endif /* ENABLED (JERRY_ES2015_BUILTIN_SYMBOL) */ +#if ENABLED (JERRY_ES2015_BUILTIN_MAP) +ecma_string_t *ecma_new_map_key_string (ecma_value_t value); +bool ecma_prop_name_is_map_key (ecma_string_t *string_p); +#endif /* ENABLED (JERRY_ES2015_BUILTIN_MAP) */ ecma_string_t *ecma_new_ecma_string_from_utf8 (const lit_utf8_byte_t *string_p, lit_utf8_size_t string_size); ecma_string_t *ecma_new_ecma_string_from_utf8_converted_to_cesu8 (const lit_utf8_byte_t *string_p, lit_utf8_size_t string_size); diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-map-prototype.c b/jerry-core/ecma/builtin-objects/ecma-builtin-map-prototype.c index 916251c54..5a8941beb 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-map-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-map-prototype.c @@ -65,6 +65,24 @@ ecma_builtin_map_prototype_object_delete (ecma_value_t this_arg, /**< this argum return ecma_op_map_delete (this_arg, key_arg); } /* ecma_builtin_map_prototype_object_delete */ +/** + * The Map.prototype object's 'forEach' routine + * + * See also: + * ECMA-262 v6, 23.1.3.5 + * + * @return ecma value + * Returned value must be freed with ecma_free_value. + */ +static ecma_value_t +ecma_builtin_map_prototype_object_foreach (ecma_value_t this_arg, /**< this argument */ + ecma_value_t predicate, /**< callback function */ + ecma_value_t predicate_this_arg) /**< this argument for + * invoke predicate */ +{ + return ecma_op_map_foreach (this_arg, predicate, predicate_this_arg); +} /* ecma_builtin_map_prototype_object_foreach */ + /** * The Map.prototype object's 'get' routine * diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-map-prototype.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-map-prototype.inc.h index e2d288322..628647e68 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-map-prototype.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-map-prototype.inc.h @@ -40,6 +40,7 @@ STRING_VALUE (LIT_GLOBAL_SYMBOL_TO_STRING_TAG, * (property name, C routine name, arguments number or NON_FIXED, value of the routine's length property) */ ROUTINE (LIT_MAGIC_STRING_CLEAR, ecma_builtin_map_prototype_object_clear, 0, 0) ROUTINE (LIT_MAGIC_STRING_DELETE, ecma_builtin_map_prototype_object_delete, 1, 1) +ROUTINE (LIT_MAGIC_STRING_FOR_EACH_UL, ecma_builtin_map_prototype_object_foreach, 2, 1) ROUTINE (LIT_MAGIC_STRING_GET, ecma_builtin_map_prototype_object_get, 1, 1) ROUTINE (LIT_MAGIC_STRING_HAS, ecma_builtin_map_prototype_object_has, 1, 1) ROUTINE (LIT_MAGIC_STRING_SET, ecma_builtin_map_prototype_object_set, 2, 2) diff --git a/jerry-core/ecma/operations/ecma-conversion.c b/jerry-core/ecma/operations/ecma-conversion.c index 781d7807b..0bafb5abb 100644 --- a/jerry-core/ecma/operations/ecma-conversion.c +++ b/jerry-core/ecma/operations/ecma-conversion.c @@ -156,6 +156,54 @@ ecma_op_same_value (ecma_value_t x, /**< ecma value */ } } /* ecma_op_same_value */ +#if ENABLED (JERRY_ES2015_BUILTIN_MAP) +/** + * SameValueZero operation. + * + * See also: + * ECMA-262 v6, 7.2.10 + * + * @return true - if the value are same according to ECMA-defined SameValueZero algorithm, + * false - otherwise + */ +bool +ecma_op_same_value_zero (ecma_value_t x, /**< ecma value */ + ecma_value_t y) /**< ecma value */ +{ + if (ecma_is_value_number (x) && ecma_is_value_number (y)) + { + ecma_number_t x_num = ecma_get_number_from_value (x); + ecma_number_t y_num = ecma_get_number_from_value (y); + + bool is_x_nan = ecma_number_is_nan (x_num); + bool is_y_nan = ecma_number_is_nan (y_num); + + if (is_x_nan || is_y_nan) + { + /* + * If both are NaN + * return true; + * else + * one of the numbers is NaN, and another - is not + * return false; + */ + return (is_x_nan && is_y_nan); + } + + if (ecma_number_is_zero (x_num) + && ecma_number_is_zero (y_num) + && ecma_number_is_negative (x_num) != ecma_number_is_negative (y_num)) + { + return true; + } + + return (x_num == y_num); + } + + return ecma_op_same_value (x, y); +} /* ecma_op_same_value_zero */ +#endif /* ENABLED (JERRY_ES2015_BUILTIN_MAP) */ + /** * ToPrimitive operation. * diff --git a/jerry-core/ecma/operations/ecma-conversion.h b/jerry-core/ecma/operations/ecma-conversion.h index 6b2fdc48e..80fafea76 100644 --- a/jerry-core/ecma/operations/ecma-conversion.h +++ b/jerry-core/ecma/operations/ecma-conversion.h @@ -39,6 +39,9 @@ typedef enum ecma_value_t ecma_op_check_object_coercible (ecma_value_t value); bool ecma_op_same_value (ecma_value_t x, ecma_value_t y); +#if ENABLED (JERRY_ES2015_BUILTIN_MAP) +bool ecma_op_same_value_zero (ecma_value_t x, ecma_value_t y); +#endif /* ENABLED (JERRY_ES2015_BUILTIN_MAP) */ ecma_value_t ecma_op_to_primitive (ecma_value_t value, ecma_preferred_type_hint_t preferred_type); bool ecma_op_to_boolean (ecma_value_t value); ecma_value_t ecma_op_to_number (ecma_value_t value); diff --git a/jerry-core/ecma/operations/ecma-map-object.c b/jerry-core/ecma/operations/ecma-map-object.c index f7c157662..32d3f973f 100644 --- a/jerry-core/ecma/operations/ecma-map-object.c +++ b/jerry-core/ecma/operations/ecma-map-object.c @@ -13,11 +13,14 @@ * limitations under the License. */ +#include "ecma-alloc.h" #include "ecma-builtins.h" #include "ecma-exceptions.h" +#include "ecma-function-object.h" #include "ecma-gc.h" #include "ecma-helpers.h" #include "ecma-map-object.h" +#include "ecma-property-hashmap.h" #include "ecma-objects.h" #if ENABLED (JERRY_ES2015_BUILTIN_MAP) @@ -29,8 +32,23 @@ * @{ */ -JERRY_STATIC_ASSERT (ECMA_MAP_OBJECT_ITEM_COUNT == 3, - ecma_map_object_item_count_must_be_3); +/** + * Creates an empty object for the map object's internal slot. + * + * Note: The created object is not registered to the GC. + * + * @return ecma value of the created object + */ +static ecma_value_t +ecma_op_map_create_internal_object (void) +{ + ecma_object_t *internal_object_p = ecma_alloc_object (); + internal_object_p->type_flags_refs = (ECMA_OBJECT_TYPE_GENERAL | ECMA_OBJECT_FLAG_EXTENSIBLE | ECMA_OBJECT_REF_ONE); + internal_object_p->property_list_or_bound_object_cp = JMEM_CP_NULL; + internal_object_p->prototype_or_outer_reference_cp = JMEM_CP_NULL; + + return ecma_make_object_value (internal_object_p); +} /* ecma_op_map_create_internal_object */ /** * Handle calling [[Construct]] of built-in map like objects @@ -43,17 +61,14 @@ ecma_op_map_create (const ecma_value_t *arguments_list_p, /**< arguments list */ { JERRY_ASSERT (arguments_list_len == 0 || arguments_list_p != NULL); - ecma_object_t *prototype_obj_p = ecma_builtin_get (ECMA_BUILTIN_ID_MAP_PROTOTYPE); - ecma_object_t *object_p = ecma_create_object (prototype_obj_p, + ecma_object_t *object_p = ecma_create_object (ecma_builtin_get (ECMA_BUILTIN_ID_MAP_PROTOTYPE), sizeof (ecma_map_object_t), ECMA_OBJECT_TYPE_CLASS); - ecma_map_object_t *map_object_p = (ecma_map_object_t *) object_p; - map_object_p->header.u.class_prop.class_id = LIT_MAGIC_STRING_MAP_UL; - map_object_p->header.u.class_prop.extra_info = 0; - map_object_p->header.u.class_prop.u.length = 0; - map_object_p->first_chunk_cp = ECMA_NULL_POINTER; - map_object_p->last_chunk_cp = ECMA_NULL_POINTER; + ecma_map_object_t *map_obj_p = (ecma_map_object_t *) object_p; + map_obj_p->header.u.class_prop.class_id = LIT_MAGIC_STRING_MAP_UL; + map_obj_p->header.u.class_prop.u.value = ecma_op_map_create_internal_object (); + map_obj_p->size = 0; return ecma_make_object_value (object_p); } /* ecma_op_map_create */ @@ -86,6 +101,63 @@ ecma_op_map_get_object (ecma_value_t this_arg) /**< this argument */ return NULL; } /* ecma_op_map_get_object */ +/** + * Creates a property key for the internal object from the given argument + * + * Note: + * This operation does not increase the reference counter of strings and symbols + * + * @return property key + */ +static ecma_string_t * +ecma_op_map_to_key (ecma_value_t key_arg) /**< key argument */ +{ + if (ecma_is_value_prop_name (key_arg)) + { + ecma_string_t *prop_name_p = ecma_get_prop_name_from_value (key_arg); + ecma_ref_ecma_string (prop_name_p); + return prop_name_p; + } + + if (ecma_is_value_object (key_arg)) + { + ecma_object_t *obj_p = ecma_get_object_from_value (key_arg); + ecma_string_t *key_string_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_MAP_KEY); + ecma_property_t *property_p = ecma_find_named_property (obj_p, key_string_p); + ecma_string_t *object_key_string; + + if (property_p == NULL) + { + object_key_string = ecma_new_map_key_string (key_arg); + ecma_property_value_t *value_p = ecma_create_named_data_property (obj_p, + key_string_p, + ECMA_PROPERTY_CONFIGURABLE_ENUMERABLE_WRITABLE, + NULL); + value_p->value = ecma_make_string_value (object_key_string); + } + else + { + object_key_string = ecma_get_string_from_value (ECMA_PROPERTY_VALUE_PTR (property_p)->value); + + } + + ecma_ref_ecma_string (object_key_string); + return object_key_string; + } + + if (ecma_is_value_integer_number (key_arg)) + { + ecma_integer_value_t integer = ecma_get_integer_from_value (key_arg); + + if (JERRY_LIKELY (integer > 0 && integer <= ECMA_DIRECT_STRING_MAX_IMM)) + { + return (ecma_string_t *) ECMA_CREATE_DIRECT_STRING (ECMA_DIRECT_STRING_ECMA_INTEGER, (uintptr_t) integer); + } + } + + return ecma_new_map_key_string (key_arg); +} /* ecma_op_map_to_key */ + /** * Returns with the size of the map object. * @@ -95,106 +167,15 @@ ecma_value_t ecma_op_map_size (ecma_value_t this_arg) /**< this argument */ { ecma_map_object_t *map_object_p = ecma_op_map_get_object (this_arg); + if (map_object_p == NULL) { return ECMA_VALUE_ERROR; } - return ecma_make_uint32_value (map_object_p->header.u.class_prop.u.length); + return ecma_make_uint32_value (map_object_p->size); } /* ecma_op_map_size */ -/** - * Linear search for the value in the map storage - * - * @return pointer to value if key is found - * NULL otherwise - */ -static ecma_value_t * -ecma_builtin_map_search (jmem_cpointer_t first_chunk_cp, /**< first chunk */ - ecma_value_t key) /**< key to search */ -{ - if (JERRY_UNLIKELY (first_chunk_cp == ECMA_NULL_POINTER)) - { - return NULL; - } - - ecma_map_object_chunk_t *chunk_p = ECMA_GET_NON_NULL_POINTER (ecma_map_object_chunk_t, - first_chunk_cp); - - bool is_direct = true; - const ecma_string_t *key_str_p = NULL; - ecma_number_t key_float = 0; - - if (ecma_is_value_non_direct_string (key)) - { - key_str_p = ecma_get_string_from_value (key); - is_direct = false; - } - else if (ecma_is_value_float_number (key)) - { - key_float = ecma_get_float_from_value (key); - is_direct = false; - } - - ecma_value_t *item_p = chunk_p->items; - ecma_value_t last_key = ECMA_VALUE_ARRAY_HOLE; - - while (true) - { - ecma_value_t item = *item_p++; - - if (JERRY_UNLIKELY (item == ECMA_VALUE_ARRAY_HOLE)) - { - JERRY_ASSERT (last_key == ECMA_VALUE_ARRAY_HOLE); - continue; - } - - if (JERRY_UNLIKELY (ecma_is_value_pointer (item))) - { - item_p = (ecma_value_t *) ecma_get_pointer_from_value (item); - - if (item_p == NULL) - { - JERRY_ASSERT (last_key == ECMA_VALUE_ARRAY_HOLE); - return NULL; - } - - JERRY_ASSERT (!ecma_is_value_pointer (*item_p)); - continue; - } - - if (last_key == ECMA_VALUE_ARRAY_HOLE) - { - last_key = item; - } - else - { - if (JERRY_LIKELY (is_direct)) - { - if (key == last_key) - { - return item_p - 1; - } - } - else if (key_str_p != NULL) - { - if (ecma_is_value_non_direct_string (last_key) - && ecma_compare_ecma_non_direct_strings (key_str_p, ecma_get_string_from_value (last_key))) - { - return item_p - 1; - } - } - else if (ecma_is_value_float_number (last_key) - && ecma_get_float_from_value (last_key) == key_float) - { - return item_p - 1; - } - - last_key = ECMA_VALUE_ARRAY_HOLE; - } - } -} /* ecma_builtin_map_search */ - /** * The generic map prototype object's 'get' routine * @@ -206,19 +187,31 @@ ecma_op_map_get (ecma_value_t this_arg, /**< this argument */ ecma_value_t key_arg) /**< key argument */ { ecma_map_object_t *map_object_p = ecma_op_map_get_object (this_arg); + if (map_object_p == NULL) { return ECMA_VALUE_ERROR; } - ecma_value_t *value_p = ecma_builtin_map_search (map_object_p->first_chunk_cp, key_arg); - - if (value_p == NULL) + if (map_object_p->size == 0) { return ECMA_VALUE_UNDEFINED; } - return ecma_copy_value (*value_p); + ecma_object_t *internal_obj_p = ecma_get_object_from_value (map_object_p->header.u.class_prop.u.value); + + ecma_string_t *prop_name_p = ecma_op_map_to_key (key_arg); + + ecma_property_t *property_p = ecma_find_named_property (internal_obj_p, prop_name_p); + + ecma_deref_ecma_string (prop_name_p); + + if (property_p == NULL) + { + return ECMA_VALUE_UNDEFINED; + } + + return ecma_copy_value (ECMA_PROPERTY_VALUE_PTR (property_p)->value); } /* ecma_op_map_get */ /** @@ -232,12 +225,26 @@ ecma_op_map_has (ecma_value_t this_arg, /**< this argument */ ecma_value_t key_arg) /**< key argument */ { ecma_map_object_t *map_object_p = ecma_op_map_get_object (this_arg); + if (map_object_p == NULL) { return ECMA_VALUE_ERROR; } - return ecma_make_boolean_value (ecma_builtin_map_search (map_object_p->first_chunk_cp, key_arg) != NULL); + if (map_object_p->size == 0) + { + return ECMA_VALUE_FALSE; + } + + ecma_object_t *internal_obj_p = ecma_get_object_from_value (map_object_p->header.u.class_prop.u.value); + + ecma_string_t *prop_name_p = ecma_op_map_to_key (key_arg); + + ecma_property_t *property_p = ecma_find_named_property (internal_obj_p, prop_name_p); + + ecma_deref_ecma_string (prop_name_p); + + return ecma_make_boolean_value (property_p != NULL); } /* ecma_op_map_has */ /** @@ -252,79 +259,34 @@ ecma_op_map_set (ecma_value_t this_arg, /**< this argument */ ecma_value_t value_arg) /**< value argument */ { ecma_map_object_t *map_object_p = ecma_op_map_get_object (this_arg); + if (map_object_p == NULL) { return ECMA_VALUE_ERROR; } - ecma_value_t *value_p = ecma_builtin_map_search (map_object_p->first_chunk_cp, key_arg); + ecma_object_t *internal_obj_p = ecma_get_object_from_value (map_object_p->header.u.class_prop.u.value); - if (value_p == NULL) + ecma_string_t *prop_name_p = ecma_op_map_to_key (key_arg); + + ecma_property_t *property_p = ecma_find_named_property (internal_obj_p, prop_name_p); + + if (property_p == NULL) { - ecma_value_t *key_p = NULL; - ecma_map_object_chunk_t *last_chunk_p = ECMA_GET_POINTER (ecma_map_object_chunk_t, - map_object_p->last_chunk_cp); - - if (last_chunk_p != NULL) - { - if (last_chunk_p->items[2] == ECMA_VALUE_ARRAY_HOLE) - { - key_p = last_chunk_p->items + 2; - - if (last_chunk_p->items[1] == ECMA_VALUE_ARRAY_HOLE) - { - key_p = last_chunk_p->items + 1; - value_p = last_chunk_p->items + 2; - } - } - } - - if (key_p == NULL || value_p == NULL) - { - size_t size = sizeof (ecma_map_object_chunk_t); - ecma_map_object_chunk_t *new_chunk_p = (ecma_map_object_chunk_t *) jmem_heap_alloc_block (size); - - new_chunk_p->items[ECMA_MAP_OBJECT_ITEM_COUNT] = ecma_make_pointer_value (NULL); - - for (int i = 0; i < ECMA_MAP_OBJECT_ITEM_COUNT; i++) - { - new_chunk_p->items[i] = ECMA_VALUE_ARRAY_HOLE; - } - - ECMA_SET_NON_NULL_POINTER (map_object_p->last_chunk_cp, new_chunk_p); - - if (last_chunk_p == NULL) - { - map_object_p->first_chunk_cp = map_object_p->last_chunk_cp; - } - else - { - last_chunk_p->items[ECMA_MAP_OBJECT_ITEM_COUNT] = ecma_make_pointer_value (new_chunk_p); - } - - if (key_p == NULL) - { - JERRY_ASSERT (value_p == NULL); - key_p = new_chunk_p->items + 0; - value_p = new_chunk_p->items + 1; - } - else - { - value_p = new_chunk_p->items + 0; - } - } - - *key_p = ecma_copy_value_if_not_object (key_arg); - map_object_p->header.u.class_prop.u.length++; + ecma_property_value_t *value_p = ecma_create_named_data_property (internal_obj_p, + prop_name_p, + ECMA_PROPERTY_CONFIGURABLE_ENUMERABLE_WRITABLE, + NULL); + value_p->value = ecma_copy_value_if_not_object (value_arg); + map_object_p->size++; } else { - ecma_free_value_if_not_object (*value_p); + ecma_named_data_property_assign_value (internal_obj_p, ECMA_PROPERTY_VALUE_PTR (property_p), value_arg); } - *value_p = ecma_copy_value_if_not_object (value_arg); - - ecma_ref_object (&map_object_p->header.object); + ecma_deref_ecma_string (prop_name_p); + ecma_ref_object ((ecma_object_t *) &map_object_p->header); return this_arg; } /* ecma_op_map_set */ @@ -334,43 +296,121 @@ ecma_op_map_set (ecma_value_t this_arg, /**< this argument */ void ecma_op_map_clear_map (ecma_map_object_t *map_object_p) /**< map object */ { - JERRY_ASSERT (ecma_get_object_type (&map_object_p->header.object) == ECMA_OBJECT_TYPE_CLASS - && (map_object_p->header.u.class_prop.class_id == LIT_MAGIC_STRING_MAP_UL)); + ecma_object_t *object_p = ecma_get_object_from_value (map_object_p->header.u.class_prop.u.value); - jmem_cpointer_t first_chunk_cp = map_object_p->first_chunk_cp; + JERRY_ASSERT (object_p->type_flags_refs >= ECMA_OBJECT_REF_ONE); - if (JERRY_UNLIKELY (first_chunk_cp == ECMA_NULL_POINTER)) + ecma_property_header_t *prop_iter_p = ecma_get_property_list (object_p); + + if (prop_iter_p != NULL && prop_iter_p->types[0] == ECMA_PROPERTY_TYPE_HASHMAP) { - return; + ecma_property_hashmap_free (object_p); + prop_iter_p = ecma_get_property_list (object_p); } - ecma_map_object_chunk_t *chunk_p = ECMA_GET_NON_NULL_POINTER (ecma_map_object_chunk_t, - first_chunk_cp); - - do + while (prop_iter_p != NULL) { - ecma_value_t *current_p = chunk_p->items; - ecma_value_t *last_p = current_p + ECMA_MAP_OBJECT_ITEM_COUNT; + JERRY_ASSERT (ECMA_PROPERTY_IS_PROPERTY_PAIR (prop_iter_p)); - do + /* Both cannot be deleted. */ + JERRY_ASSERT (prop_iter_p->types[0] != ECMA_PROPERTY_TYPE_DELETED + || prop_iter_p->types[1] != ECMA_PROPERTY_TYPE_DELETED); + + ecma_property_pair_t *prop_pair_p = (ecma_property_pair_t *) prop_iter_p; + + for (int i = 0; i < ECMA_PROPERTY_PAIR_ITEM_COUNT; i++) { - ecma_free_value_if_not_object (*current_p++); + ecma_property_t *property_p = (ecma_property_t *) (prop_iter_p->types + i); + jmem_cpointer_t name_cp = prop_pair_p->names_cp[i]; + + if (prop_iter_p->types[i] != ECMA_PROPERTY_TYPE_DELETED) + { + ecma_free_property (object_p, name_cp, property_p); + } } - while (current_p < last_p); - ecma_value_t next = *current_p; + prop_iter_p = ECMA_GET_POINTER (ecma_property_header_t, + prop_iter_p->next_property_cp); - jmem_heap_free_block (chunk_p, sizeof (ecma_map_object_chunk_t)); - - chunk_p = (ecma_map_object_chunk_t *) ecma_get_pointer_from_value (next); + ecma_dealloc_property_pair (prop_pair_p); } - while (chunk_p != NULL); - map_object_p->header.u.class_prop.u.length = 0; - map_object_p->first_chunk_cp = ECMA_NULL_POINTER; - map_object_p->last_chunk_cp = ECMA_NULL_POINTER; + ecma_dealloc_object (object_p); } /* ecma_op_map_clear_map */ +/** + * The generic map prototype object's 'forEach' routine + * + * @return ecma value + * Returned value must be freed with ecma_free_value. + */ +ecma_value_t +ecma_op_map_foreach (ecma_value_t this_arg, /**< this argument */ + ecma_value_t predicate, /**< callback function */ + ecma_value_t predicate_this_arg) /**< this argument for + * invoke predicate */ +{ + ecma_map_object_t *map_object_p = ecma_op_map_get_object (this_arg); + + if (map_object_p == NULL) + { + return ECMA_VALUE_ERROR; + } + + if (!ecma_op_is_callable (predicate)) + { + return ecma_raise_type_error (ECMA_ERR_MSG ("Callback function is not callable.")); + } + + JERRY_ASSERT (ecma_is_value_object (predicate)); + ecma_object_t *func_object_p = ecma_get_object_from_value (predicate); + + ecma_object_t *internal_obj_p = ecma_get_object_from_value (map_object_p->header.u.class_prop.u.value); + + ecma_collection_header_t *props_p = ecma_op_object_get_property_names (internal_obj_p, ECMA_LIST_NO_OPTS); + + ecma_value_t *ecma_value_p = ecma_collection_iterator_init (props_p); + + ecma_value_t ret_value = ECMA_VALUE_UNDEFINED; + + while (ecma_value_p != NULL) + { + ecma_string_t *prop_name_p = ecma_get_prop_name_from_value (*ecma_value_p); + ecma_property_t *property_p = ecma_find_named_property (internal_obj_p, prop_name_p); + JERRY_ASSERT (property_p != NULL); + + ecma_value_t value = ecma_copy_value (ECMA_PROPERTY_VALUE_PTR (property_p)->value); + ecma_value_t key_arg; + + if (ecma_prop_name_is_map_key (prop_name_p)) + { + key_arg = prop_name_p->u.value; + } + else + { + key_arg = *ecma_value_p; + } + + ecma_value_t call_args[] = { value, key_arg }; + + ecma_value_t call_value = ecma_op_function_call (func_object_p, predicate_this_arg, call_args, 2); + + ecma_free_value (value); + + if (ECMA_IS_VALUE_ERROR (call_value)) + { + ret_value = call_value; + break; + } + + ecma_value_p = ecma_collection_iterator_next (ecma_value_p); + } + + ecma_free_values_collection (props_p, 0); + + return ret_value; +} /* ecma_op_map_foreach */ + /** * The Map prototype object's 'clear' routine * @@ -380,70 +420,21 @@ ecma_op_map_clear_map (ecma_map_object_t *map_object_p) /**< map object */ ecma_value_t ecma_op_map_clear (ecma_value_t this_arg) /**< this argument */ { - /* WeakMap does not have a clear method. */ ecma_map_object_t *map_object_p = ecma_op_map_get_object (this_arg); + if (map_object_p == NULL) { return ECMA_VALUE_ERROR; } ecma_op_map_clear_map (map_object_p); + + map_object_p->header.u.class_prop.u.value = ecma_op_map_create_internal_object (); + map_object_p->size = 0; + return ECMA_VALUE_UNDEFINED; } /* ecma_op_map_clear */ -/** - * Deletes the current chunk if it is filled with ECMA_VALUE_ARRAY_HOLE. - * - * @return next chunk if the chunk is deleted, NULL otherwise - */ -static ecma_map_object_chunk_t * -ecma_op_map_delete_chunk (ecma_map_object_t *map_object_p, /**< map object */ - ecma_map_object_chunk_t *chunk_p, /**< current chunk */ - ecma_map_object_chunk_t *prev_chunk_p) /**< previous chunk */ -{ - for (int i = 0; i < ECMA_MAP_OBJECT_ITEM_COUNT; i++) - { - JERRY_ASSERT (!ecma_is_value_pointer (chunk_p->items[i])); - - if (chunk_p->items[i] != ECMA_VALUE_ARRAY_HOLE) - { - return NULL; - } - } - - ecma_value_t next_chunk = chunk_p->items[ECMA_MAP_OBJECT_ITEM_COUNT]; - ecma_map_object_chunk_t *next_chunk_p = (ecma_map_object_chunk_t *) ecma_get_pointer_from_value (next_chunk); - - jmem_heap_free_block (chunk_p, sizeof (ecma_map_object_chunk_t)); - - if (prev_chunk_p != NULL) - { - prev_chunk_p->items[ECMA_MAP_OBJECT_ITEM_COUNT] = ecma_make_pointer_value (next_chunk_p); - - if (next_chunk_p == NULL) - { - JERRY_ASSERT (map_object_p->first_chunk_cp != map_object_p->last_chunk_cp); - JERRY_ASSERT (ECMA_GET_NON_NULL_POINTER (ecma_map_object_chunk_t, map_object_p->last_chunk_cp) == chunk_p); - - ECMA_SET_POINTER (map_object_p->last_chunk_cp, prev_chunk_p); - } - return next_chunk_p; - } - - if (next_chunk_p == NULL) - { - JERRY_ASSERT (map_object_p->first_chunk_cp == map_object_p->last_chunk_cp); - JERRY_ASSERT (ECMA_GET_NON_NULL_POINTER (ecma_map_object_chunk_t, map_object_p->last_chunk_cp) == chunk_p); - - map_object_p->first_chunk_cp = ECMA_NULL_POINTER; - map_object_p->last_chunk_cp = ECMA_NULL_POINTER; - return next_chunk_p; - } - - ECMA_SET_POINTER (map_object_p->first_chunk_cp, next_chunk_p); - return next_chunk_p; -} /* ecma_op_map_delete_chunk */ - /** * The generic map prototype object's 'delete' routine * @@ -455,123 +446,27 @@ ecma_op_map_delete (ecma_value_t this_arg, /**< this argument */ ecma_value_t key_arg) /**< key argument */ { ecma_map_object_t *map_object_p = ecma_op_map_get_object (this_arg); + if (map_object_p == NULL) { return ECMA_VALUE_ERROR; } - if (JERRY_UNLIKELY (map_object_p->first_chunk_cp == ECMA_NULL_POINTER)) + ecma_object_t *internal_obj_p = ecma_get_object_from_value (map_object_p->header.u.class_prop.u.value); + + ecma_string_t *prop_name_p = ecma_op_map_to_key (key_arg); + + ecma_property_t *property_p = ecma_find_named_property (internal_obj_p, prop_name_p); + + ecma_deref_ecma_string (prop_name_p); + + if (property_p == NULL) { return ECMA_VALUE_FALSE; } - ecma_map_object_chunk_t *chunk_p = ECMA_GET_NON_NULL_POINTER (ecma_map_object_chunk_t, - map_object_p->first_chunk_cp); - - bool is_direct = true; - const ecma_string_t *key_str_p = NULL; - ecma_number_t key_float = 0; - - if (ecma_is_value_non_direct_string (key_arg)) - { - key_str_p = ecma_get_string_from_value (key_arg); - is_direct = false; - } - else if (ecma_is_value_float_number (key_arg)) - { - key_float = ecma_get_float_from_value (key_arg); - is_direct = false; - } - - ecma_map_object_chunk_t *prev_chunk_p = NULL; - ecma_value_t *item_p = chunk_p->items; - bool is_key = true; - - while (true) - { - ecma_value_t item = *item_p++; - - if (JERRY_UNLIKELY (item == ECMA_VALUE_ARRAY_HOLE)) - { - JERRY_ASSERT (is_key); - continue; - } - - if (JERRY_UNLIKELY (ecma_is_value_pointer (item))) - { - prev_chunk_p = chunk_p; - chunk_p = (ecma_map_object_chunk_t *) ecma_get_pointer_from_value (item); - - if (chunk_p == NULL) - { - JERRY_ASSERT (is_key); - return ECMA_VALUE_FALSE; - } - - item_p = chunk_p->items; - - JERRY_ASSERT (!ecma_is_value_pointer (*item_p)); - continue; - } - - if (is_key) - { - if (JERRY_LIKELY (is_direct)) - { - if (key_arg == item) - { - break; - } - } - else if (key_str_p != NULL) - { - if (ecma_is_value_non_direct_string (item) - && ecma_compare_ecma_non_direct_strings (key_str_p, ecma_get_string_from_value (item))) - { - break; - } - } - else if (ecma_is_value_float_number (item) - && ecma_get_float_from_value (item) == key_float) - { - break; - } - } - - is_key = !is_key; - } - - map_object_p->header.u.class_prop.u.length--; - - item_p -= 1; - ecma_free_value_if_not_object (item_p[0]); - item_p[0] = ECMA_VALUE_ARRAY_HOLE; - - if ((item_p - chunk_p->items) < ECMA_MAP_OBJECT_ITEM_COUNT - 1) - { - JERRY_ASSERT (!ecma_is_value_pointer (item_p[1])); - - ecma_free_value_if_not_object (item_p[1]); - item_p[1] = ECMA_VALUE_ARRAY_HOLE; - - ecma_op_map_delete_chunk (map_object_p, chunk_p, prev_chunk_p); - return ECMA_VALUE_TRUE; - } - - ecma_map_object_chunk_t *next_chunk_p = ecma_op_map_delete_chunk (map_object_p, chunk_p, prev_chunk_p); - - if (next_chunk_p == NULL) - { - prev_chunk_p = chunk_p; - - ecma_value_t next_chunk = chunk_p->items[ECMA_MAP_OBJECT_ITEM_COUNT]; - next_chunk_p = (ecma_map_object_chunk_t *) ecma_get_pointer_from_value (next_chunk); - } - - ecma_free_value_if_not_object (next_chunk_p->items[0]); - next_chunk_p->items[0] = ECMA_VALUE_ARRAY_HOLE; - - ecma_op_map_delete_chunk (map_object_p, next_chunk_p, prev_chunk_p); + ecma_delete_property (internal_obj_p, ECMA_PROPERTY_VALUE_PTR (property_p)); + map_object_p->size--; return ECMA_VALUE_TRUE; } /* ecma_op_map_delete */ diff --git a/jerry-core/ecma/operations/ecma-map-object.h b/jerry-core/ecma/operations/ecma-map-object.h index 62f908796..29ad9f366 100644 --- a/jerry-core/ecma/operations/ecma-map-object.h +++ b/jerry-core/ecma/operations/ecma-map-object.h @@ -30,6 +30,7 @@ ecma_value_t ecma_op_map_create (const ecma_value_t *arguments_list_p, ecma_length_t arguments_list_len); ecma_value_t ecma_op_map_size (ecma_value_t this_arg); ecma_value_t ecma_op_map_get (ecma_value_t this_arg, ecma_value_t key_arg); +ecma_value_t ecma_op_map_foreach (ecma_value_t this_arg, ecma_value_t predicate, ecma_value_t predicate_this_arg); ecma_value_t ecma_op_map_has (ecma_value_t this_arg, ecma_value_t key_arg); ecma_value_t ecma_op_map_set (ecma_value_t this_arg, ecma_value_t key_arg, ecma_value_t value_arg); void ecma_op_map_clear_map (ecma_map_object_t *map_object_p); diff --git a/jerry-core/lit/lit-magic-strings.h b/jerry-core/lit/lit-magic-strings.h index a20f70554..faf636b40 100644 --- a/jerry-core/lit/lit-magic-strings.h +++ b/jerry-core/lit/lit-magic-strings.h @@ -47,6 +47,7 @@ typedef enum LIT_INTERNAL_MAGIC_STRING_PROMISE_PROPERTY_VALUE, /**< [[Values]] property */ LIT_INTERNAL_MAGIC_STRING_PROMISE_PROPERTY_REMAINING_ELEMENT, /**< [[RemainingElement]] property */ LIT_INTERNAL_MAGIC_STRING_ITERATOR_NEXT_INDEX, /**< [[%Iterator%NextIndex]] property */ + LIT_INTERNAL_MAGIC_STRING_MAP_KEY, /**< Property key used when an object is a key in a map object */ /* List of well known symbols */ LIT_GLOBAL_SYMBOL_HAS_INSTANCE, /**< @@hasInstance well known symbol */ LIT_GLOBAL_SYMBOL_IS_CONCAT_SPREADABLE, /**< @@isConcatSpreadable well known symbol */ diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index 579a272d9..f794b3d4a 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -329,6 +329,7 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_COMPILE, "compile") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_ENTRIES, "entries") #endif #if ENABLED (JERRY_BUILTIN_ARRAY) \ +|| ENABLED (JERRY_ES2015_BUILTIN_MAP) \ || ENABLED (JERRY_ES2015_BUILTIN_TYPEDARRAY) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_FOR_EACH_UL, "forEach") #endif diff --git a/tests/jerry/es2015/map-prototype-foreach.js b/tests/jerry/es2015/map-prototype-foreach.js new file mode 100644 index 000000000..300f20c82 --- /dev/null +++ b/tests/jerry/es2015/map-prototype-foreach.js @@ -0,0 +1,108 @@ +/* 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 map = new Map(); + +/* Test when the map object keys are strings */ +for (var i = 0; i < 15; i++) { + map.set ("foo" + i, i); + assert (map.size === i + 1); +} + +var counter = 0; + +map.forEach (function (value, key) { + assert (typeof key === "string"); + assert (key === "foo" + counter); + assert (typeof value === "number"); + assert (value === counter); + counter++; +}); + +map.clear (); +assert (map.size === 0); +counter = 0; + +/* Test when the map object keys are numbers */ +for (var i = 0; i < 15; i++) { + map.set (i + 0.5, "foo" + i); + assert (map.size === i + 1); +} + +map.forEach (function (value, key) { + assert (typeof key === "number"); + assert (key === counter + 0.5); + assert (typeof value === "string"); + assert (value === "foo" + counter); + counter++; +}); + +map.clear (); +assert (map.size === 0); +counter = 0; + +var objectList = []; + +/* Test when the map object keys are objects */ +for (var i = 0; i < 15; i++) { + objectList[i] = { index : i }; + map.set (objectList[i], "foo" + i); + assert (map.size === i + 1); +} + +map.forEach (function (value, key) { + assert (typeof key === "object"); + assert (key === objectList[counter]); + assert (key.index === objectList[counter].index); + assert (typeof value === "string"); + assert (value === "foo" + counter); + counter++; +}); + +/* Test when the this argument is not a map object */ +try { + Map.prototype.forEach.call ({}); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +/* Test when the 1st argument is not callable */ +try { + map.forEach (5); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +/* Test when the the callback function throws error */ +try { + map.forEach (function (value, key) { + throw new ReferenceError ("foo"); + }); + assert (false); +} catch (e) { + assert (e instanceof ReferenceError); + assert (e.message === "foo"); +} + +/* Test when the 2nd optional argument is present */ +var object = { + _secret : 42 +} + +map.forEach (function (value, key) { + assert (this._secret === 42); +}, object); diff --git a/tests/jerry/es2015/map.js b/tests/jerry/es2015/map.js index 3618045e6..955e1ce1d 100644 --- a/tests/jerry/es2015/map.js +++ b/tests/jerry/es2015/map.js @@ -73,6 +73,43 @@ assert(m.get(1) === 8); assert(m.get(2) === 8); assert(m.get(3) === 8); +m.set(NaN, "not a number"); +assert(m.size === 4); +assert(m.get(NaN) === "not a number"); +assert(m.get(Number("foo")) === "not a number") + +m.set(0, "PosZero"); +assert(m.size === 5); +m.set(-0, "NegZero"); +assert(m.size === 5); +assert(m.get (0) === "NegZero"); +assert(m.get (-0) === "NegZero"); + +var symbolFoo = Symbol ("foo"); +m.set (symbolFoo, "SymbolFoo"); +assert(m.size === 6); +assert(m.get(symbolFoo) === "SymbolFoo"); + +var object = {}; +m.set (object, "object"); +assert(m.size === 7); +assert(m.get(object) === "object"); +assert(m.get({}) === undefined); + +var myObj = { o : 4 }; +m.set("foo", myObj); +assert(m.size === 8); +assert(m.get ("foo") === myObj); +assert(m.get ("foo").o === 4); + +m.clear(); +assert(m.size === 0); + +m.set("foo", myObj); +assert(m.size === 1); +assert(m.get ("foo") === myObj); +assert(m.get ("foo").o === 4); + var mapNameDesc = Object.getOwnPropertyDescriptor (Map, 'name'); assert(mapNameDesc.value === "Map"); assert(mapNameDesc.writable === false);