Engine bug: Error.prototype.toString is missing across the entire Error hierarchy
ES2026 §20.5.3.4 defines Error.prototype.toString as a method returning name + ": " + message (with edge cases for empty name/message). It is inherited by all native Error subclasses and by user class extends Error. In Goccia it is undefined everywhere.
Reproduce
./build.pas loader
cat > /tmp/probe.js <<'EOJS'
const e = new Error("hi");
console.log("typeof e.toString:", typeof e.toString); // expected "function", actual "undefined"
console.log("typeof Error.prototype.toString:", typeof Error.prototype.toString);
const t = new TypeError("nope");
console.log("typeof t.toString:", typeof t.toString); // expected "function", actual "undefined"
class MyErr extends Error {}
const m = new MyErr("custom");
console.log("typeof m.toString:", typeof m.toString); // expected "function", actual "undefined"
// Spec-correct String(error) result:
console.log("String(e):", String(e)); // expected "Error: hi"
EOJS
./build/GocciaScriptLoader /tmp/probe.js
./build/GocciaScriptLoader --mode=bytecode /tmp/probe.js
Both modes print undefined for all four typeof checks. String(e) post-#527 throws TypeError: Cannot convert object to primitive value because ToPrimitive(string) falls through to valueOf/toString and finds neither.
Spec context
ES2026 §20.5.3.4 Error.prototype.toString:
1. Let O be the this value.
2. If O is not an Object, throw a TypeError exception.
3. Let name be ? Get(O, "name").
4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name).
5. Let msg be ? Get(O, "message").
6. If msg is undefined, set msg to ""; otherwise set msg to ? ToString(msg).
7. If name is the empty String, return msg.
8. If msg is the empty String, return name.
9. Return the string-concatenation of name, ": ", and msg.
Defined on Error.prototype only — all NativeError subclasses inherit it through their prototype chain.
Likely fix area
source/units/Goccia.Builtins.Globals.pas — when FErrorProto is initialized (around the PROP_MESSAGE/PROP_NAME defines), also define PROP_TO_STRING as a method that runs the §20.5.3.4 algorithm. The other Error subclass prototypes (FTypeErrorProto, FRangeErrorProto, etc.) inherit from FErrorProto, so the method becomes available everywhere via the prototype chain — no per-subclass duplication.
Tests
Add to tests/built-ins/Error/prototype/ (or create toString.js if not present):
test("Error.prototype.toString returns name: message", () => {
expect(new Error("hi").toString()).toBe("Error: hi");
});
test("TypeError.prototype.toString returns name: message", () => {
expect(new TypeError("nope").toString()).toBe("TypeError: nope");
});
test("Error.prototype.toString returns just name when message is empty", () => {
expect(new Error().toString()).toBe("Error");
expect(new Error("").toString()).toBe("Error");
});
test("Error.prototype.toString returns just message when name is empty", () => {
const e = new Error("foo");
e.name = "";
expect(e.toString()).toBe("foo");
});
test("user class extending Error inherits prototype.toString", () => {
class MyErr extends Error {}
expect(new MyErr("x").toString()).toBe("Error: x");
});
test("String(error) uses prototype.toString", () => {
expect(String(new Error("hi"))).toBe("Error: hi");
});
User impact
Surfaced by
#527 (ES2026 ToStringLiteral refactor) — the spec ToString path now actually invokes obj.toString(), which is undefined for Error instances.
Related
Engine bug:
Error.prototype.toStringis missing across the entire Error hierarchyES2026 §20.5.3.4 defines
Error.prototype.toStringas a method returningname + ": " + message(with edge cases for emptyname/message). It is inherited by all native Error subclasses and by userclass extends Error. In Goccia it isundefinedeverywhere.Reproduce
Both modes print
undefinedfor all fourtypeofchecks.String(e)post-#527 throwsTypeError: Cannot convert object to primitive valuebecauseToPrimitive(string)falls through tovalueOf/toStringand finds neither.Spec context
ES2026 §20.5.3.4 Error.prototype.toString:
Defined on
Error.prototypeonly — all NativeError subclasses inherit it through their prototype chain.Likely fix area
source/units/Goccia.Builtins.Globals.pas— whenFErrorProtois initialized (around thePROP_MESSAGE/PROP_NAMEdefines), also definePROP_TO_STRINGas a method that runs the §20.5.3.4 algorithm. The other Error subclass prototypes (FTypeErrorProto,FRangeErrorProto, etc.) inherit fromFErrorProto, so the method becomes available everywhere via the prototype chain — no per-subclass duplication.Tests
Add to
tests/built-ins/Error/prototype/(or createtoString.jsif not present):User impact
String(error)and template-literal${error}raise spuriousTypeErrors (post-Make ToStringLiteral ES2026-compliant; add ToPropertyKey #527ToStringLiteralrefactor).assert.sameValue(String(err), "Error: …")patterns in test262 fail.toThrow(messageString)matcher cannot reliably surface assertion failures because the fallback string-matching path stringifies the caught error viae.ToStringLiteraland trips this gap (forced Make ToStringLiteral ES2026-compliant; add ToPropertyKey #527 to use side-effect counters instead of message-string matching in the new@@-dispatch ordering tests).TGocciaObjectValue.ToStringLiteralreturned the hardcoded[object Object]debug format.Surfaced by
#527 (ES2026 ToStringLiteral refactor) — the spec ToString path now actually invokes
obj.toString(), which is undefined for Error instances.Related
Error.prototype.constructoris undefined (same shape of bug, different missing property on the same prototype). Likely same fix-PR surface area.String(obj)usertoString()invocation (which now correctly attempts to call the missing method).console.logon prototype-less objects throws; the Error.prototype gap is the most common in-practice trigger of console.log on prototype-less object throws TypeError #536.