Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions lib/resolve.ml
Original file line number Diff line number Diff line change
Expand Up @@ -488,9 +488,52 @@ let rec resolve_decl (ctx : context) (decl : top_level) : unit result =
Symbol.SKVariable tc.tc_name.span tc.tc_vis in
resolve_expr ctx tc.tc_value

(** Pre-register a top-level declaration's *names* (no body resolution),
so that forward references between top-level declarations resolve
(issue #135 slice 11). Previously [resolve_decl] defined a name then
immediately resolved its body, and [resolve_program] folded in source
order — so `fn a() { b() } fn b() {}` failed with `UndefinedVariable
b`. This is a standard two-pass: pass 1 declares every top-level
name, pass 2 resolves bodies. [Symbol.define] is [Hashtbl.replace]
based, so the re-definition inside [resolve_decl] is idempotent. *)
let pre_register_decl (ctx : context) (decl : top_level) : unit =
match decl with
| TopFn fd ->
ignore (Symbol.define ctx.symbols fd.fd_name.name
Symbol.SKFunction fd.fd_name.span fd.fd_vis)
| TopType td ->
ignore (Symbol.define ctx.symbols td.td_name.name
Symbol.SKType td.td_name.span td.td_vis);
(match td.td_body with
| TyEnum variants ->
List.iter (fun (vd : variant_decl) ->
ignore (Symbol.define ctx.symbols vd.vd_name.name
Symbol.SKConstructor vd.vd_name.span td.td_vis)
) variants
| TyAlias _ | TyStruct _ | TyExtern -> ())
| TopEffect ed ->
ignore (Symbol.define ctx.symbols ed.ed_name.name
Symbol.SKEffect ed.ed_name.span ed.ed_vis);
List.iter (fun op ->
ignore (Symbol.define ctx.symbols op.eod_name.name
Symbol.SKEffectOp op.eod_name.span ed.ed_vis)
) ed.ed_ops
| TopTrait td ->
ignore (Symbol.define ctx.symbols td.trd_name.name
Symbol.SKTrait td.trd_name.span td.trd_vis)
| TopConst tc ->
ignore (Symbol.define ctx.symbols tc.tc_name.name
Symbol.SKVariable tc.tc_name.span tc.tc_vis)
| TopImpl _ -> ()

(** Run the forward-declaration pass over a whole program. *)
let pre_register_program (ctx : context) (program : program) : unit =
List.iter (pre_register_decl ctx) program.prog_decls

(** Resolve an entire program *)
let resolve_program (program : program) : (context, resolve_error * Span.t) Result.t =
let ctx = create_context () in
pre_register_program ctx program;
match List.fold_left (fun acc decl ->
match acc with
| Error e -> Error e
Expand All @@ -506,7 +549,9 @@ let resolve_and_typecheck_module (loaded_mod : Module_loader.loaded_module)
let symbols = Symbol.create () in
let mod_ctx = { symbols; current_module = []; imports = []; references = [] } in

(* Resolve all declarations in the module *)
(* Pass 1: forward-declare every top-level name (#135 slice 11). *)
pre_register_program mod_ctx prog;
(* Pass 2: resolve all declaration bodies. *)
let* () = List.fold_left (fun acc decl ->
match acc with
| Error e -> Error e
Expand Down Expand Up @@ -753,7 +798,9 @@ let resolve_program_with_loader
let type_ctx = Typecheck.create_context ctx.symbols in
(* First resolve imports using module loader *)
let* () = resolve_imports_with_loader ctx type_ctx loader program.prog_imports in
(* Then resolve declarations *)
(* Pass 1: forward-declare every top-level name (#135 slice 11). *)
pre_register_program ctx program;
(* Pass 2: resolve declaration bodies. *)
let* () = List.fold_left (fun acc decl ->
match acc with
| Error e -> Error e
Expand Down
23 changes: 23 additions & 0 deletions test/test_e2e.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3380,6 +3380,26 @@ let test_generic_hof_monomorphic_caller () =
{|fn fold<T, U>(arr: [T], init: U, f: (U, T) -> U) -> U { return init; }
fn sum(a: [Int]) -> Int { return fold(a, 0, fn(acc, x) => acc + x); }|})

(* Issue #135 slice 11: the resolver is two-pass — all top-level names
are forward-declared before any body is resolved. Before, a call to
a function defined later in the file was `UndefinedVariable`. *)
let test_forward_reference () =
Alcotest.(check bool) "fn calling a later-defined fn resolves" true
(parse_check_passes
{|fn a() -> Int { return b(); }
fn b() -> Int { return 1; }|})

let test_mutual_recursion () =
Alcotest.(check bool) "mutually recursive fns resolve + typecheck" true
(parse_check_passes
{|fn even(n: Int) -> Bool { if n == 0 { return true; } return odd(n - 1); }
fn odd(n: Int) -> Bool { if n == 0 { return false; } return even(n - 1); }|})

let test_self_recursion_not_regressed () =
Alcotest.(check bool) "self-recursion still resolves" true
(parse_check_passes
{|fn rec(n: Int) -> Int { if n <= 0 { return 0; } return rec(n - 1); }|})

let test_multi_arg_arrow () =
Alcotest.(check bool) "(A, B) -> C parses + typechecks" true
(parse_check_passes
Expand Down Expand Up @@ -3437,6 +3457,9 @@ let type_syntax_sugar_tests = [
Alcotest.test_case "trait sig + assoc non-regressed (#135 sl5)" `Quick test_trait_sig_and_assoc_not_regressed;
Alcotest.test_case "generic fn multi-instantiation (#135 sl7)" `Quick test_generic_fn_multi_instantiation;
Alcotest.test_case "generic HOF + mono caller (#135 sl7)" `Quick test_generic_hof_monomorphic_caller;
Alcotest.test_case "forward reference resolves (#135 sl11)" `Quick test_forward_reference;
Alcotest.test_case "mutual recursion (#135 sl11)" `Quick test_mutual_recursion;
Alcotest.test_case "self-recursion non-regressed (#135 sl11)" `Quick test_self_recursion_not_regressed;
Alcotest.test_case "(A, B) -> C (multi-arg arrow)" `Quick test_multi_arg_arrow;
Alcotest.test_case "(A, B) without arrow remains tuple" `Quick test_tuple_type_still_works;
]
Expand Down
Loading