Skip to content

refactor(transform/hir): de-duplicate HIR-walking helpers (#5293)#5305

Merged
proggeramlug merged 2 commits into
mainfrom
worktree-refactor-hir-walkers-5293
Jun 17, 2026
Merged

refactor(transform/hir): de-duplicate HIR-walking helpers (#5293)#5305
proggeramlug merged 2 commits into
mainfrom
worktree-refactor-hir-walkers-5293

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Closes #5293.

An external audit flagged several HIR/IR-walking helpers copy-pasted across files. Each copy must be hand-updated whenever a new Expr/HIR node variant is added; a missed copy is a silent miscompile, not a build error — the same bug-class behind #5143 (FuncId-collision from a scan missing class field-init closures).

What changed

1. compute_max_local_id / compute_max_func_id (perry-transform)
Deleted the copies in finally_inline, async_to_generator (including compute_max_func_id_module), state_desugar, and unroll, plus their private scan_* helpers. All callers now use the canonical, already-pub, exhaustive-walk_expr_children-backed implementations in generator::id_scan.

Before routing, the canonical scanners were made a true superset of every copy so no caller loses coverage:

  • scan switch case.test expressions (some copies did, the canonical didn't),
  • scan module-global initializers (async_to_generator's copy did, the canonical didn't).

A higher max id is always safe — fresh ids just start higher; a missed id is what collides.

2. class_computed_member_registration_expr (perry-hir)
Dropped the 3 byte-identical copies in lower/{lower_expr,module_decl,stmt}.rs and re-exported the canonical lower_decl::class_computed one through lower_decl::mod (the lower/* files already pull it in via use super::*).

3. collect_assigned_locals_expr (perry-hir)
Replaced the ~1120-line hand-rolled per-variant descent — which ended in a _ => {} catch-all that silently skipped any newly-added variant nesting a LocalSet/Update — with the exhaustive walk_expr_children for child descent. Only the 7 assignment-recording arms (LocalSet, Update, array push/splice/copyWithin/reverse, SetAdd) and the intentional don't-recurse-into-closures arm remain custom. Net: same recorded assignments on every currently-handled variant, plus coverage for variants the catch-all used to drop.

The optional codegen for_each_body_in_module extraction (item 4 in the issue) is not included here — left for a follow-up to keep this diff a pure mechanical dedup.

Validation

Pure refactor, no behavior change. Net −2176 lines.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Reduced the risk of LocalId/FuncId allocation collisions by expanding max-id scanning to cover additional expression locations (including global initializers and switch case tests) and by using shared canonical ID computation helpers across transformation passes.
  • Refactor
    • Consolidated duplicate “max id”/scan logic across multiple transformation passes.
    • Streamlined assignment-tracking and reorganized class computed-member lowering helpers for clearer, more reliable analysis.

Collapse copy-pasted HIR/IR walkers whose copies must each be hand-updated
when a new Expr variant is added — a missed copy is a silent miscompile,
not a build error (the same bug-class as #5143).

- compute_max_local_id / compute_max_func_id: delete the copies in
  finally_inline, async_to_generator (incl. compute_max_func_id_module),
  state_desugar, and unroll; route all callers through the canonical,
  already-`pub`, exhaustive-walker-backed implementations in
  generator::id_scan. First the canonical scanners are made a true
  superset of every copy: they now also scan switch `case.test` exprs and
  module-global initializers (a higher max id is always safe — fresh ids
  just start higher; a missed id collides).
- class_computed_member_registration_expr: drop the 3 byte-identical
  copies in lower/{lower_expr,module_decl,stmt}.rs and re-export the
  canonical lower_decl::class_computed one through lower_decl::mod.
- collect_assigned_locals_expr: replace the ~1120-line hand-rolled
  per-variant descent (ending in a `_ => {}` catch-all that silently
  skipped newly-added variants) with the exhaustive walk_expr_children
  for child descent, keeping only the 7 assignment-recording arms and the
  intentional don't-recurse-into-closures arm custom.

Pure refactor, no behavior change. Verified: perry-hir (158) and
perry-transform (34) lib tests pass in release; an end-to-end smoke
program covering switch case-tests, module-global closures, async,
generators, finally, computed class members, and unrolled loops matches
`node --experimental-strip-types` byte-for-byte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 17, 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: fe6a4922-15ee-4a1c-834d-2baf36a932ca

📥 Commits

Reviewing files that changed from the base of the PR and between 6a76aa0 and 1c7ba04.

📒 Files selected for processing (1)
  • crates/perry-hir/src/analysis.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/perry-hir/src/analysis.rs

📝 Walkthrough

Walkthrough

Two independent deduplication refactors from issue #5293: (1) in perry-hir, three copy-pasted class_computed_member_registration_expr helpers are removed from lower/ sub-modules and routed through a single pub(crate) re-export, and collect_assigned_locals_expr is rewritten from ~1000 lines of exhaustive match to a slim assignment-focused match plus walk_expr_children; (2) in perry-transform, duplicated compute_max_local_id/compute_max_func_id implementations are deleted from four passes and replaced with imports of the canonical generator/id_scan helpers, which are also extended to scan global.init and Stmt::Switch case-test expressions.

Changes

HIR: class_computed helper consolidation and analysis refactor

Layer / File(s) Summary
Consolidate class_computed_member_registration_expr to single canonical source
crates/perry-hir/src/lower_decl/mod.rs, crates/perry-hir/src/lower/lower_expr.rs, crates/perry-hir/src/lower/module_decl.rs, crates/perry-hir/src/lower/stmt.rs
Adds pub(crate) use class_computed::class_computed_member_registration_expr in lower_decl/mod.rs and removes the three identical private copies from lower_expr.rs, module_decl.rs, and stmt.rs.
Refactor collect_assigned_locals_expr to use walk_expr_children
crates/perry-hir/src/analysis.rs
Replaces ~1000-line exhaustive variant enumeration with a focused match on assignment-bearing expressions (LocalSet, Update, array-rebinding mutators, SetAdd, Closure guard), delegating all other child descent to walk_expr_children.

Transform: canonical id_scan extensions and duplicate removal

Layer / File(s) Summary
Extend canonical id_scan to cover global.init and Switch case-test
crates/perry-transform/src/generator/id_scan.rs
Adds global.init expression scanning and per-case test-expression scanning to both compute_max_local_id and compute_max_func_id, closing coverage gaps that allowed ID collisions when closures appeared in those positions.
Remove duplicated max-ID helpers from transform passes
crates/perry-transform/src/async_to_generator.rs, crates/perry-transform/src/finally_inline.rs, crates/perry-transform/src/state_desugar.rs, crates/perry-transform/src/unroll/mod.rs
Deletes ~1000 lines of duplicated compute_max_local_id/compute_max_func_id and scan_* helpers from four transform passes, replacing each with an import of the canonical crate::generator helpers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PerryTS/perry#5186: Modifies compute_max_func_id / async_to_generator to add class-field initializer locations, directly overlapping with the ID-scan coverage extended in this PR.

Poem

🐇 Five scanners were five too many,
Each copied with care, not a penny —
Now one rules the crate,
And walkers delegate,
No silent miss-match, not any! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: de-duplicating HIR-walking helpers as a refactoring effort targeting issue #5293.
Description check ✅ Passed The PR description comprehensively covers the changes across three main components, includes the issue reference, validation approach, and follows repository conventions.
Linked Issues check ✅ Passed All changes directly address the objectives from issue #5293: consolidates compute_max_local_id/compute_max_func_id copies [#5293], eliminates class_computed_member_registration_expr duplicates [#5293], and refactors collect_assigned_locals_expr [#5293].
Out of Scope Changes check ✅ Passed All changes are scoped to the declared objectives: helper consolidation, canonical scanner enhancement (switch case.test, module-global initializers), and walk_expr_children delegation. Optional codegen extraction deferred as noted.
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 worktree-refactor-hir-walkers-5293

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

cargo fmt wants the walk_expr_children closure body wrapped in a block
since the single-line form exceeds the width limit. Fixes the lint CI
failure on this branch; no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0161bea2ibAsgFkKE2w7F3Ei
@proggeramlug proggeramlug merged commit 7b5f4b8 into main Jun 17, 2026
14 of 15 checks passed
@proggeramlug proggeramlug deleted the worktree-refactor-hir-walkers-5293 branch June 17, 2026 11:07
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.

refactor(transform/hir): de-duplicate HIR-walking helpers (compute_max_local_id ×5, etc.) to kill a recurring bug-class

2 participants