From 37425839244475484d4e1ea3b4851d359ff1229c Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 10 May 2026 21:13:40 +0200 Subject: [PATCH] iter 23.1.2: load_workspace auto-injects prelude module --- crates/ail/src/main.rs | 13 ++++++ crates/ail/tests/e2e.rs | 5 ++- crates/ailang-core/src/workspace.rs | 69 ++++++++++++++++++++++++++++- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/crates/ail/src/main.rs b/crates/ail/src/main.rs index 99c9411..5723fed 100644 --- a/crates/ail/src/main.rs +++ b/crates/ail/src/main.rs @@ -1140,6 +1140,19 @@ fn workspace_error_to_diagnostic( "type": type_repr, })), ), + // Iter 23.1: the user named a module `prelude`, which the + // loader reserves for the auto-injected prelude module. + W::ReservedModuleName { name } => Some( + ailang_check::Diagnostic::error( + "reserved-module-name", + format!( + "module name {name:?} is reserved (auto-injected by the loader)" + ), + ) + .with_ctx(serde_json::json!({ + "module": name, + })), + ), } } diff --git a/crates/ail/tests/e2e.rs b/crates/ail/tests/e2e.rs index 34f1751..186457c 100644 --- a/crates/ail/tests/e2e.rs +++ b/crates/ail/tests/e2e.rs @@ -846,7 +846,9 @@ fn workspace_lists_imported_modules() { ); let modules = v["modules"].as_array().expect("modules must be array"); - assert_eq!(modules.len(), 2, "expected 2 modules: {stdout}"); + // Iter 23.1: the loader auto-injects the `prelude` module, so + // the count is the user's two modules plus prelude. + assert_eq!(modules.len(), 3, "expected 3 modules: {stdout}"); let names: Vec<&str> = modules .iter() @@ -854,6 +856,7 @@ fn workspace_lists_imported_modules() { .collect(); assert!(names.contains(&"ws_main"), "ws_main missing: {names:?}"); assert!(names.contains(&"ws_lib"), "ws_lib missing: {names:?}"); + assert!(names.contains(&"prelude"), "prelude missing: {names:?}"); } /// Guards Iter 5b: `ail check examples/ws_main.ail.json --json` must diff --git a/crates/ailang-core/src/workspace.rs b/crates/ailang-core/src/workspace.rs index eb3caab..d666140 100644 --- a/crates/ailang-core/src/workspace.rs +++ b/crates/ailang-core/src/workspace.rs @@ -282,6 +282,13 @@ pub enum WorkspaceLoadError { superclass: String, type_repr: String, }, + + /// Iter 23.1: a user module attempted to use the reserved + /// module name `prelude`. The prelude is auto-injected by + /// the loader, so a user module of the same name would + /// collide silently. Reject explicitly. + #[error("module name {name:?} is reserved (auto-injected by the loader)")] + ReservedModuleName { name: String }, } /// Hash over the canonical bytes of a complete module. @@ -295,6 +302,23 @@ pub fn module_hash(m: &Module) -> String { h.to_hex().as_str()[..16].to_string() } +/// Iter 23.1: the prelude module is embedded into the binary at +/// compile time so the loader needs no on-disk resolution. The +/// canonical source-of-truth file remains +/// `examples/prelude.ail.json` (the file the LLM-author edits). +const PRELUDE_JSON: &str = include_str!( + "../../../examples/prelude.ail.json" +); + +/// Iter 23.1: parse the embedded prelude source into a `Module`. +/// Panics on parse failure — the prelude is build-time-validated; +/// a failure here means the embedded file is malformed and is a +/// build-correctness bug, not a runtime concern. +fn load_prelude() -> Module { + serde_json::from_str(PRELUDE_JSON) + .expect("examples/prelude.ail.json must parse as a Module") +} + /// Load entry module plus all transitively reachable modules. /// /// Algorithm: DFS over `imports`, with two sets: @@ -333,6 +357,19 @@ pub fn load_workspace(entry_path: &Path) -> Result>() + ); + let prelude = &ws.modules["prelude"]; + assert_eq!(prelude.name, "prelude"); + assert!( + prelude.defs.iter().any(|d| matches!( + d, + crate::ast::Def::Type(t) if t.name == "Ordering" + )), + "prelude must contain Ordering type def" + ); } #[test]