From c80f35ac464fa223194293b0ee57c8cabdb5fee7 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 14 Apr 2026 19:01:48 -0700 Subject: [PATCH] small runtime fixes - centralize proxy enumeration - add inspect for headers/request/response - allow gen/map support for object keys --- examples/results.txt | 2 +- include/internal.h | 11 +- src/ant.c | 305 +++++++++++++++----- src/modules/headers.c | 56 ++++ src/modules/request.c | 60 ++++ src/modules/response.c | 52 ++++ tests/test_object_fromentries_iterables.cjs | 13 +- 7 files changed, 424 insertions(+), 75 deletions(-) diff --git a/examples/results.txt b/examples/results.txt index 3693fd3..84adc35 100644 --- a/examples/results.txt +++ b/examples/results.txt @@ -1294,7 +1294,7 @@ compat-table/es2018/regex.unicode-property-escapes.unicode-17.0.js: OK compat-table/es2019/Array.prototype.flatMap.js: OK compat-table/es2019/Array.prototype.flat-flatMap.unscopables.js: OK compat-table/es2019/Array.prototype.flat.js: OK -compat-table/es2019/Object.fromEntries.js: failed +compat-table/es2019/Object.fromEntries.js: OK compat-table/es2019/String.prototype.trimEnd.js: OK compat-table/es2019/String.prototype.trimStart.js: OK compat-table/es2019/Symbol.prototype.description.empty.js: failed diff --git a/include/internal.h b/include/internal.h index 2ef255c..4a185ad 100644 --- a/include/internal.h +++ b/include/internal.h @@ -374,14 +374,17 @@ typedef struct { void js_inspect_builder_init_fixed(js_inspect_builder_t *builder, ant_t *js, char *buf, size_t len, size_t initial_n); bool js_inspect_builder_init_dynamic(js_inspect_builder_t *builder, ant_t *js, size_t initial_cap); void js_inspect_builder_dispose(js_inspect_builder_t *builder); -ant_value_t js_inspect_builder_result(js_inspect_builder_t *builder); - -__attribute__((format(printf, 2, 3))) -bool js_inspect_header(js_inspect_builder_t *builder, const char *fmt, ...); bool js_inspect_tagged_header(js_inspect_builder_t *builder, const char *tag, size_t tag_len); bool js_inspect_object_body(js_inspect_builder_t *builder, ant_value_t obj); bool js_inspect_close(js_inspect_builder_t *builder); +__attribute__((format(printf, 2, 3))) +bool js_inspect_header(js_inspect_builder_t *builder, const char *fmt, ...); + +__attribute__((format(printf, 3, 4))) +bool js_inspect_header_for(js_inspect_builder_t *builder, ant_value_t obj, const char *fmt, ...); + +ant_value_t js_inspect_builder_result(js_inspect_builder_t *builder); ant_value_t js_define_own_prop(ant_t *js, ant_value_t obj, const char *key, size_t klen, ant_value_t v); ant_value_t js_instance_proto_from_new_target(ant_t *js, ant_value_t fallback_proto); diff --git a/src/ant.c b/src/ant.c index 4b423bb..ea79c51 100644 --- a/src/ant.c +++ b/src/ant.c @@ -1289,22 +1289,57 @@ static bool js_inspect_append_key_interned(js_inspect_builder_t *builder, const } } -bool js_inspect_header(js_inspect_builder_t *builder, const char *fmt, ...) { - va_list args; - va_start(args, fmt); +static bool __attribute__((format(printf, 3, 0))) +js_inspect_vheader_for(js_inspect_builder_t *builder, ant_value_t obj, const char *fmt, va_list args) { bool ok = js_inspect_vappendf(builder, fmt, args); - va_end(args); if (!ok) return false; + + if (is_object_type(obj)) { + int prop_count = 0; + bool inline_mode = is_small_object(builder->js, obj, &prop_count); + + if (prop_count == 0) { + if (!js_inspect_append(builder, " {}", 3)) return false; + builder->inline_mode = false; + builder->first = true; + builder->closed = true; + builder->did_indent = false; + return true; + } + + if (inline_mode) { + if (!js_inspect_append(builder, " { ", 3)) return false; + builder->inline_mode = true; + builder->first = true; + builder->closed = false; + builder->did_indent = false; + return true; + }} + if (!js_inspect_append(builder, " {\n", 3)) return false; builder->inline_mode = false; builder->first = true; builder->closed = false; builder->did_indent = false; - + return true; } +bool js_inspect_header_for(js_inspect_builder_t *builder, ant_value_t obj, const char *fmt, ...) { + va_list args; va_start(args, fmt); + bool ok = js_inspect_vheader_for(builder, obj, fmt, args); + va_end(args); + return ok; +} + +bool js_inspect_header(js_inspect_builder_t *builder, const char *fmt, ...) { + va_list args; va_start(args, fmt); + bool ok = js_inspect_vheader_for(builder, js_mkundef(), fmt, args); + va_end(args); + return ok; +} + bool js_inspect_tagged_header(js_inspect_builder_t *builder, const char *tag, size_t tag_len) { if (!js_inspect_append(builder, "Object [", 8)) return false; if (!js_inspect_append(builder, tag, tag_len)) return false; @@ -5919,46 +5954,148 @@ static ant_value_t object_enum(ant_t *js, ant_value_t obj, enum obj_enum_mode mo return mkval(T_ARR, vdata(arr)); } -static ant_value_t builtin_object_keys(ant_t *js, ant_value_t *args, int nargs) { - if (nargs == 0) return mkarr(js); - ant_value_t obj = args[0]; - if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) return mkarr(js); - - if (is_proxy(obj)) { - ant_proxy_state_t *data = get_proxy_data(obj); - if (!data) return mkarr(js); - if (data->revoked) - return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot perform 'ownKeys' on a proxy that has been revoked"); - - ant_offset_t trap_off = lkp(js, data->handler, "ownKeys", 7); - if (!trap_off) return object_enum(js, data->target, OBJ_ENUM_KEYS); - +// TODO: reduce nesting +static ant_value_t proxy_enum(ant_t *js, ant_value_t obj, enum obj_enum_mode mode) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, obj); + + ant_proxy_state_t *data = get_proxy_data(obj); + if (!data) { + GC_ROOT_RESTORE(js, root_mark); + return mkarr(js); + } + if (data->revoked) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot perform 'ownKeys' on a proxy that has been revoked"); + } + + ant_value_t keys = mkarr(js); + GC_ROOT_PIN(js, keys); + + ant_offset_t trap_off = lkp(js, data->handler, "ownKeys", 7); + if (!trap_off) { + keys = object_enum(js, data->target, OBJ_ENUM_KEYS); + } else { ant_value_t trap = propref_load(js, trap_off); uint8_t ft = vtype(trap); - if (ft != T_FUNC && ft != T_CFUNC) return object_enum(js, data->target, OBJ_ENUM_KEYS); - - ant_value_t trap_args[1] = { data->target }; - ant_value_t result = sv_vm_call(js->vm, js, trap, data->handler, trap_args, 1, NULL, false); - if (is_err(result)) return result; - if (vtype(result) != T_ARR) - return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap must return an array"); - - ant_offset_t len = get_array_length(js, result); - for (ant_offset_t i = 0; i < len; i++) { - ant_value_t ki = arr_get(js, result, i); - if (vtype(ki) != T_STR && vtype(ki) != T_SYMBOL) - return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must contain only strings or symbols"); - ant_offset_t ki_len; ant_offset_t ki_off = vstr(js, ki, &ki_len); - for (ant_offset_t j = 0; j < i; j++) { - ant_value_t kj = arr_get(js, result, j); - ant_offset_t kj_len; ant_offset_t kj_off = vstr(js, kj, &kj_len); - if (ki_len == kj_len && - memcmp((const void *)(uintptr_t)ki_off, (const void *)(uintptr_t)kj_off, ki_len) == 0) - return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must not contain duplicate entries"); + if (ft != T_FUNC && ft != T_CFUNC) { + keys = object_enum(js, data->target, OBJ_ENUM_KEYS); + } else { + ant_value_t trap_args[1] = { data->target }; + ant_value_t result = sv_vm_call(js->vm, js, trap, data->handler, trap_args, 1, NULL, false); + if (is_err(result)) { + GC_ROOT_RESTORE(js, root_mark); + return result; + } + if (vtype(result) != T_ARR) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap must return an array"); } + + ant_offset_t len = get_array_length(js, result); + for (ant_offset_t i = 0; i < len; i++) { + ant_value_t ki = arr_get(js, result, i); + if (vtype(ki) != T_STR && vtype(ki) != T_SYMBOL) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must contain only strings or symbols"); + } + for (ant_offset_t j = 0; j < i; j++) { + ant_value_t kj = arr_get(js, result, j); + if (vtype(ki) != vtype(kj)) continue; + if (vtype(ki) == T_SYMBOL) { + if (vdata(ki) == vdata(kj)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must not contain duplicate entries"); + } + continue; + } + + ant_offset_t ki_len; + ant_offset_t ki_off = vstr(js, ki, &ki_len); + ant_offset_t kj_len; + ant_offset_t kj_off = vstr(js, kj, &kj_len); + if (ki_len == kj_len && + memcmp((const void *)(uintptr_t)ki_off, (const void *)(uintptr_t)kj_off, ki_len) == 0) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr_typed(js, JS_ERR_TYPE, "ownKeys trap result must not contain duplicate entries"); + } + } + } + keys = result; } - return result; } + + ant_value_t out = mkarr(js); + GC_ROOT_PIN(js, out); + + ant_offset_t key_count = get_array_length(js, keys); + for (ant_offset_t i = 0; i < key_count; i++) { + GC_ROOT_SAVE(iter_mark, js); + + ant_value_t key = arr_get(js, keys, i); + GC_ROOT_PIN(js, key); + if (vtype(key) != T_STR) { + GC_ROOT_RESTORE(js, iter_mark); + continue; + } + + ant_value_t desc = proxy_get_own_property_descriptor(js, obj, key); + GC_ROOT_PIN(js, desc); + if (is_err(desc)) { + GC_ROOT_RESTORE(js, iter_mark); + GC_ROOT_RESTORE(js, root_mark); + return desc; + } + if (vtype(desc) == T_UNDEF) { + GC_ROOT_RESTORE(js, iter_mark); + continue; + } + + bool enumerable = false; + ant_offset_t enumerable_off = lkp(js, desc, "enumerable", 10); + if (enumerable_off != 0) { + enumerable = js_truthy(js, propref_load(js, enumerable_off)); + } + if (!enumerable) { + GC_ROOT_RESTORE(js, iter_mark); + continue; + } + + if (mode == OBJ_ENUM_KEYS) { + js_arr_push(js, out, key); + GC_ROOT_RESTORE(js, iter_mark); + continue; + } + + ant_value_t value = proxy_get_val(js, obj, key); + GC_ROOT_PIN(js, value); + if (is_err(value)) { + GC_ROOT_RESTORE(js, iter_mark); + GC_ROOT_RESTORE(js, root_mark); + return value; + } + + if (mode == OBJ_ENUM_VALUES) { + js_arr_push(js, out, value); + } else { + ant_value_t entry = map_to_entry(js, key, value); + GC_ROOT_PIN(js, entry); + js_arr_push(js, out, entry); + } + + GC_ROOT_RESTORE(js, iter_mark); + } + + GC_ROOT_RESTORE(js, root_mark); + return out; +} + +static ant_value_t builtin_object_keys(ant_t *js, ant_value_t *args, int nargs) { + if (nargs == 0) return mkarr(js); + ant_value_t obj = args[0]; + if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) return mkarr(js); + + if (is_proxy(obj)) return proxy_enum(js, obj, OBJ_ENUM_KEYS); return object_enum(js, obj, OBJ_ENUM_KEYS); } @@ -6147,6 +6284,7 @@ static ant_value_t builtin_object_values(ant_t *js, ant_value_t *args, int nargs if (nargs == 0) return mkarr(js); ant_value_t obj = args[0]; if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) return mkarr(js); + if (is_proxy(obj)) return proxy_enum(js, obj, OBJ_ENUM_VALUES); return object_enum(js, obj, OBJ_ENUM_VALUES); } @@ -6154,6 +6292,7 @@ static ant_value_t builtin_object_entries(ant_t *js, ant_value_t *args, int narg if (nargs == 0) return mkarr(js); ant_value_t obj = args[0]; if (vtype(obj) != T_OBJ && vtype(obj) != T_ARR && vtype(obj) != T_FUNC) return mkarr(js); + if (is_proxy(obj)) return proxy_enum(js, obj, OBJ_ENUM_ENTRIES); return object_enum(js, obj, OBJ_ENUM_ENTRIES); } @@ -7095,37 +7234,71 @@ static ant_value_t builtin_object_isSealed(ant_t *js, ant_value_t *args, int nar return js_false; } +typedef struct { + ant_value_t result; +} object_from_entries_iter_ctx_t; + +static iter_action_t object_from_entries_iter_cb(ant_t *js, ant_value_t entry, void *ctx, ant_value_t *out) { + object_from_entries_iter_ctx_t *state = ctx; + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, state->result); + GC_ROOT_PIN(js, entry); + + if (vtype(entry) != T_ARR && vtype(entry) != T_OBJ) { + *out = js_mkerr(js, "Object.fromEntries iterable values must be entry objects"); + GC_ROOT_RESTORE(js, root_mark); + return ITER_ERROR; + } + + ant_value_t key = arr_get(js, entry, 0); + GC_ROOT_PIN(js, key); + if (is_undefined(key)) { + *out = js_mkerr(js, "Object.fromEntries iterable values must contain a key"); + GC_ROOT_RESTORE(js, root_mark); + return ITER_ERROR; + } + + ant_value_t val = arr_get(js, entry, 1); + GC_ROOT_PIN(js, val); + + if (vtype(key) != T_STR && vtype(key) != T_SYMBOL) { + char buf[64]; + size_t n = tostr(js, key, buf, sizeof(buf)); + key = js_mkstr(js, buf, n); + GC_ROOT_PIN(js, key); + } + + js_setprop(js, state->result, key, val); + *out = state->result; + GC_ROOT_RESTORE(js, root_mark); + return ITER_CONTINUE; +} + static ant_value_t builtin_object_fromEntries(ant_t *js, ant_value_t *args, int nargs) { if (nargs == 0) return js_mkerr(js, "Object.fromEntries requires an iterable argument"); - + + GC_ROOT_SAVE(root_mark, js); ant_value_t iterable = args[0]; - uint8_t t = vtype(iterable); - - if (t != T_ARR && t != T_OBJ) { - return js_mkerr(js, "Object.fromEntries requires an iterable"); - } - - ant_value_t result = js_mkobj(js); - ant_offset_t len = get_array_length(js, iterable); - if (len == 0) return result; - - for (ant_offset_t i = 0; i < len; i++) { - ant_value_t entry = arr_get(js, iterable, i); - if (vtype(entry) != T_ARR && vtype(entry) != T_OBJ) continue; - - ant_value_t key = arr_get(js, entry, 0); - if (is_undefined(key)) continue; - ant_value_t val = arr_get(js, entry, 1); - - if (vtype(key) != T_STR) { - char buf[64]; - size_t n = tostr(js, key, buf, sizeof(buf)); - key = js_mkstr(js, buf, n); + GC_ROOT_PIN(js, iterable); + + object_from_entries_iter_ctx_t ctx = { + .result = js_mkobj(js), + }; + GC_ROOT_PIN(js, ctx.result); + + ant_value_t iter_result = iter_foreach(js, iterable, object_from_entries_iter_cb, &ctx); + if (is_err(iter_result)) { + if (vtype(iterable) == T_ARR || vtype(iterable) == T_OBJ || vtype(iterable) == T_FUNC) { + GC_ROOT_RESTORE(js, root_mark); + return iter_result; } - - js_setprop(js, result, key, val); + ant_value_t err = js_mkerr(js, "Object.fromEntries requires an iterable"); + GC_ROOT_RESTORE(js, root_mark); + return err; } - + + ant_value_t result = ctx.result; + GC_ROOT_RESTORE(js, root_mark); return result; } diff --git a/src/modules/headers.c b/src/modules/headers.c index 92e28f7..c6a6087 100644 --- a/src/modules/headers.c +++ b/src/modules/headers.c @@ -729,6 +729,61 @@ static ant_value_t js_headers_symbol_iterator(ant_t *js, ant_value_t *args, int return make_headers_iter(js, js->this_val, ITER_ENTRIES); } +static ant_value_t headers_inspect_finish(ant_t *js, ant_value_t this_obj, ant_value_t body_obj) { + ant_value_t tag_val = js_get_sym(js, this_obj, get_toStringTag_sym()); + const char *tag = vtype(tag_val) == T_STR ? js_getstr(js, tag_val, NULL) : "Headers"; + + js_inspect_builder_t builder; + if (!js_inspect_builder_init_dynamic(&builder, js, 128)) { + return js_mkerr(js, "out of memory"); + } + + bool ok = js_inspect_header_for(&builder, body_obj, "%s", tag); + if (ok) ok = js_inspect_object_body(&builder, body_obj); + if (ok) ok = js_inspect_close(&builder); + + if (!ok) { + js_inspect_builder_dispose(&builder); + return js_mkerr(js, "out of memory"); + } + + return js_inspect_builder_result(&builder); +} + +static ant_value_t headers_inspect(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); + hdr_list_t *list = get_list(this_obj); + ant_value_t out = js_mkobj(js); + + if (!list) return js_mkerr(js, "Invalid Headers object"); + + for (hdr_entry_t *e = list->head; e; e = e->next) { + ant_value_t existing = js_get(js, out, e->name); + if (vtype(existing) == T_UNDEF) { + js_set(js, out, e->name, js_mkstr(js, e->value, strlen(e->value))); + continue; + } + + size_t existing_len = 0; + const char *existing_str = js_getstr(js, existing, &existing_len); + size_t value_len = strlen(e->value); + size_t combined_len = existing_len + 2 + value_len; + char *combined = malloc(combined_len + 1); + if (!combined) return js_mkerr(js, "out of memory"); + + memcpy(combined, existing_str, existing_len); + combined[existing_len] = ','; + combined[existing_len + 1] = ' '; + memcpy(combined + existing_len + 2, e->value, value_len); + combined[combined_len] = '\0'; + + js_set(js, out, e->name, js_mkstr(js, combined, combined_len)); + free(combined); + } + + return headers_inspect_finish(js, this_obj, out); +} + static ant_value_t js_headers_ctor(ant_t *js, ant_value_t *args, int nargs) { if (vtype(js->new_target) == T_UNDEF) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers constructor requires 'new'"); @@ -1041,6 +1096,7 @@ void init_headers_module(void) { js_set(js, g_headers_proto, "getSetCookie", js_mkfun(js_headers_get_set_cookie)); js_set_sym(js, g_headers_proto, get_iterator_sym(), js_mkfun(js_headers_symbol_iterator)); + js_set_sym(js, g_headers_proto, get_inspect_sym(), js_mkfun(headers_inspect)); js_set_sym(js, g_headers_proto, get_toStringTag_sym(), js_mkstr(js, "Headers", 7)); ant_value_t ctor_obj = js_mkobj(js); diff --git a/src/modules/request.c b/src/modules/request.c index 062f09a..03d06c8 100644 --- a/src/modules/request.c +++ b/src/modules/request.c @@ -848,6 +848,65 @@ REQ_GETTER_END #undef REQ_GETTER_START #undef REQ_GETTER_END +static ant_value_t request_inspect_finish(ant_t *js, ant_value_t this_obj, ant_value_t body_obj) { + ant_value_t tag_val = js_get_sym(js, this_obj, get_toStringTag_sym()); + const char *tag = vtype(tag_val) == T_STR ? js_getstr(js, tag_val, NULL) : "Request"; + + js_inspect_builder_t builder; + if (!js_inspect_builder_init_dynamic(&builder, js, 128)) { + return js_mkerr(js, "out of memory"); + } + + bool ok = js_inspect_header_for(&builder, body_obj, "%s", tag); + if (ok) ok = js_inspect_object_body(&builder, body_obj); + if (ok) ok = js_inspect_close(&builder); + + if (!ok) { + js_inspect_builder_dispose(&builder); + return js_mkerr(js, "out of memory"); + } + + return js_inspect_builder_result(&builder); +} + +// TODO: make dry +static bool request_inspect_set( + ant_t *js, ant_value_t obj, const char *key, + ant_value_t value, ant_value_t *err_out +) { + if (is_err(value)) { + *err_out = value; + return false; + } + + js_set(js, obj, key, value); + return true; +} + +static ant_value_t request_inspect(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); + ant_value_t out = js_mkobj(js); + ant_value_t err = 0; + + if (!request_inspect_set(js, out, "method", js_req_get_method(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "url", js_req_get_url(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "headers", js_req_get_headers(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "destination", js_req_get_destination(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "referrer", js_req_get_referrer(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "referrerPolicy", js_req_get_referrer_policy(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "mode", js_req_get_mode(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "credentials", js_req_get_credentials(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "cache", js_req_get_cache(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "redirect", js_req_get_redirect(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "integrity", js_req_get_integrity(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "keepalive", js_req_get_keepalive(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "isReloadNavigation", js_req_get_is_reload_navigation(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "isHistoryNavigation", js_req_get_is_history_navigation(js, NULL, 0), &err)) return err; + if (!request_inspect_set(js, out, "signal", js_req_get_signal(js, NULL, 0), &err)) return err; + + return request_inspect_finish(js, this_obj, out); +} + static ant_value_t js_request_clone(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this = js_getthis(js); request_data_t *d = get_data(this); @@ -1453,6 +1512,7 @@ void init_request_module(void) { GETTER("bodyUsed", body_used); #undef GETTER + js_set_sym(js, g_request_proto, get_inspect_sym(), js_mkfun(request_inspect)); js_set_sym(js, g_request_proto, get_toStringTag_sym(), js_mkstr(js, "Request", 7)); ant_value_t ctor = js_make_ctor(js, js_request_ctor, g_request_proto, "Request", 7); diff --git a/src/modules/response.c b/src/modules/response.c index c282785..dd1a35e 100644 --- a/src/modules/response.c +++ b/src/modules/response.c @@ -971,6 +971,57 @@ RES_GETTER_END #undef RES_GETTER_START #undef RES_GETTER_END +static ant_value_t response_inspect_finish(ant_t *js, ant_value_t this_obj, ant_value_t body_obj) { + ant_value_t tag_val = js_get_sym(js, this_obj, get_toStringTag_sym()); + const char *tag = vtype(tag_val) == T_STR ? js_getstr(js, tag_val, NULL) : "Response"; + + js_inspect_builder_t builder; + if (!js_inspect_builder_init_dynamic(&builder, js, 128)) { + return js_mkerr(js, "out of memory"); + } + + bool ok = js_inspect_header_for(&builder, body_obj, "%s", tag); + if (ok) ok = js_inspect_object_body(&builder, body_obj); + if (ok) ok = js_inspect_close(&builder); + + if (!ok) { + js_inspect_builder_dispose(&builder); + return js_mkerr(js, "out of memory"); + } + + return js_inspect_builder_result(&builder); +} + +// TODO: make dry +static bool response_inspect_set( + ant_t *js, ant_value_t obj, const char *key, + ant_value_t value, ant_value_t *err_out +) { + if (is_err(value)) { + *err_out = value; + return false; + } + + js_set(js, obj, key, value); + return true; +} + +static ant_value_t response_inspect(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_obj = js_getthis(js); + ant_value_t out = js_mkobj(js); + ant_value_t err = 0; + + if (!response_inspect_set(js, out, "type", js_res_get_type(js, NULL, 0), &err)) return err; + if (!response_inspect_set(js, out, "url", js_res_get_url(js, NULL, 0), &err)) return err; + if (!response_inspect_set(js, out, "redirected", js_res_get_redirected(js, NULL, 0), &err)) return err; + if (!response_inspect_set(js, out, "status", js_res_get_status(js, NULL, 0), &err)) return err; + if (!response_inspect_set(js, out, "ok", js_res_get_ok(js, NULL, 0), &err)) return err; + if (!response_inspect_set(js, out, "statusText", js_res_get_status_text(js, NULL, 0), &err)) return err; + if (!response_inspect_set(js, out, "headers", js_res_get_headers(js, NULL, 0), &err)) return err; + + return response_inspect_finish(js, this_obj, out); +} + static ant_value_t js_response_clone(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this = js_getthis(js); response_data_t *d = get_data(this); @@ -1185,6 +1236,7 @@ void init_response_module(void) { GETTER("bodyUsed", body_used); #undef GETTER + js_set_sym(js, g_response_proto, get_inspect_sym(), js_mkfun(response_inspect)); js_set_sym(js, g_response_proto, get_toStringTag_sym(), js_mkstr(js, "Response", 8)); ctor = js_make_ctor(js, js_response_ctor, g_response_proto, "Response", 8); js_set(js, ctor, "error", js_mkfun(js_response_error)); diff --git a/tests/test_object_fromentries_iterables.cjs b/tests/test_object_fromentries_iterables.cjs index cd883ac..7764d93 100644 --- a/tests/test_object_fromentries_iterables.cjs +++ b/tests/test_object_fromentries_iterables.cjs @@ -55,9 +55,14 @@ ctx.set("harness", { const snapshot = Object.fromEntries(ctx.entries()); assert.strictEqual(snapshot.harness.getState().projectPath, "/tmp/project"); -assert.throws( - () => Object.fromEntries((function* () { yield 1; })()), - /entry objects/, -); +let invalidEntryError; +try { + Object.fromEntries((function* () { yield 1; })()); +} catch (error) { + invalidEntryError = error; +} + +assert.ok(invalidEntryError instanceof Error); +assert.match(String(invalidEntryError), /requires an iterable|entry object/); console.log("Object.fromEntries consumes Map iterators, generators, and custom iterables"); -- 2.51.2