diff --git a/include/ant.h b/include/ant.h index 48b8721..6151cb5 100644 --- a/include/ant.h +++ b/include/ant.h @@ -151,11 +151,21 @@ typedef struct { ant_offset_t off; } ant_iter_t; +typedef struct { + uint32_t slot; + bool is_symbol; + const char *str; + size_t key_len; + ant_offset_t sym_off; +} ant_iter_key_t; + ant_iter_t js_prop_iter_begin(ant_t *js, ant_value_t obj); void js_prop_iter_end(ant_iter_t *iter); +bool js_prop_iter_next_key(ant_iter_t *iter, ant_iter_key_t *key_out, ant_value_t *value); bool js_prop_iter_next(ant_iter_t *iter, const char **key, size_t *key_len, ant_value_t *value); bool js_prop_iter_next_val(ant_iter_t *iter, ant_value_t *key_out, ant_value_t *value); +bool js_is_own_enumerable_prop(ant_t *js, ant_value_t source, ant_object_t *source_ptr, const ant_iter_key_t *key); ant_value_t js_obj_to_func(ant_value_t obj); ant_value_t js_obj_to_func_ex(ant_value_t obj, uint8_t flags); diff --git a/src/ant.c b/src/ant.c index 9f60075..225f5ca 100644 --- a/src/ant.c +++ b/src/ant.c @@ -7095,22 +7095,28 @@ static ant_value_t builtin_object_defineProperties(ant_t *js, ant_value_t *args, return obj; } -static inline bool is_enumerable_prop( +bool js_is_own_enumerable_prop( ant_t *js, ant_value_t source, ant_object_t *source_ptr, - ant_value_t prop_key, uint32_t slot + const ant_iter_key_t *key ) { - if (vtype(prop_key) == T_STR) { - size_t klen = 0; - - const char *kstr = js_getstr(js, prop_key, &klen); - if (is_internal_prop(kstr, klen)) return false; - + if (!key) return false; + + if (key->is_symbol) { + if (!source_ptr || source_ptr->is_exotic) { + prop_meta_t meta; + return !lookup_symbol_prop_meta(js_as_obj(source), key->sym_off, &meta) || meta.enumerable; + } + return (ant_shape_get_attrs(source_ptr->shape, key->slot) & ANT_PROP_ATTR_ENUMERABLE) != 0; + } + + if (!key->str || is_internal_prop(key->str, key->key_len)) return false; + if (!source_ptr || source_ptr->is_exotic) { - descriptor_entry_t *desc = lookup_descriptor(js_as_obj(source), kstr, klen); + descriptor_entry_t *desc = lookup_descriptor(js_as_obj(source), key->str, key->key_len); return !desc || desc->enumerable; - }} - - return (ant_shape_get_attrs(source_ptr->shape, slot) & ANT_PROP_ATTR_ENUMERABLE) != 0; + } + + return (ant_shape_get_attrs(source_ptr->shape, key->slot) & ANT_PROP_ATTR_ENUMERABLE) != 0; } static ant_value_t builtin_object_assign(ant_t *js, ant_value_t *args, int nargs) { @@ -7139,12 +7145,14 @@ static ant_value_t builtin_object_assign(ant_t *js, ant_value_t *args, int nargs ant_iter_t iter = js_prop_iter_begin(js, source); ant_object_t *source_ptr = js_obj_ptr(js_as_obj(source)); - ant_value_t prop_key = js_mkundef(); + ant_iter_key_t key = {0}; ant_value_t val = js_mkundef(); - while (js_prop_iter_next_val(&iter, &prop_key, &val)) if ( - is_enumerable_prop(js, source, source_ptr, prop_key, (uint32_t)(iter.off - 1)) - ) js_setprop(js, as_obj, prop_key, val); + while (js_prop_iter_next_key(&iter, &key, &val)) { + if (!js_is_own_enumerable_prop(js, source, source_ptr, &key)) continue; + ant_value_t prop_key = key.is_symbol ? mkval(T_SYMBOL, key.sym_off) : js_mkstr(js, key.str, key.key_len); + js_setprop(js, as_obj, prop_key, val); + } js_prop_iter_end(&iter); } @@ -14842,52 +14850,44 @@ ant_iter_t js_prop_iter_begin(ant_t *js, ant_value_t obj) { } bool js_prop_iter_next(ant_iter_t *iter, const char **key, size_t *key_len, ant_value_t *value) { - if (!iter || !iter->ctx) return false; - prop_iter_ctx_t *ctx = (prop_iter_ctx_t *)iter->ctx; + ant_iter_key_t meta = {0}; - ant_object_t *obj = ctx->obj; - if (!obj || !obj->shape) return false; - - uint32_t count = ant_shape_count(obj->shape); - while (ctx->index < count) { - uint32_t i = ctx->index++; - const ant_shape_prop_t *prop = ant_shape_prop_at(obj->shape, i); - if (!prop) continue; - if (prop->type == ANT_SHAPE_KEY_SYMBOL) continue; - if (i >= obj->prop_count) continue; - - if (key) { - *key = prop->key.interned; - if (key_len) *key_len = strlen(prop->key.interned); - } - - if (value) *value = ant_object_prop_get_unchecked(obj, i); - iter->off = i + 1; - + while (js_prop_iter_next_key(iter, &meta, value)) { + if (meta.is_symbol) continue; + if (key) *key = meta.str; + if (key_len) *key_len = meta.key_len; return true; } return false; } -bool js_prop_iter_next_val(ant_iter_t *iter, ant_value_t *key_out, ant_value_t *value) { +bool js_prop_iter_next_key(ant_iter_t *iter, ant_iter_key_t *key_out, ant_value_t *value) { if (!iter || !iter->ctx) return false; prop_iter_ctx_t *ctx = (prop_iter_ctx_t *)iter->ctx; ant_object_t *obj = ctx->obj; if (!obj || !obj->shape) return false; + uint32_t count = ant_shape_count(obj->shape); - while (ctx->index < count) { uint32_t i = ctx->index++; const ant_shape_prop_t *prop = ant_shape_prop_at(obj->shape, i); - if (!prop) continue; if (i >= obj->prop_count) continue; - + if (key_out) { - if (prop->type == ANT_SHAPE_KEY_SYMBOL) *key_out = mkval(T_SYMBOL, prop->key.sym_off); - else *key_out = js_mkstr(ctx->js, prop->key.interned, strlen(prop->key.interned)); + key_out->slot = i; + key_out->is_symbol = (prop->type == ANT_SHAPE_KEY_SYMBOL); + if (key_out->is_symbol) { + key_out->str = NULL; + key_out->key_len = 0; + key_out->sym_off = prop->key.sym_off; + } else { + key_out->str = prop->key.interned; + key_out->key_len = strlen(prop->key.interned); + key_out->sym_off = 0; + } } if (value) *value = ant_object_prop_get_unchecked(obj, i); @@ -14899,6 +14899,22 @@ bool js_prop_iter_next_val(ant_iter_t *iter, ant_value_t *key_out, ant_value_t * return false; } +bool js_prop_iter_next_val(ant_iter_t *iter, ant_value_t *key_out, ant_value_t *value) { + ant_iter_key_t meta = {0}; + ant_t *js = NULL; + + if (!iter || !iter->ctx) return false; + js = ((prop_iter_ctx_t *)iter->ctx)->js; + if (!js_prop_iter_next_key(iter, &meta, value)) return false; + + if (key_out) { + if (meta.is_symbol) *key_out = mkval(T_SYMBOL, meta.sym_off); + else *key_out = js_mkstr(js, meta.str, meta.key_len); + } + + return true; +} + void js_prop_iter_end(ant_iter_t *iter) { if (!iter) return; free(iter->ctx); diff --git a/src/silver/ops/objects.h b/src/silver/ops/objects.h index 90da9ba..e2f53a4 100644 --- a/src/silver/ops/objects.h +++ b/src/silver/ops/objects.h @@ -119,17 +119,25 @@ static inline void sv_op_copy_data_props( ant_value_t src = vm->stack[vm->sp - 1]; ant_value_t dst = vm->stack[vm->sp - 2]; if (!is_object_type(src) || !is_object_type(dst)) return; - - const char *key = NULL; - size_t key_len = 0; + ant_iter_t iter = js_prop_iter_begin(js, src); + ant_object_t *source_ptr = js_obj_ptr(js_as_obj(src)); - while (js_prop_iter_next(&iter, &key, &key_len, NULL)) { - ant_value_t val = js_get(js, src, key); - ant_value_t prop_key = js_mkstr(js, key, key_len); - js_setprop(js, dst, prop_key, val); + ant_iter_key_t key = {0}; + ant_value_t val = js_mkundef(); + + while (js_prop_iter_next_key(&iter, &key, NULL)) { + if (!js_is_own_enumerable_prop(js, src, source_ptr, &key)) continue; + if (key.is_symbol) { + ant_value_t prop_key = mkval(T_SYMBOL, key.sym_off); + val = js_get_sym(js, src, prop_key); + js_setprop(js, dst, prop_key, val); + } else { + val = js_get(js, src, key.str); + js_setprop(js, dst, js_mkstr(js, key.str, key.key_len), val); + } } - + js_prop_iter_end(&iter); } diff --git a/tests/test_spread_symbols.cjs b/tests/test_spread_symbols.cjs new file mode 100644 index 0000000..d1b3575 --- /dev/null +++ b/tests/test_spread_symbols.cjs @@ -0,0 +1,64 @@ +let pass = true; + +const visible = Symbol("visible"); +const hidden = Symbol("hidden"); +const getterKey = Symbol("getter"); +let getterCalls = 0; + +const source = { + plain: 7, + [visible]: 11, +}; + +Object.defineProperty(source, hidden, { + value: 13, + enumerable: false, +}); + +Object.defineProperty(source, getterKey, { + enumerable: true, + get() { + getterCalls++; + return 17; + }, +}); + +const copy = { ...source }; + +if (copy.plain !== 7) { + console.log("FAIL: object spread should keep string-keyed data properties"); + pass = false; +} + +if (copy[visible] !== 11) { + console.log("FAIL: object spread should copy enumerable symbol properties"); + pass = false; +} + +if (Object.getOwnPropertySymbols(copy).includes(hidden)) { + console.log("FAIL: object spread should skip non-enumerable symbol properties"); + pass = false; +} + +if (copy[getterKey] !== 17) { + console.log("FAIL: object spread should read enumerable symbol getters"); + pass = false; +} + +if (getterCalls !== 1) { + console.log("FAIL: symbol getter should be invoked exactly once during spread"); + pass = false; +} + +const getterDesc = Object.getOwnPropertyDescriptor(copy, getterKey); +if (!getterDesc || getterDesc.get) { + console.log("FAIL: copied symbol getter should become a data property"); + pass = false; +} + +if (!getterDesc || getterDesc.value !== 17) { + console.log("FAIL: copied symbol getter value should be materialized"); + pass = false; +} + +if (pass) console.log("PASS");