Skip to content

fix(deforest): bail on producers called from super-containing member bodies (#5780) - #5791

Closed
proggeramlug wants to merge 1 commit into
mainfrom
fix/deforest-super-bail
Closed

proggeramlug wants to merge 1 commit into
mainfrom
fix/deforest-super-bail

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Fixes #5780.

What broke

Commit 5d47364 extended the deforestation Phase 3 call-site rewriter to
class member bodies (constructors, methods, getters, setters,
static_methods). When a member body also contains a super property
read or method call, injecting the deforest temporaries (let v = []
before the call) can disturb the [[HomeObject]] context that super
reads rely on at runtime. The result is super.prop resolving to
undefined/null and throwing TypeError: Cannot convert undefined or null to object (24 failing cases in the gap suite, cluster A).

Fix

Treat a class member body that contains any super expression as an
unsafe context for deforestation — the same conservative bail
already used for closure bodies (#5136).

detect.rs — 4th detection pass

When body_has_super_ref(&body) is true for a class member body, call
scan_candidate_funcrefs_in_stmts (new helper) instead of
scan_producers_used_in_closures. The new helper marks all
candidate FuncRefs in that body — not only those inside closures —
as excluded. This prevents a producer from being deforested when its
only call site lives in a super-containing member body: without this
gate the producer gains the +1 out-param while its call site keeps
the original arity, an arity-mismatch SIGSEGV of the same class as #5136.

mod.rs — Phase 3

Added if !body_has_super_ref(&body) guards around every class member
rewrite block as defence-in-depth. Detection already excludes the
affected producers, so these guards never fire in practice — but they
make the invariant explicit and protect against future detection gaps.

New helpers

body_has_super_ref / stmt_has_super_ref / expr_has_super_ref
cover all 9 Super* HIR variants:
SuperCall, SuperCallSpread, SuperMethodCall,
SuperMethodCallSpread, SuperPropertyGet, SuperPropertySet,
ObjectSuperPropertyGet, ObjectSuperPropertySet,
ObjectSuperMethodCall.

tests.rs

Added rejects_producer_called_from_super_containing_method to lock in
both the detection exclusion (producer not in the candidates map) and
the signature-preservation invariant (no synthetic out-param added).

What is NOT changed

  • Super-free class member bodies continue to have their call sites
    rewritten as before (the fix is surgical — super-free methods are
    unaffected).
  • analyze_producer, body_has_closure, all detection passes 1–3,
    and the existing deforests_producer_called_from_class_method
    regression test are unchanged.
  • No version bumps (contributor PR).

Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved class-member processing so methods containing super references are no longer rewritten in ways that could change behavior.
    • Preserved original function parameters and call behavior when a producer is only used inside such a member body.
    • Added coverage to prevent regressions for this scenario.

…bodies (#5780)

Commit 5d47364 extended Phase 3 call-site rewriting to class member
bodies (constructors, methods, accessors). When a member body contains
a super reference the [[HomeObject]] context can be disturbed by
injecting the deforest temporaries, causing super.prop reads to resolve
to undefined and throw at runtime.

Fix: treat a class member body that contains any super expression as
an unsafe context for deforestation — the same conservative bail used
for closure bodies (#5136).

* detect.rs (4th pass): for member bodies where `body_has_super_ref`
  is true, use `scan_candidate_funcrefs_in_stmts` to exclude ALL
  producers called there instead of only those inside nested closures.
  This prevents a deforested producer from gaining the +1 out-param
  while its call site in the super-containing body keeps the original
  arity (the arity-mismatch SIGSEGV class).

* mod.rs (Phase 3): add `if !body_has_super_ref(&body)` guards around
  every class member rewrite block as defence-in-depth.

* New helpers: `body_has_super_ref` / `stmt_has_super_ref` /
  `expr_has_super_ref` cover all 9 Super HIR variants; exported from
  the deforest crate for potential reuse.

* tests.rs: add `rejects_producer_called_from_super_containing_method`
  to lock in the detection and signature-preservation invariants.
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a body_has_super_ref helper that detects super references in a statement list. detect_producers now excludes producer candidates found in class member bodies containing super. Phase 3 call-site rewriting is guarded to skip those same bodies. A regression test validates the new bail-out.

Deforest super-ref bail-out

Layer / File(s) Summary
body_has_super_ref helper and updated detect_producers
crates/perry-transform/src/deforest/detect.rs
Adds body_has_super_ref and internal AST-walk helpers to detect super-related expression variants; updates detect_producers to exclude FuncRef producer candidates found inside super-containing class member bodies instead of running the closure-usage scan.
Phase 3 guard and re-export
crates/perry-transform/src/deforest/mod.rs
Re-exports body_has_super_ref; wraps each rewrite_call_sites_in_stmts call for constructor, methods, getters, setters, and static methods with a !body_has_super_ref(...) guard.
Regression test
crates/perry-transform/src/deforest/tests.rs
Adds a test asserting a producer called only inside a class method with a super reference is not detected and its signature is not modified by the pass.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PerryTS/perry#5772: Directly precedes this PR — expanded deforestation to class member bodies, which introduced the super regression this PR fixes.
  • PerryTS/perry#5788: Implements the same super-ref bail-out fix across the same deforest files with the same helper approach.

Poem

🐇 A super call hid in a class one day,
and the deforester came to rewrite away.
But now with a scan of each statement's tree,
if super is found — we let the body be!
The rabbit hops safe, no null-object in sight,
the test262 suite sleeps peacefully tonight. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR fixes the super-containing member-body regression, but linked issue #5780 also includes the new.target direct-eval over-rejection, which is not addressed here. Add the missing new.target direct-eval fix or retarget this PR so it does not claim to close #5780 until both regressions are covered.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main fix: bailing out on producers from super-containing class member bodies.
Description check ✅ Passed The description is mostly complete and clearly explains the bug, fix, changed files, and regression test, despite using custom headings.
Out of Scope Changes check ✅ Passed All changes are directly related to the super-deforestation regression and its regression test; no unrelated edits are evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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/deforest-super-bail
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/deforest-super-bail

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Duplicate of #5788 (already merged at 04:44Z) — same deforest super-bail fix (detect.rs/mod.rs, root #5772). This is an earlier slow #5780 cluster-A worker that completed after #5788 landed. Closing; the fix is already on main.

@proggeramlug
proggeramlug deleted the fix/deforest-super-bail branch June 29, 2026 06:44

@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: 1

🤖 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-transform/src/deforest/detect.rs`:
- Around line 488-495: The super detection in expr_has_super_ref only inspects
direct expression children via walk_expr_children, so nested Expr::Closure
bodies are skipped and super references inside them are missed. Update
expr_has_super_ref to recurse into closure bodies as well, using the existing
traversal entry point in detect.rs and the expr_has_super_ref helper so nested
closures are treated as super-bearing and detect_producers cannot admit unsafe
helpers.
🪄 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: bae21260-ae2f-4dde-b634-20e05ecdb0f5

📥 Commits

Reviewing files that changed from the base of the PR and between d83f935 and d3fe2a8.

📒 Files selected for processing (3)
  • crates/perry-transform/src/deforest/detect.rs
  • crates/perry-transform/src/deforest/mod.rs
  • crates/perry-transform/src/deforest/tests.rs

Comment on lines +488 to +495
let mut found = false;
walk_expr_children(e, &mut |child| {
if !found {
found = expr_has_super_ref(child);
}
});
found
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Recurse into nested closure bodies when detecting super.

Line 489 only walks expression children, but walk_expr_children does not enter Expr::Closure bodies. A method like const g = () => super.x; const v = helper(); is therefore treated as super-free, so detect_producers can still admit helper and Phase 3 can still rewrite the member body—the same regression this PR is trying to fence off.

Suggested fix
 fn expr_has_super_ref(e: &Expr) -> bool {
     if matches!(
         e,
         Expr::SuperCall(_)
             | Expr::SuperCallSpread(_)
             | Expr::SuperMethodCall { .. }
             | Expr::SuperMethodCallSpread { .. }
             | Expr::SuperPropertyGet { .. }
             | Expr::SuperPropertySet { .. }
             | Expr::ObjectSuperPropertyGet { .. }
             | Expr::ObjectSuperPropertySet { .. }
             | Expr::ObjectSuperMethodCall { .. }
     ) {
         return true;
     }
+    if let Expr::Closure { body, .. } = e {
+        if body_has_super_ref(body) {
+            return true;
+        }
+    }
     let mut found = false;
     walk_expr_children(e, &mut |child| {
         if !found {
             found = expr_has_super_ref(child);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let mut found = false;
walk_expr_children(e, &mut |child| {
if !found {
found = expr_has_super_ref(child);
}
});
found
}
if let Expr::Closure { body, .. } = e {
if body_has_super_ref(body) {
return true;
}
}
let mut found = false;
walk_expr_children(e, &mut |child| {
if !found {
found = expr_has_super_ref(child);
}
});
found
}
🤖 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-transform/src/deforest/detect.rs` around lines 488 - 495, The
super detection in expr_has_super_ref only inspects direct expression children
via walk_expr_children, so nested Expr::Closure bodies are skipped and super
references inside them are missed. Update expr_has_super_ref to recurse into
closure bodies as well, using the existing traversal entry point in detect.rs
and the expr_has_super_ref helper so nested closures are treated as
super-bearing and detect_producers cannot admit unsafe helpers.

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.

REGRESSION: super.prop throws 'Cannot convert undefined or null to object' (~24) + new.target-in-direct-eval over-rejected (6)

2 participants