Files
scriptc/tests/corpus/5915-dyn-error-string-conversions.ts
T
Chris Tate 6d80207e4c Convert errors held as any with Error.prototype.toString
Errors that reach checked-dynamic code are objects carrying the reserved
"%error" marker, with name and message either own or on their prototype.
Several string conversions handled them differently from Node:

- `"" + error`, `error + ""` and `error == "TypeError: ..."` converted
  through the plain-object path and answered "[object Object]".
- Template literals and Array join read name and message as own members
  only, so a builtin error whose name is inherited printed its message
  alone.
- String(error) used the native error, which did not see name and message
  assigned through the dynamic view.

scr_dyn_error_string implements Error.prototype.toString over the
encoding, reading both members through the prototype, and the template
renderer, Array join, ToPrimitive, ToString and the numeric conversion use
it. Assigning a string name or message through the dynamic view now also
updates the native error, as assigning stack and cause already did.
2026-10-18 04:07:51 -05:00

49 lines
1.7 KiB
TypeScript

// Errors held as `any` convert to strings through Error.prototype.toString
// in every dynamic conversion: template literals, `+` with a string on
// either side, String(), Array join, loose equality, Number() and property
// keys. The name is read through the prototype ("Error", "TypeError", a
// subclass's own name), an empty name or message leaves the other alone,
// and a subclass without its own name inherits "Error".
class NamedError extends Error {
constructor(message: string) {
super(message);
this.name = "NamedError";
}
}
class PlainSubclass extends Error {}
function show(label: string, error: any): void {
console.log(label, "|", `${error}`, "|", "" + error, "|", error + "!", "|", String(error), "|", [error, 1].join(" "));
}
show("error", new Error("plain"));
show("type", new TypeError("bad thing"));
show("range", new RangeError("too far"));
show("syntax", new SyntaxError("unexpected"));
show("named", new NamedError("mine"));
show("subclass", new PlainSubclass("inherited name"));
show("empty message", new TypeError(""));
show("no message", new Error());
const renamed: any = new Error("message only");
renamed.name = "";
show("empty name", renamed);
const changed: any = new RangeError("before");
changed.message = "after";
show("changed message", changed);
const numeric: any = new Error("42");
numeric.name = "";
const unnumbered: any = new TypeError("x");
console.log("Number", Number(numeric), Number(unnumbered), +numeric);
const type: any = new TypeError("bad thing");
console.log("loose equality", type == "TypeError: bad thing", type == "bad thing");
const keyed: any = {};
keyed[type] = 1;
console.log("key", Object.keys(keyed).join(","));