Skip to content

Commit 079d407

Browse files
fix(loader): discover stdlib via walk-up + user-share fallbacks (closes #415) (#433)
## Summary Closes #415. Idaptik PR #107 hit this directly: `use prelude::{Option, Some, None}` failed under `affinescript check` when invoked from a directory that wasn't the affinescript repo root, because the loader's default config only knew about `./stdlib` and the `AFFINESCRIPT_STDLIB` env var. The workaround was a local `enum UndoResult { ... }`. Replaces the hard-coded `./stdlib` default with `discover_stdlib`, which tries in order: | # | Path | Status | |---|---|---| | 1 | `$AFFINESCRIPT_STDLIB` | unchanged — explicit operator override | | 2 | `./stdlib` | unchanged — in-repo dev | | 3 | walk up from CWD looking for `stdlib/prelude.affine` | **NEW** — catches sub-directory invocations | | 4 | `<binary_dir>/../share/affinescript/stdlib/` | **NEW** — XDG-style installed location | | 5 | `$HOME/.local/share/affinescript/stdlib/` | **NEW** — user-local install | Falls back to `./stdlib` (preserves historical `ModuleNotFound` error) if nothing matches. ## Verification (local) | Scenario | Result | |---|---| | in-repo root | `./stdlib` resolves (unchanged) | | in-repo sub-dir | walk-up finds it (NEW) | | bare `/tmp/` dir, after `cp -r stdlib ~/.local/share/affinescript/` | user-share finds it (NEW) | ## What this PR does NOT do The literal `use stdlib::Option` parser syntax mentioned in #415's body is a parse error in current grammar (multi-segment `stdlib::` prefix not accepted). That's a grammar change deferred as follow-up. The practical `Done when` criterion — `affinescript check` succeeds with `use prelude::{Option, Some, None}` from any directory — is met by this PR. ## Roadmap updates - stdlib roadmap #5 (cross-file `use` resolvability) `◑` → `●` (follow-up doc PR to flip the row) - #6 (Prelude Option reach) and #10 (exhaustive imported-enum match) remain `◑` — they depend on the pattern-compiler change rather than the loader fix. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5cc87d2 commit 079d407

1 file changed

Lines changed: 60 additions & 5 deletions

File tree

lib/module_loader.ml

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,69 @@ let create (config : config) : t =
4848
loading = Hashtbl.create 16;
4949
}
5050

51+
(** Test whether a candidate stdlib directory contains the prelude. *)
52+
let stdlib_dir_valid path =
53+
Sys.file_exists (Filename.concat path "prelude.affine")
54+
55+
(** Walk up from [start_dir] looking for an ancestor whose [stdlib/]
56+
sub-directory contains the prelude. Returns [Some path] on hit, [None]
57+
after exhausting the parent chain. Used by [default_config] to find
58+
the stdlib when [affinescript check] is invoked from a sub-directory
59+
of an affinescript repo (issue #415). *)
60+
let find_stdlib_walking_up start_dir =
61+
let rec walk dir =
62+
let candidate = Filename.concat dir "stdlib" in
63+
if stdlib_dir_valid candidate then Some candidate
64+
else
65+
let parent = Filename.dirname dir in
66+
if parent = dir then None
67+
else walk parent
68+
in
69+
walk start_dir
70+
71+
(** Discover the stdlib directory by trying, in order:
72+
1. [$AFFINESCRIPT_STDLIB] — explicit operator override
73+
2. [./stdlib] — current behaviour, for in-repo dev
74+
3. Walk up from CWD looking for [stdlib/prelude.affine] — catches
75+
sub-directory invocations of [affinescript check]
76+
4. [<binary_dir>/../share/affinescript/stdlib/] — XDG-style installed
77+
location alongside an installed binary
78+
5. [$HOME/.local/share/affinescript/stdlib/] — user-local install
79+
Falls back to ["./stdlib"] (and lets the loader's own
80+
[ModuleNotFound] surface for a clean error) if nothing matches.
81+
Issue #415. *)
82+
let discover_stdlib () =
83+
match Sys.getenv_opt "AFFINESCRIPT_STDLIB" with
84+
| Some p -> p
85+
| None ->
86+
let cwd = Sys.getcwd () in
87+
let cwd_local = Filename.concat cwd "stdlib" in
88+
if stdlib_dir_valid cwd_local then cwd_local
89+
else match find_stdlib_walking_up cwd with
90+
| Some p -> p
91+
| None ->
92+
let binary_share =
93+
let bin = Sys.executable_name in
94+
if bin <> "" then
95+
Filename.concat
96+
(Filename.dirname (Filename.dirname bin))
97+
"share/affinescript/stdlib"
98+
else ""
99+
in
100+
if binary_share <> "" && stdlib_dir_valid binary_share then binary_share
101+
else
102+
let user_share =
103+
match Sys.getenv_opt "HOME" with
104+
| Some h -> Filename.concat h ".local/share/affinescript/stdlib"
105+
| None -> ""
106+
in
107+
if user_share <> "" && stdlib_dir_valid user_share then user_share
108+
else "./stdlib" (* preserves the historical default error path *)
109+
51110
(** Create default configuration *)
52111
let default_config () : config =
53-
let stdlib_path =
54-
try Sys.getenv "AFFINESCRIPT_STDLIB"
55-
with Not_found -> "./stdlib"
56-
in
57112
{
58-
stdlib_path;
113+
stdlib_path = discover_stdlib ();
59114
search_paths = [];
60115
current_dir = Sys.getcwd ();
61116
}

0 commit comments

Comments
 (0)