Skip to content

fix(hir,runtime): per-evaluation class bindings must carry the capture BOX, and expose .name#6500

Merged
proggeramlug merged 2 commits into
PerryTS:mainfrom
proggeramlug:fix/6497-boxed-capture-fresh-binding
Jul 17, 2026
Merged

fix(hir,runtime): per-evaluation class bindings must carry the capture BOX, and expose .name#6500
proggeramlug merged 2 commits into
PerryTS:mainfrom
proggeramlug:fix/6497-boxed-capture-fresh-binding

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #6497 — the main-tip regression from be2e23f (#6470) that currently reds conformance-smoke shards 5/7 on every PR.

What broke

#6470 routes capturing function-body class declarations through ClassExprFresh (per-evaluation captures). Two interactions:

  1. Boxed (mutated) captures lost their cell. The class field-init closure mutating a captured outer local gets a split cell (class captures are value-based; need boxed-capture mode) #5951 shared-mutable-capture pass rewrites every value read of a boxed capture into cell[0], and exempts RegisterClassCaptures args — capture sites must snapshot the whole box handle. The new ClassExprFresh.captured_args had no exemption, so the fresh class object snapshotted the cell's current value. A class that captures AND mutates a local then read undefined and dropped writes:

    function sharedCell() {
      let count = 0;
      class Counter { bump() { count++; } read() { return count; } }
      const c = new Counter(); c.bump(); c.bump(); count += 10;
      return c.read() + "," + count;   // node: "12,12" — main: "undefined,10"
    }
  2. .name read undefined on the fresh class value (makeTagged("S").name): nothing routed the read to the template's registry name. Added a name arm next to the compat(nestjs): .prototype of a capturing class expression is undefined — tslib __decorate crashes @nestjs/common module init #4949 prototype arm in the OBJECT_TYPE_CLASS property tail; explicit static name members still win.

Fix

  • shared_mutable_capture.rs: exempt ClassExprFresh.captured_args from the cell[0] rewrite exactly like RegisterClassCaptures; statics' initializer values are ordinary reads and still rewrite.
  • get_field_by_name_tail.rs: .name on heap class objects resolves via class_name_for_id, after own-field lookup, honoring key deletion.

