Skip to content

Commit 9d4e00b

Browse files
hyperpolymathclaude
andcommitted
fix(ast): find_free_vars walks loops, assignments and pattern binders
Statements other than `let` and expression statements were skipped, so a name used only inside a `for`/`while` body (or an assignment) was never reported free. Import flattening then dropped the private helper it named: a public `create` calling `apply_attr` in a for-loop compiled to `ReferenceError: apply_attr is not defined` on Bun-ESM. The same walker drives closure conversion, which could likewise miss a capture. Also bind every variable of a destructuring `let` and of match-arm patterns (and walk guards), instead of only plain `PatVar` lets. dune test main suite 553 OK; Bun-ESM 34/34; WASM 38/38. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 5e0279e commit 9d4e00b

1 file changed

Lines changed: 36 additions & 10 deletions

File tree

‎lib/ast.ml‎

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,22 @@ let fn_body_contains_return : fn_body -> bool = function
554554
undefined `h`). Like the other walkers in this module it is deliberately
555555
conservative: constructs it does not inspect contribute [] rather than a
556556
wrong answer. *)
557+
(** Variables bound by a pattern. *)
558+
let rec pattern_binders (pat : pattern) : string list =
559+
match pat with
560+
| PatWildcard _ | PatLit _ -> []
561+
| PatVar id -> [id.name]
562+
| PatTuple pats -> List.concat_map pattern_binders pats
563+
| PatRecord (fields, _) ->
564+
List.concat_map (fun (id, pat_opt) ->
565+
match pat_opt with
566+
| Some p -> pattern_binders p
567+
| None -> [id.name]
568+
) fields
569+
| PatCon (_, pats) -> List.concat_map pattern_binders pats
570+
| PatOr (p1, p2) -> pattern_binders p1 @ pattern_binders p2
571+
| PatAs (id, pat) -> id.name :: pattern_binders pat
572+
557573
let rec find_free_vars (bound_vars : string list) (expr : expr) : string list =
558574
match expr with
559575
| ExprLit _ -> []
@@ -574,10 +590,7 @@ let rec find_free_vars (bound_vars : string list) (expr : expr) : string list =
574590
| ExprLet lb ->
575591
let rhs_free = find_free_vars bound_vars lb.el_value in
576592
(* Add bound variable to scope for body *)
577-
let new_bound = match lb.el_pat with
578-
| PatVar id -> id.name :: bound_vars
579-
| _ -> bound_vars
580-
in
593+
let new_bound = pattern_binders lb.el_pat @ bound_vars in
581594
let body_free = match lb.el_body with
582595
| Some e -> find_free_vars new_bound e
583596
| None -> []
@@ -596,14 +609,22 @@ let rec find_free_vars (bound_vars : string list) (expr : expr) : string list =
596609
match stmt with
597610
| StmtLet sl ->
598611
let rhs_free = find_free_vars bound sl.sl_value in
599-
let new_bound = match sl.sl_pat with
600-
| PatVar id -> id.name :: bound
601-
| _ -> bound
602-
in
612+
let new_bound = pattern_binders sl.sl_pat @ bound in
603613
(new_bound, acc_free @ rhs_free)
604614
| StmtExpr e ->
605615
(bound, acc_free @ find_free_vars bound e)
606-
| _ -> (bound, acc_free)
616+
(* Loops and assignments were skipped entirely, so a name used only
617+
inside a `for`/`while` body was never reported: import flattening
618+
then dropped the helper it called (`ReferenceError: apply_attr is
619+
not defined`), and closure conversion could miss a capture. *)
620+
| StmtAssign (lhs, _, rhs) ->
621+
(bound, acc_free @ find_free_vars bound lhs @ find_free_vars bound rhs)
622+
| StmtWhile (cond, body) ->
623+
(bound, acc_free @ find_free_vars bound cond
624+
@ find_free_vars bound (ExprBlock body))
625+
| StmtFor (pat, iter, body) ->
626+
(bound, acc_free @ find_free_vars bound iter
627+
@ find_free_vars (pattern_binders pat @ bound) (ExprBlock body))
607628
) (bound_vars, []) blk.blk_stmts in
608629
(* The tail expression is in scope of the block's own `let`
609630
bindings, so its free vars must exclude them — use the
@@ -617,7 +638,12 @@ let rec find_free_vars (bound_vars : string list) (expr : expr) : string list =
617638
free @ expr_free
618639
| ExprMatch m ->
619640
find_free_vars bound_vars m.em_scrutinee @
620-
List.concat (List.map (fun arm -> find_free_vars bound_vars arm.ma_body) m.em_arms)
641+
List.concat (List.map (fun arm ->
642+
let arm_bound = pattern_binders arm.ma_pat @ bound_vars in
643+
(match arm.ma_guard with
644+
| Some g -> find_free_vars arm_bound g
645+
| None -> [])
646+
@ find_free_vars arm_bound arm.ma_body) m.em_arms)
621647
| ExprReturn e_opt ->
622648
(match e_opt with Some e -> find_free_vars bound_vars e | None -> [])
623649
| ExprTuple exprs | ExprArray exprs ->

0 commit comments

Comments
 (0)