Skip to content

fix(error): forward ES2022 cause through super(message, options) (#5127)#5158

Merged
proggeramlug merged 2 commits into
mainfrom
fix/5127-error-cause-super
Jun 15, 2026
Merged

fix(error): forward ES2022 cause through super(message, options) (#5127)#5158
proggeramlug merged 2 commits into
mainfrom
fix/5127-error-cause-super

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Fixes #5127.

Problem

An Error subclass that forwards options via super(message, options) lost the ES2022 cause:

class AppError extends Error {
  constructor(msg: string, opts?: ErrorOptions) { super(msg, opts); this.name = "AppError"; }
}
const e = new AppError("high-level", { cause: new TypeError("low-level") });
console.log(e.cause); // undefined  (Node: the TypeError)

A plain new Error(msg, { cause }) (no subclass) already worked, so the option was dropped specifically when passed up through super(...).

Root cause

The Error-like super(...) codegen arm (this_super_call.rs) only assigned this.message = args[0] and this.name = <parent>. It ignored args[1] (the options object), so cause never reached the subclass instance (which is a generic heap object, not an ErrorHeader).

Fix

  • New runtime helper js_error_apply_cause_to_object(this, options) (error.rs) — mirrors the existing apply_cause_from_options but installs a non-enumerable own cause property on the instance object, matching Node's InstallErrorCause. No-op for non-object options.
  • The Error-like super(...) arm now calls it whenever a second argument is forwarded.

Verification

Output matches Node exactly, including cause being a non-enumerable own property, plain-Error cause still working, and no-options ⇒ no cause:

high-level low-level
AppError true
false true        # Object.keys excludes cause; hasOwnProperty true
42
undefined

New regression test: crates/perry/tests/issue_5127_error_cause_super.rs (green).

No changelog/version bump per maintainer's release-at-merge workflow.

Summary by CodeRabbit

  • New Features

    • Error subclasses now forward ES2022 { cause } from super(message, options) into the created error instance, supporting standard error-chaining patterns.
  • Bug Fixes

    • Ensures cause is applied correctly when using super(message, options) (including the expected non-enumerable behavior on instances).
  • Tests

    • Added a regression test covering super(message, options) propagation for custom Error subclasses, plus coverage for plain Error and missing options.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7242a36a-4ed5-417e-8d06-a4897dc1c158

📥 Commits

Reviewing files that changed from the base of the PR and between a081213 and 44c905e.

📒 Files selected for processing (4)
  • crates/perry-codegen/src/expr/this_super_call.rs
  • crates/perry-codegen/src/runtime_decls/objects.rs
  • crates/perry-runtime/src/error.rs
  • crates/perry/tests/issue_5127_error_cause_super.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/perry/tests/issue_5127_error_cause_super.rs
  • crates/perry-runtime/src/error.rs
  • crates/perry-codegen/src/runtime_decls/objects.rs
  • crates/perry-codegen/src/expr/this_super_call.rs

📝 Walkthrough

Walkthrough

Fixes issue #5127 by adding a new runtime FFI function js_error_apply_cause_to_object that reads cause from an ES2022 options object and installs it as a non-enumerable property on user-constructed Error subclass instances. The function is declared in the codegen runtime module and called from the Error-like super(message, options) code path. A regression test verifies the end-to-end behavior.

Changes

ES2022 cause forwarding in Error subclass super()

Layer / File(s) Summary
Runtime FFI function and codegen wiring
crates/perry-runtime/src/error.rs, crates/perry-codegen/src/runtime_decls/objects.rs, crates/perry-codegen/src/expr/this_super_call.rs
Adds js_error_apply_cause_to_object (converts options f64 to a JS value, guards on object type, reads options.cause, and calls js_object_set_field_by_name_nonenum when cause is present), declares it in the codegen module as VOID(I64, DOUBLE), and invokes it from the Error-like super() path when a second argument exists.
Regression test for issue #5127
crates/perry/tests/issue_5127_error_cause_super.rs
Compiles and runs a TypeScript Error subclass that forwards cause via super(msg, opts), asserting cause is preserved, non-subclass Error still works, and missing options yield undefined.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A cause got lost in the super chain one day,
Passed up to parent but it slipped away.
Now a new FFI friend installs it just right,
Non-enumerable, tidy, tucked in out of sight.
The regression test bunnies all hop with glee —
ES2022 errors fixed, as they should be! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary fix: forwarding ES2022 cause through super(message, options) for Error subclasses, directly addressing issue #5127.
Description check ✅ Passed The PR description comprehensively documents the problem, root cause, fix details, and verification approach with expected output matching Node. All critical context is provided.
Linked Issues check ✅ Passed The PR implementation directly addresses #5127 by adding runtime helper js_error_apply_cause_to_object and updating Error-like super(...) codegen to forward the options argument, properly preserving the cause property on Error subclass instances.
Out of Scope Changes check ✅ Passed All changes are narrowly scoped to fixing issue #5127: new runtime helper, codegen update for super(...) handling, declaration in objects.rs, and regression test. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/5127-error-cause-super

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/perry/tests/issue_5127_error_cause_super.rs (1)

60-76: ⚡ Quick win

Add a multi-hop subclass case to guard the still-separate super() code path

Please extend this regression with Leaf extends Mid extends Error and super(msg, opts) assertion. That path is lowered differently and should be covered explicitly to prevent silent regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/tests/issue_5127_error_cause_super.rs` around lines 60 - 76, The
current test in issue_5127_error_cause_super.rs only covers single-level Error
subclasses (AppError extends Error), but there is a separate code path for
multi-level inheritance chains that needs explicit test coverage. Add a test
case with a two-level inheritance hierarchy (Leaf extends Mid extends Error)
where the Leaf constructor invokes super(msg, opts) to verify that the cause
option is properly forwarded through multiple levels of inheritance, ensuring
the regression guard covers this distinct lowering path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-codegen/src/expr/this_super_call.rs`:
- Around line 603-613: The cause forwarding fix for the Error-like branch needs
to be mirrored in the fallback Error-chain branch to handle multi-level
inheritance chains. Locate the fallback Error-chain branch handling code and
apply the same cause forwarding logic: add a conditional check for args[1]
existence and call js_error_apply_cause_to_object with the instance handle and
the options value, using the same approach as in the direct Error-like branch
shown in the diff (checking opts_val from lowered_args.get(1), creating a block,
and calling js_error_apply_cause_to_object with the appropriate parameters).

In `@crates/perry-runtime/src/error.rs`:
- Around line 517-529: The function js_error_apply_cause_to_object is a new FFI
entrypoint that is not protected from dead-code elimination during optimized
builds. Add the symbol name js_error_apply_cause_to_object to the existing
#[used] keepalive anchor attribute in this file (typically found as a string
concatenation or list near the top or bottom of the file) that is used to
preserve FFI symbols during LTO optimization. This ensures the symbol remains
available when the code is compiled with optimizations.

---

Nitpick comments:
In `@crates/perry/tests/issue_5127_error_cause_super.rs`:
- Around line 60-76: The current test in issue_5127_error_cause_super.rs only
covers single-level Error subclasses (AppError extends Error), but there is a
separate code path for multi-level inheritance chains that needs explicit test
coverage. Add a test case with a two-level inheritance hierarchy (Leaf extends
Mid extends Error) where the Leaf constructor invokes super(msg, opts) to verify
that the cause option is properly forwarded through multiple levels of
inheritance, ensuring the regression guard covers this distinct lowering path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c2a7692d-f884-4058-9e34-feb06301c1f2

📥 Commits

Reviewing files that changed from the base of the PR and between c1da78e and a081213.

📒 Files selected for processing (4)
  • crates/perry-codegen/src/expr/this_super_call.rs
  • crates/perry-codegen/src/runtime_decls/objects.rs
  • crates/perry-runtime/src/error.rs
  • crates/perry/tests/issue_5127_error_cause_super.rs

Comment on lines +603 to +613
// #5127: `super(message, options)` must forward the
// ES2022 `cause` option. The instance is a generic
// object, so install a non-enumerable `cause`
// property from args[1] when present.
if let Some(opts_val) = lowered_args.get(1) {
let blk = ctx.block();
blk.call_void(
"js_error_apply_cause_to_object",
&[(I64, &this_handle), (DOUBLE, opts_val)],
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cause forwarding is only fixed for direct extends Error, not multi-hop chains

This new call covers the direct Error-like branch, but the fallback Error-chain branch (around Line 726-Line 804) still omits args[1] forwarding. class Mid extends Error {}; class Leaf extends Mid { constructor(m,o){ super(m,o) } } will still lose cause.

Suggested fix (mirror the same forwarding in the fallback Error-chain branch)
@@
                     blk.call_void(
                         "js_object_set_field_by_name",
                         &[
                             (I64, &this_handle),
                             (I64, &name_key_raw),
                             (DOUBLE, &name_val_box),
                         ],
                     );
+                    if let Some(opts_val) = lowered_args.get(1) {
+                        let blk = ctx.block();
+                        blk.call_void(
+                            "js_error_apply_cause_to_object",
+                            &[(I64, &this_handle), (DOUBLE, opts_val)],
+                        );
+                    }
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/expr/this_super_call.rs` around lines 603 - 613, The
cause forwarding fix for the Error-like branch needs to be mirrored in the
fallback Error-chain branch to handle multi-level inheritance chains. Locate the
fallback Error-chain branch handling code and apply the same cause forwarding
logic: add a conditional check for args[1] existence and call
js_error_apply_cause_to_object with the instance handle and the options value,
using the same approach as in the direct Error-like branch shown in the diff
(checking opts_val from lowered_args.get(1), creating a block, and calling
js_error_apply_cause_to_object with the appropriate parameters).

Comment on lines +517 to +529
#[no_mangle]
pub extern "C" fn js_error_apply_cause_to_object(obj: *mut crate::object::ObjectHeader, options: f64) {
let opts = crate::value::JSValue::from_bits(options.to_bits());
if !opts.is_pointer() {
return;
}
let key = js_string_from_bytes(b"cause".as_ptr(), 5);
let key_f64 = crate::value::js_nanbox_string(key as i64);
let cause = crate::value::js_dyn_index_get(options, key_f64);
if cause.to_bits() != TAG_UNDEFINED_BITS {
crate::object::js_object_set_field_by_name_nonenum(obj, key as *const StringHeader, cause);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing keepalive anchor for new codegen-only FFI symbol

js_error_apply_cause_to_object is a generated-code entrypoint, but it is not added to the #[used] keepalive anchors used in this file for auto-optimize/LTO dead-strip protection. This can make the symbol disappear in optimized links.

Suggested fix
@@
 #[used]
 static KEEP_ERROR_IS_ERROR: extern "C" fn(f64) -> f64 = js_error_is_error;
+#[used]
+static KEEP_ERROR_APPLY_CAUSE_TO_OBJECT: extern "C" fn(
+    *mut crate::object::ObjectHeader,
+    f64,
+) = js_error_apply_cause_to_object;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/error.rs` around lines 517 - 529, The function
js_error_apply_cause_to_object is a new FFI entrypoint that is not protected
from dead-code elimination during optimized builds. Add the symbol name
js_error_apply_cause_to_object to the existing #[used] keepalive anchor
attribute in this file (typically found as a string concatenation or list near
the top or bottom of the file) that is used to preserve FFI symbols during LTO
optimization. This ensures the symbol remains available when the code is
compiled with optimizations.

Ralph Küpper added 2 commits June 15, 2026 07:37
…rror subclasses (#5127)

An Error subclass that forwarded options via super(message, options) lost
the cause — this.cause was undefined afterward, even though plain
new Error(msg, { cause }) worked.

Root cause: the Error-like super(...) codegen arm only assigned
this.message = args[0] and this.name = <parent>; it ignored args[1] (the
options object), so the cause never reached the (generic-object) subclass
instance.

Fix: add js_error_apply_cause_to_object(this, options) — mirrors the
existing apply_cause_from_options but installs a non-enumerable own cause
property on the instance object (matching Node's InstallErrorCause). The
super-call arm now calls it whenever a second argument is forwarded.
@proggeramlug proggeramlug force-pushed the fix/5127-error-cause-super branch from a081213 to 44c905e Compare June 15, 2026 05:37
@proggeramlug proggeramlug merged commit 48787f1 into main Jun 15, 2026
15 checks passed
@proggeramlug proggeramlug deleted the fix/5127-error-cause-super branch June 15, 2026 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error subclass loses cause when forwarding options via super(message, options)

1 participant