From ed817c017dbe4660e381cb9edc079d073ecdd3ac Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 17 Oct 2026 22:50:45 -0500 Subject: [PATCH] Name the rejected value in the Object prototype TypeError Object.setPrototypeOf and Object.create threw "Object prototype may only be an Object or null" without the value Node appends. The message now ends with the value's string form, as in Node: "...: 7", "...: undefined", "...: Symbol(tag)". A prototype or receiver that is a valid object of a kind these functions do not handle yet (a function or Map prototype for Object.create, a Map prototype or receiver for Object.setPrototypeOf) threw the same TypeError. It now throws an Error that says the operation is not supported for that value. --- ...5913-prototype-type-error-messages.ts.json | 6 +++ packages/runtime/src/scr_json.c | 42 ++++++++++++++----- .../5913-prototype-type-error-messages.ts | 39 +++++++++++++++++ tests/harness/corpus-costs.json | 6 +++ 4 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5913-prototype-type-error-messages.ts.json create mode 100644 tests/corpus/5913-prototype-type-error-messages.ts diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5913-prototype-type-error-messages.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5913-prototype-type-error-messages.ts.json new file mode 100644 index 000000000..f7e3d10b9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5913-prototype-type-error-messages.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5913-prototype-type-error-messages.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cdfb3d0f9..958112b66 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1729,13 +1729,40 @@ static bool scr_dyn_typed_ref_prototype(const ScrDyn *value) { return view && (view->kind == SCR_DYN_OBJ || view->kind == SCR_DYN_ARR); } +/* True when `prototype` is null or an object or function, the values + * Object.create and Object.setPrototypeOf accept. Otherwise throws Node's + * TypeError, whose message ends with the value's string form. */ +static bool scr_dyn_prototype_valid(const ScrDyn *prototype) { + if (scr_dyn_is_object(prototype) || scr_dyn_is_callable(prototype)) return true; + if (scr_exc_pending()) return false; + ScrStr *text = prototype->kind == SCR_DYN_SYMBOL + ? prototype->v.symbol.render(prototype->v.symbol.value) : scr_dyn_string_coerce_js(prototype); + if (!text) return false; + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "Object prototype may only be an Object or null: "); + scr_jb_put_str(&b, text); + scr_str_release(text); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b)); + return false; +} + +/* A valid receiver or prototype whose kind this operation does not handle. */ +static void scr_dyn_prototype_unsupported(const char *operation) { + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, operation); + scr_jb_puts(&b, " with this value is not supported yet"); + scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b)); +} + ScrDyn *scr_dyn_obj_create(ScrDyn *prototype) { + if (!scr_dyn_prototype_valid(prototype)) return NULL; if (prototype != scr_builtin_object_prototype && scr_dyn_class_reflection_fence(prototype)) return NULL; bool typed = scr_dyn_typed_ref_prototype(prototype); if (scr_exc_pending()) return NULL; if (prototype->kind != SCR_DYN_OBJ && prototype->kind != SCR_DYN_ARR && prototype->kind != SCR_DYN_NULL && !typed) { - static const char message[] = "Object prototype may only be an Object or null"; - scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + scr_dyn_prototype_unsupported("Object.create"); return NULL; } ScrDyn *object = prototype->kind == SCR_DYN_NULL ? scr_dyn_new_obj_null_proto() : scr_dyn_new_obj(); @@ -1786,12 +1813,7 @@ ScrDyn *scr_dyn_set_prototype(ScrDyn *object, ScrDyn *prototype) { * only check. */ if (object->kind == SCR_DYN_BOOL || object->kind == SCR_DYN_NUM || object->kind == SCR_DYN_STR || object->kind == SCR_DYN_BIGINT || object->kind == SCR_DYN_SYMBOL) { - bool valid = scr_dyn_is_object(prototype) || scr_dyn_is_callable(prototype); - if (scr_exc_pending()) return NULL; - if (valid) return scr_dyn_retain(object); - static const char message[] = "Object prototype may only be an Object or null"; - scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); - return NULL; + return scr_dyn_prototype_valid(prototype) ? scr_dyn_retain(object) : NULL; } if (scr_dyn_class_reflection_fence(object) || scr_dyn_class_reflection_fence(prototype)) return NULL; if (object->kind == SCR_DYN_TYPED_REF) { @@ -1821,12 +1843,12 @@ ScrDyn *scr_dyn_set_prototype(ScrDyn *object, ScrDyn *prototype) { scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); return NULL; } + if (!scr_dyn_prototype_valid(prototype)) return NULL; bool typed = scr_dyn_typed_ref_prototype(prototype); if (scr_exc_pending()) return NULL; if ((object->kind != SCR_DYN_OBJ && object->kind != SCR_DYN_ARR) || (prototype->kind != SCR_DYN_OBJ && prototype->kind != SCR_DYN_ARR && prototype->kind != SCR_DYN_FUNC && prototype->kind != SCR_DYN_NULL && !typed)) { - static const char message[] = "Object prototype may only be an Object or null"; - scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + scr_dyn_prototype_unsupported("Object.setPrototypeOf"); return NULL; } for (const ScrDyn *current = scr_dyn_chain_link(prototype); current;) { diff --git a/tests/corpus/5913-prototype-type-error-messages.ts b/tests/corpus/5913-prototype-type-error-messages.ts new file mode 100644 index 000000000..cb78e4cb1 --- /dev/null +++ b/tests/corpus/5913-prototype-type-error-messages.ts @@ -0,0 +1,39 @@ +// Object.setPrototypeOf and Object.create reject a prototype that is +// neither an object nor null with Node's TypeError, whose message ends with +// the value's string form: numbers as String(n) (-0 as 0), bigints without +// the n suffix, symbols as Symbol(description), and the empty string as +// nothing. + +function message(run: () => unknown): string { + try { + run(); + return "no error"; + } catch (error) { + return `${error instanceof TypeError} ${(error as Error).message}`; + } +} + +const prototypes: [string, unknown][] = [ + ["number", 7], + ["fraction", 1.5], + ["negative zero", -0], + ["nan", NaN], + ["string", "proto"], + ["empty string", ""], + ["boolean", true], + ["undefined", undefined], + ["bigint", 10n], + ["symbol", Symbol("tag")], +]; + +for (const [label, prototype] of prototypes) { + const invalid = prototype as object | null; + const target: unknown = JSON.parse('{"own":1}'); + console.log(label, "object receiver |", message(() => Object.setPrototypeOf(target, invalid))); + console.log(label, "primitive receiver |", message(() => Object.setPrototypeOf(5, invalid))); + console.log(label, "create |", message(() => Object.create(invalid))); +} + +const valid: unknown = JSON.parse('{"own":1}'); +console.log("null prototype |", message(() => Object.setPrototypeOf(valid, null))); +console.log("object prototype |", message(() => Object.create({ inherited: true }))); diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index ecd7f5d9b..a653b9bae 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -1841,6 +1841,9 @@ "5648-dns-lookup-forms.ts": 2992, "5649-dns-service-servers.ts": 2712, "5650-dns-lookup-errors-untyped.cjs": 2822, + "5910-set-prototype-of-primitive-object-kinds.ts": 2719, + "5911-dyn-class-to-primitive.ts": 2850, + "5913-prototype-type-error-messages.ts": 2484, "5970-net-connect-host-name.ts": 2400, "5971-buffer-chunks-to-string.ts": 2700, "5972-net-set-encoding-listeners.ts": 2550, @@ -4468,6 +4471,9 @@ "5648-dns-lookup-forms.ts": 3416, "5649-dns-service-servers.ts": 4916, "5650-dns-lookup-errors-untyped.cjs": 3889, + "5910-set-prototype-of-primitive-object-kinds.ts": 5033, + "5911-dyn-class-to-primitive.ts": 4732, + "5913-prototype-type-error-messages.ts": 4068, "5970-net-connect-host-name.ts": 2800, "5971-buffer-chunks-to-string.ts": 3100, "5972-net-set-encoding-listeners.ts": 3000,