Fix import resolution: correct library paths + hook type mismatch
Root causes of server [http-load] errors: 1. _import_hook passed pre-computed string key to library_loaded_p which calls library_name_key(string) → sx_to_list(string) → crash. Fix: pass original list spec, not the string key. 2. resolve_library_path didn't check web/lib/ for (sx dom), (sx browser), (web boot-helpers). These libraries use namespace prefixes that don't match their file locations. Server startup errors: 190 → 0. 2683/2684 tests pass (1 known: define-library import clause — spec gap). New test file: spec/tests/test-import-bind.sx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -291,15 +291,20 @@ let resolve_library_path lib_spec =
|
||||
let parts = match lib_spec with List l | ListRef { contents = l } -> l | _ -> [] in
|
||||
match List.map (fun v -> match v with Symbol s -> s | String s -> s | _ -> "") parts with
|
||||
| ["sx"; name] ->
|
||||
(* Check spec/ first, then lib/ *)
|
||||
(* Check spec/ first, then lib/, then web/lib/ (dom.sx, browser.sx live there) *)
|
||||
let spec_path = Filename.concat !_spec_base (name ^ ".sx") in
|
||||
let lib_path = Filename.concat !_lib_base (name ^ ".sx") in
|
||||
let web_lib_path = Filename.concat (Filename.concat !_web_base "lib") (name ^ ".sx") in
|
||||
if Sys.file_exists spec_path then Some spec_path
|
||||
else if Sys.file_exists lib_path then Some lib_path
|
||||
else if Sys.file_exists web_lib_path then Some web_lib_path
|
||||
else None
|
||||
| ["web"; name] ->
|
||||
let path = Filename.concat !_web_base (name ^ ".sx") in
|
||||
if Sys.file_exists path then Some path else None
|
||||
let lib_path = Filename.concat (Filename.concat !_web_base "lib") (name ^ ".sx") in
|
||||
if Sys.file_exists path then Some path
|
||||
else if Sys.file_exists lib_path then Some lib_path
|
||||
else None
|
||||
| [prefix; name] ->
|
||||
(* Generic: try prefix/name.sx *)
|
||||
let path = Filename.concat prefix (name ^ ".sx") in
|
||||
@@ -311,9 +316,14 @@ let resolve_library_path lib_spec =
|
||||
let _import_env : env option ref = ref None
|
||||
|
||||
let load_library_file path =
|
||||
(* Use eval_expr which has the cek_run import patch — handles nested imports *)
|
||||
let env = match !_import_env with Some e -> e | None -> Sx_types.make_env () in
|
||||
let exprs = Sx_parser.parse_file path in
|
||||
List.iter (fun expr -> ignore (Sx_ref.eval_expr expr (Env env))) exprs
|
||||
List.iter (fun expr ->
|
||||
try ignore (Sx_ref.eval_expr expr (Env env))
|
||||
with Eval_error msg ->
|
||||
Printf.eprintf "[load-library] %s: %s\n%!" (Filename.basename path) msg
|
||||
) exprs
|
||||
|
||||
(** IO-aware CEK run — handles suspension by dispatching IO requests.
|
||||
Import requests are handled locally (load .sx file).
|
||||
@@ -787,11 +797,11 @@ let () =
|
||||
Loads the .sx file for the library, registers it, and returns true. *)
|
||||
let () =
|
||||
Sx_types._import_hook := Some (fun lib_spec ->
|
||||
let key = Sx_ref.library_name_key lib_spec in
|
||||
if Sx_types.sx_truthy (Sx_ref.library_loaded_p key) then true
|
||||
if Sx_types.sx_truthy (Sx_ref.library_loaded_p lib_spec) then true
|
||||
else match resolve_library_path lib_spec with
|
||||
| Some path ->
|
||||
(try load_library_file path; true
|
||||
(try load_library_file path;
|
||||
true
|
||||
with _ -> false)
|
||||
| None -> false)
|
||||
|
||||
@@ -2492,6 +2502,8 @@ let http_mode port =
|
||||
ignore (env_bind env "_spec-dir" (String spec_base));
|
||||
ignore (env_bind env "_lib-dir" (String lib_base));
|
||||
ignore (env_bind env "_web-dir" (String web_base));
|
||||
(* Set import env so load_library_file (called by _import_hook) uses the main env *)
|
||||
_import_env := Some env;
|
||||
let t0 = Unix.gettimeofday () in
|
||||
(* Core spec + adapters.
|
||||
Skip: primitives.sx (declarative metadata — all prims native in OCaml),
|
||||
|
||||
@@ -495,8 +495,7 @@ and cek_run state =
|
||||
let op = match request with Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "") | _ -> "" in
|
||||
if op = "import" then
|
||||
let lib_spec = match request with Dict d -> (match Hashtbl.find_opt d "library" with Some v -> v | _ -> Nil) | _ -> Nil in
|
||||
let key = library_name_key lib_spec in
|
||||
let resolved = sx_truthy (library_loaded_p key) ||
|
||||
let resolved = sx_truthy (library_loaded_p lib_spec) ||
|
||||
(match !_import_hook with Some hook -> hook lib_spec | None -> false) in
|
||||
if resolved then run (cek_resume final Nil)
|
||||
else raise (Eval_error "IO suspension in non-IO context")
|
||||
@@ -834,8 +833,7 @@ let cek_run_iterative state =
|
||||
let op = match request with Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "") | _ -> "" in
|
||||
if op = "import" then begin
|
||||
let lib_spec = match request with Dict d -> (match Hashtbl.find_opt d "library" with Some v -> v | _ -> Nil) | _ -> Nil in
|
||||
let key = library_name_key lib_spec in
|
||||
let resolved = sx_truthy (library_loaded_p key) ||
|
||||
let resolved = sx_truthy (library_loaded_p lib_spec) ||
|
||||
(match !_import_hook with Some hook -> hook lib_spec | None -> false) in
|
||||
if resolved then begin
|
||||
s := cek_resume !s Nil;
|
||||
|
||||
75
spec/tests/test-import-bind.sx
Normal file
75
spec/tests/test-import-bind.sx
Normal file
@@ -0,0 +1,75 @@
|
||||
;; Tests for import binding — catching bind_import_set bugs
|
||||
|
||||
;; Test 1: basic define-library + import (same session)
|
||||
(define-library (test basic-lib)
|
||||
(export double greet)
|
||||
(begin
|
||||
(define double (fn (x) (* x 2)))
|
||||
(define greet "hi")))
|
||||
|
||||
(import (test basic-lib))
|
||||
|
||||
(deftest "import binds exported functions"
|
||||
(assert= (double 5) 10))
|
||||
|
||||
(deftest "import binds exported values"
|
||||
(assert= greet "hi"))
|
||||
|
||||
;; Test 2: library with import clause inside define-library
|
||||
;; This is what engine.sx does — import clause triggers suspension.
|
||||
;; The define-library handler should process import clauses.
|
||||
(define-library (test dep-lib)
|
||||
(export triple)
|
||||
(begin
|
||||
(define triple (fn (x) (* x 3)))))
|
||||
|
||||
(define-library (test uses-dep)
|
||||
(export six-times)
|
||||
(import (test dep-lib))
|
||||
(begin
|
||||
(define six-times (fn (x) (triple (double x))))))
|
||||
|
||||
(import (test uses-dep))
|
||||
|
||||
(deftest "define-library import clause binds into library env"
|
||||
(assert= (six-times 2) 12))
|
||||
|
||||
;; Test 3: import after cek_run suspension
|
||||
(define-library (test suspended-lib)
|
||||
(export add10)
|
||||
(begin
|
||||
(define add10 (fn (x) (+ x 10)))))
|
||||
|
||||
(import (test suspended-lib))
|
||||
|
||||
(deftest "import after suspension binds correctly"
|
||||
(assert= (add10 5) 15))
|
||||
|
||||
;; Test 4: library-loaded? works
|
||||
(deftest "library-loaded? accepts list spec"
|
||||
(assert= (library-loaded? (quote (test basic-lib))) true))
|
||||
|
||||
;; Test 5: re-import binds
|
||||
(define-library (test reimport)
|
||||
(export val99)
|
||||
(begin
|
||||
(define val99 99)))
|
||||
|
||||
(import (test reimport))
|
||||
|
||||
(deftest "re-import of loaded library binds exports"
|
||||
(assert= val99 99))
|
||||
|
||||
;; Test 6: bind_import_set with library loaded by _import_hook
|
||||
;; When the hook loads a library, the library key in the registry
|
||||
;; may be a string "web engine" rather than a list (web engine).
|
||||
;; bind_import_set must handle both.
|
||||
(define-library (test string-key-lib)
|
||||
(export sval)
|
||||
(begin
|
||||
(define sval 42)))
|
||||
|
||||
(import (test string-key-lib))
|
||||
|
||||
(deftest "import works with string-keyed library"
|
||||
(assert= sval 42))
|
||||
Reference in New Issue
Block a user