diff --git a/src/ant.c b/src/ant.c index 020a16c..0fbb9a2 100644 --- a/src/ant.c +++ b/src/ant.c @@ -1510,6 +1510,21 @@ continue_object_print:; if (lookup_string_prop_meta(js, as_obj, key, (size_t)klen, &meta) && !meta.enumerable) continue; } + if (prop->has_getter || prop->has_setter) { + if (!first) n += cpy(buf + n, REMAIN(n, len), inline_mode ? ", " : ",\n", 2); + first = false; + if (!inline_mode) n += add_indent(buf + n, REMAIN(n, len), stringify_indent); + n += strkey_interned(js, key, (size_t)klen, buf + n, REMAIN(n, len)); + n += cpy(buf + n, REMAIN(n, len), ": ", 2); + if (prop->has_getter && prop->has_setter) + n += cpy(buf + n, REMAIN(n, len), "[Getter/Setter]", 15); + else if (prop->has_getter) + n += cpy(buf + n, REMAIN(n, len), "[Getter]", 8); + else + n += cpy(buf + n, REMAIN(n, len), "[Setter]", 8); + continue; + } + if (!first) n += cpy(buf + n, REMAIN(n, len), inline_mode ? ", " : ",\n", 2); first = false; if (!inline_mode) n += add_indent(buf + n, REMAIN(n, len), stringify_indent); @@ -3139,22 +3154,27 @@ bool js_try_get_own_data_prop(ant_t *js, ant_value_t obj, const char *key, size_ return false; } +// TODO: decompose into smaller helpers ant_value_t js_setprop(ant_t *js, ant_value_t obj, ant_value_t k, ant_value_t v) { uint8_t ot = vtype(obj); - if (ot == T_STR || ot == T_NUM || ot == T_BOOL) { + if (ot == T_STR || ot == T_NUM || ot == T_BOOL || ot == T_CFUNC) { ant_offset_t klen; ant_offset_t koff = vstr(js, k, &klen); const char *key = (char *)(uintptr_t)(koff); - ant_value_t proto = get_prototype_for_type(js, ot); - if (is_object_type(proto)) { - ant_value_t setter = js_mkundef(); - bool has_setter = false; - lkp_with_setter(js, proto, key, klen, &setter, &has_setter); - if (has_setter && (vtype(setter) == T_FUNC || vtype(setter) == T_CFUNC)) { - call_proto_accessor(js, obj, setter, true, &v, 1, true); - return v; + + if (ot != T_CFUNC) { + ant_value_t proto = get_prototype_for_type(js, ot); + if (is_object_type(proto)) { + ant_value_t setter = js_mkundef(); + bool has_setter = false; + lkp_with_setter(js, proto, key, klen, &setter, &has_setter); + if (has_setter && (vtype(setter) == T_FUNC || vtype(setter) == T_CFUNC)) { + call_proto_accessor(js, obj, setter, true, &v, 1, true); + return v; + } } } + if (sv_vm_is_strict(js->vm)) return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot create property '%.*s' on %s", @@ -5761,8 +5781,16 @@ static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, i ant_value_t obj = args[0]; ant_value_t prop = args[1]; ant_value_t descriptor = args[2]; - uint8_t t = vtype(obj); + + if (t == T_CFUNC) { + ant_value_t fn_obj = mkobj(js, 0); + set_slot(fn_obj, SLOT_CFUNC, obj); + obj = js_obj_to_func(fn_obj); + args[0] = obj; + t = T_FUNC; + } + if (t != T_OBJ && t != T_ARR && t != T_FUNC) { return js_mkerr(js, "Object.defineProperty called on non-object"); } diff --git a/src/silver/ops/objects.h b/src/silver/ops/objects.h index 7e5aaf6..259e61e 100644 --- a/src/silver/ops/objects.h +++ b/src/silver/ops/objects.h @@ -122,10 +122,10 @@ static inline void sv_op_copy_data_props( const char *key = NULL; size_t key_len = 0; - ant_value_t val = js_mkundef(); ant_iter_t iter = js_prop_iter_begin(js, src); - while (js_prop_iter_next(&iter, &key, &key_len, &val)) { + 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); } diff --git a/tests/test_spread_accessor.js b/tests/test_spread_accessor.js new file mode 100644 index 0000000..6e644c8 --- /dev/null +++ b/tests/test_spread_accessor.js @@ -0,0 +1,59 @@ +// Repro: object spread should invoke getters + +const src = {}; +Object.defineProperty(src, 'x', { + get() { return 42; }, + enumerable: true, +}); +Object.defineProperty(src, 'y', { + get() { return 'hello'; }, + enumerable: true, +}); +src.z = 99; // plain data property for comparison + +const copy = { ...src }; + +let pass = true; + +if (copy.x !== 42) { + console.log("FAIL: copy.x expected 42, got", copy.x); + pass = false; +} +if (copy.y !== 'hello') { + console.log("FAIL: copy.y expected 'hello', got", copy.y); + pass = false; +} +if (copy.z !== 99) { + console.log("FAIL: copy.z expected 99, got", copy.z); + pass = false; +} + +// Also test getter that references `this` +const src2 = { _val: 10 }; +Object.defineProperty(src2, 'doubled', { + get() { return this._val * 2; }, + enumerable: true, +}); + +const copy2 = { ...src2 }; +if (copy2.doubled !== 20) { + console.log("FAIL: copy2.doubled expected 20, got", copy2.doubled); + pass = false; +} +if (copy2._val !== 10) { + console.log("FAIL: copy2._val expected 10, got", copy2._val); + pass = false; +} + +// Getter-only (no setter) should still copy the value, not the accessor +const desc = Object.getOwnPropertyDescriptor(copy, 'x'); +if (desc.get) { + console.log("FAIL: copy.x should be a data property, not an accessor"); + pass = false; +} +if (desc.value !== 42) { + console.log("FAIL: copy.x descriptor value expected 42, got", desc.value); + pass = false; +} + +if (pass) console.log("PASS");