Repository navigation
runtime: honor cause options for Error subclasses and AggregateError #2836
Description
Summary
Perry partially supports new Error(message, { cause }), but the lowering only recognizes a literal object option on the base Error constructor. Node applies the ES2022 cause option across native Error subclasses and AggregateError, and it reads the cause from ordinary runtime option objects, not only AST object literals.
This leaves TypeError, RangeError, ReferenceError, SyntaxError, AggregateError, and new Error(message, optionsVariable) without the expected .cause value.
Node behavior
A local Node v25.9.0 probe shows:
const cause = { why: 1 };
const opts = { cause };
console.log(new Error("outer", opts).cause === cause); // true
console.log(new RangeError("range", { cause: "c" }).cause); // "c"
console.log(new SyntaxError("syntax", { cause: 123 }).cause); // 123
const agg = new AggregateError([1, 2], "many", { cause });
console.log(agg.name, agg.message, agg.errors, agg.cause === cause);
// AggregateError many [1, 2] trueThe .cause property is non-enumerable, but it is directly readable.
Current Perry evidence
crates/perry-runtime/src/error.rs::ErrorHeaderalready has acauseslot, andjs_error_new_with_cause(message, cause)stores it for base Error objects.crates/perry-hir/src/lower/expr_new.rsonly enters the two-argument cause-detection branch whenclass_name == "Error".- That branch only recognizes
{ cause: ... }or shorthand{ cause }at the AST object-literal level. If the options object is held in a variable or produced dynamically, lowering evaluates it for side effects and emits plainExpr::ErrorNewwith no cause. - The same file lowers
TypeError,RangeError,ReferenceError, andSyntaxErrorby taking only the first argument and emittingExpr::TypeErrorNew/RangeErrorNew/ReferenceErrorNew/SyntaxErrorNew, so their second options argument is ignored. AggregateErrorlowering reads only the first two constructor args (errors,message) intoExpr::AggregateErrorNew { errors, message }; there is no options/cause field in that IR variant andcrates/perry-runtime/src/error.rs::js_aggregateerror_new()only sets.errors.- PR Inspect Error cause details like Node #2151 improved util.inspect display for existing error causes, but it does not make constructors populate
.causefor these missing forms.
Suggested test surface
Add node-suite tests for:
const cause = { why: 1 };
const opts = { cause };
console.log("error dynamic:", new Error("outer", opts).cause === cause);
console.log("type cause:", new TypeError("bad", { cause }).cause === cause);
console.log("range cause:", new RangeError("bad", { cause: "r" }).cause);
console.log("syntax cause:", new SyntaxError("bad", { cause: 123 }).cause);
const agg = new AggregateError([1, 2], "many", { cause });
console.log("agg cause:", agg.cause === cause, JSON.stringify(agg.errors));Expected output should show each constructor preserving the cause option.
Scope / non-goals
This issue is specifically about constructor option handling for .cause. It does not cover stack-frame formatting / source mapping (#1278), or AggregateError's broader iterable validation semantics.
Activity
fix(error): cause options, AggregateError iterables, Error.isError/st…