Validation

  • test_gap_anon_shape_boxed_capture and test_gap_5952_mixin_factory_binding: byte-identical to node (both fail on clean origin/main).
  • fix(hir): per-evaluation captures for class declarations in function bodies (#6465) #6470's own repro (effect makeException, per-evaluation _tags) still passes, plus a boxed-counter variant through the same factory shape.
  • perry-runtime 1321/0 single-threaded; perry-hir unchanged (the c262_parity failure logical_property_assignment_short_circuits_the_store_4586 is pre-existing — fails identically on clean origin/main).

https://claude.ai/code/session_01M44HoYS3CAYZyagm3ZzYDG

Summary by CodeRabbit

  • New Features

    • Class expression objects now expose their class name via the .name property.
    • Heap-backed class objects now resolve .prototype and .name through consistent runtime logic.
  • Bug Fixes

    • Improved handling of captured arguments in fresh class expressions, preserving the correct snapshot array reference when required.
    • More accurate fallback behavior for .name when no own data field is present (returns undefined when marked deleted).

…e BOX, and expose .name (PerryTS#6497)

be2e23f (PerryTS#6470) routes capturing function-body class declarations through
ClassExprFresh so each factory evaluation carries its own captured
environment. Two interactions regressed (bisected in PerryTS#6497; gap tests
test_gap_anon_shape_boxed_capture and test_gap_5952_mixin_factory_binding
fail on main, which reds conformance shards 5/7 for every PR):

1. The PerryTS#5951 shared-mutable-capture pass rewrites every value read of a
   boxed capture into `cell[0]`. It exempts `RegisterClassCaptures` args
   (capture sites must snapshot the WHOLE box handle) but not the new
   `ClassExprFresh.captured_args` — so the fresh class object snapshotted
   the cell's current VALUE. Methods of a class that captures AND mutates
   a local then indexed into a number: reads came back `undefined`,
   writes were lost (`shared cell: undefined,10` vs node's `12,12`).
   Exempt captured_args exactly like RegisterClassCaptures; statics'
   initializer values are ordinary reads and still rewrite.

2. `.name` on a heap class-expression value read `undefined`: the class
   object's own fields hold only per-evaluation statics, and nothing
   routed the read to the template's registry name. Add a `name` arm next
   to the PerryTS#4949 `prototype` arm in the OBJECT_TYPE_CLASS tail — own
   static `name` members still win, and a deleted key still reads
   undefined. This also fixes `Tagged.name` for PerryTS#6470's own factory shape.

Both gap tests now match node byte-for-byte, and PerryTS#6470's original repro
(effect's makeException — per-evaluation `_tag`s) still passes, as does a
boxed-counter variant through the same factory shape. perry-runtime suite
1321/0; perry-hir suites unchanged (the pre-existing c262_parity failure
`logical_property_assignment_short_circuits_the_store_4586` fails
identically on clean origin/main).

Claude-Session: https://claude.ai/code/session_01M44HoYS3CAYZyagm3ZzYDG
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Fresh class expression lowering now preserves indexed capture handles while rewriting statics and other captures. Runtime heap class objects use shared helpers to resolve "prototype" and "name" properties from class metadata and own fields.

Changes

Fresh class behavior

Layer / File(s) Summary
Fresh class capture rewriting
crates/perry-hir/src/lower/shared_mutable_capture.rs
ClassExprFresh rewrites named and symbol statics, preserves indexed LocalGet capture handles, and rewrites other captured arguments.
Heap class property resolution
crates/perry-runtime/src/object/field_get_set/class_object_props.rs, crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs, crates/perry-runtime/src/object/field_get_set.rs
Shared helpers resolve class "prototype" values with fallback behavior and class "name" values from own fields or registry metadata; the helpers are wired into named property reads.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#6470 — Introduces the function-body ClassExprFresh capture path this lowering change adjusts.
  • PerryTS/perry#6054 — Modifies shared-mutable capture lowering in the same HIR pipeline.
  • PerryTS/perry#6409 — Changes capture argument preservation and rewriting in the same lowering logic.

Suggested reviewers: andrewtdiz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main fixes: preserving capture boxes for class bindings and exposing .name on class objects.
Description check ✅ Passed The description covers the regression, root cause, fix, and validation, though it doesn't strictly follow the template headings.
Linked Issues check ✅ Passed The changes address #6497 by preserving boxed captures for ClassExprFresh and resolving class object .name as required.
Out of Scope Changes check ✅ Passed The module split and property helper refactor are directly tied to the requested runtime fix and file-size lint constraint.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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

🧹 Nitpick comments (1)
crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs (1)

1331-1331: 📐 Maintainability & Code Quality | 🔵 Trivial

Rebuild static wrapper crates.

As per coding guidelines, when changing runtime or stdlib code, please ensure you rebuild the corresponding static wrapper crates (perry-runtime-static and perry-stdlib-static) because the runtime and stdlib crates themselves emit only rlibs and otherwise may leave stale archives linked.

🤖 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/object/field_get_set/get_field_by_name_tail.rs` at
line 1331, Rebuild the perry-runtime-static and perry-stdlib-static wrapper
crates after the runtime change near the ClassExprFresh handling, ensuring their
generated static archives reflect the updated runtime and stdlib code.

Source: Coding guidelines

🤖 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-runtime/src/object/field_get_set/get_field_by_name_tail.rs`:
- Around line 1344-1350: The `.name` deletion check in the class-name fallback
incorrectly uses shared `class_id` state, causing one instance’s deletion to
affect others. Replace `class_is_key_deleted` and its corresponding
`class_mark_key_deleted` usage for `.name` with an instance-local tombstone or
side table, and update the fallback near `class_name_for_id` to consult that
per-instance state.

---

Nitpick comments:
In `@crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs`:
- Line 1331: Rebuild the perry-runtime-static and perry-stdlib-static wrapper
crates after the runtime change near the ClassExprFresh handling, ensuring their
generated static archives reflect the updated runtime and stdlib code.
🪄 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: d2762008-4103-4248-81d7-48253a02dbd6

📥 Commits

Reviewing files that changed from the base of the PR and between 77def53 and c444a61.

📒 Files selected for processing (2)
  • crates/perry-hir/src/lower/shared_mutable_capture.rs
  • crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs

Comment thread crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs Outdated
…me_tail

The `.name` arm pushed get_field_by_name_tail.rs past the 2000-line lint
cap. Move it and the adjacent PerryTS#4949 `.prototype` arm into a
`class_object_props` sibling module; behavior unchanged (gap tests
re-verified byte-identical to node, perry-runtime 1321/0).

Claude-Session: https://claude.ai/code/session_01M44HoYS3CAYZyagm3ZzYDG

@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.

🧹 Nitpick comments (1)
crates/perry-runtime/src/object/field_get_set/class_object_props.rs (1)

1-4: 📐 Maintainability & Code Quality | 🔵 Trivial

Rebuild static wrapper crates.

As per coding guidelines, when changing runtime or stdlib code, please rebuild the corresponding static wrapper crates (perry-runtime-static and perry-stdlib-static) because the runtime and stdlib crates themselves emit only rlibs and otherwise may leave stale archives linked.

🤖 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/object/field_get_set/class_object_props.rs` around
lines 1 - 4, Rebuild the generated static wrapper crates perry-runtime-static
and perry-stdlib-static after this runtime change, ensuring their archives are
refreshed and include the updated runtime behavior.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@crates/perry-runtime/src/object/field_get_set/class_object_props.rs`:
- Around line 1-4: Rebuild the generated static wrapper crates
perry-runtime-static and perry-stdlib-static after this runtime change, ensuring
their archives are refreshed and include the updated runtime behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b7a5e00-2d11-4da5-aaf5-61f729946a2c

📥 Commits

Reviewing files that changed from the base of the PR and between c444a61 and 7c3f7a2.

📒 Files selected for processing (3)
  • crates/perry-runtime/src/object/field_get_set.rs
  • crates/perry-runtime/src/object/field_get_set/class_object_props.rs
  • crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant