Merge main

This commit is contained in:
Chris Tate
2026-10-17 23:57:47 -05:00
4 changed files with 83 additions and 10 deletions
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5913-prototype-type-error-messages.ts"
],
"diags": []
}
+32 -10
View File
@@ -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;) {
@@ -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 })));
+6
View File
@@ -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,
"5939-js-let-object-reassigned-wider.js": 1945,
"5950-tuple-predicate-methods.ts": 5990,
"5951-result-never-widening.ts": 4710,
@@ -4473,6 +4476,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,
"5939-js-let-object-reassigned-wider.js": 4436,
"5950-tuple-predicate-methods.ts": 7790,
"5951-result-never-widening.ts": 6120,