Skip to content

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

Description

@proggeramlug

Background

An external code audit flagged several HIR/IR-walking helpers that are copy-pasted across files. Each copy must be hand-updated whenever a new Expr/HIR node variant is added — and when one copy is missed, the result is a silent miscompile, not a build error. This is the same bug-class behind several recent fixes (e.g. #5143 FuncId-collision from a transform scan missing class field-init closures).

Verified against main (v0.5.1176).

Confirmed duplication

compute_max_local_id — 5 copies

  • crates/perry-transform/src/generator/id_scan.rs:6 (canonical, already pub)
  • crates/perry-transform/src/finally_inline.rs:167
  • crates/perry-transform/src/async_to_generator.rs:626
  • crates/perry-transform/src/unroll/mod.rs:671
  • crates/perry-transform/src/state_desugar.rs:175

compute_max_func_id — ~4 copies

  • crates/perry-transform/src/generator/id_scan.rs:232 (canonical, pub)
  • crates/perry-transform/src/state_desugar.rs:209
  • crates/perry-transform/src/async_to_generator.rs:214 (compute_max_func_id_module)
  • crates/perry-transform/src/unroll/mod.rs:873

The copies carry a comment about "avoiding a pub-visibility bump," but the canonical versions are already pub, so the copies can simply be deleted in favor of importing the canonical ones.

class_computed_member_registration_expr — 4 byte-identical copies

  • crates/perry-hir/src/lower_decl/class_computed.rs:16 (canonical, pub(crate))
  • crates/perry-hir/src/lower/lower_expr.rs:43
  • crates/perry-hir/src/lower/module_decl.rs:17
  • crates/perry-hir/src/lower/stmt.rs:17

(The latter three are md5-identical; route them all through the canonical class_computed.rs.)

Related exhaustiveness risk (same bug-class)

collect_assigned_locals_expr (crates/perry-hir/src/analysis.rs:330, ~1121 lines) ends in a catch-all _ => {} at analysis.rs:1451. Unlike collect_local_refs_expr / remap_local_ids_in_expr (which delegate child descent to the exhaustive walk_expr_children walker), this one manually enumerates variants — so a newly-added Expr variant containing a nested LocalSet silently won't have its assigned locals collected. Converting it to use the exhaustive walker for child descent (keeping only the assignment-tracking arms custom) would close the gap.

Audit also flagged a 5× module-walking duplication in crates/perry-codegen/src/codegen/mod.rs (walking functions + class methods/ctors/getters/setters/static-methods/computed-members), proposing a for_each_body_in_module(hir, &mut |body| …) helper. Not independently line-verified here, but the same motivation applies.

Proposed fix

  1. Delete the compute_max_local_id / compute_max_func_id copies; import the canonical pub versions from generator::id_scan.
  2. Collapse the 4 class_computed_member_registration_expr copies to the canonical lower_decl::class_computed one.
  3. Make collect_assigned_locals_expr delegate child descent to walk_expr_children.
  4. (Optional) Extract a for_each_body_in_module iterator for the codegen walks.

Why not bundled into a fix PR

These are pure refactors (no behavior change) and orthogonal to correctness fixes — kept separate to keep diffs reviewable. Surfaced alongside #5292 (which fixed the one reproducible correctness bug from the same audit).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions