iter 23.1.2: load_workspace auto-injects prelude module

This commit is contained in:
2026-05-10 21:13:40 +02:00
parent cce3d9738e
commit 3742583924
3 changed files with 85 additions and 2 deletions
+68 -1
View File
@@ -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<Workspace, WorkspaceLoadError
&mut visiting_set,
)?;
// Iter 23.1: inject the prelude module. It is implicitly
// part of every workspace, so the user's import graph
// does not name it. A user module that happens to be named
// "prelude" would collide here; reject explicitly so the
// failure is informative rather than silent.
if modules.contains_key("prelude") {
return Err(WorkspaceLoadError::ReservedModuleName {
name: "prelude".to_string(),
});
}
let prelude = load_prelude();
modules.insert("prelude".to_string(), prelude);
// Iter 22b.2: class-schema validation runs before registry
// construction so that ill-kinded class definitions are rejected
// before any instance against them is registered.
@@ -851,7 +888,37 @@ mod tests {
assert_eq!(ws.entry, "ws_main");
assert!(ws.modules.contains_key("ws_main"));
assert!(ws.modules.contains_key("ws_lib"));
assert_eq!(ws.modules.len(), 2);
// Iter 23.1: the loader auto-injects the `prelude` module,
// so the count is the user's two modules plus prelude.
assert_eq!(ws.modules.len(), 3);
}
#[test]
fn loads_workspace_auto_injects_prelude() {
// Iter 23.1: the prelude module is implicitly part of every
// workspace, regardless of whether the user's modules import
// it. Loading any well-formed workspace must result in
// `ws.modules["prelude"]` being present with the Ordering
// type def.
let workspace_root =
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let entry = workspace_root.join("examples").join("ws_main.ail.json");
let ws = load_workspace(&entry).expect("load workspace");
assert!(
ws.modules.contains_key("prelude"),
"prelude module must be auto-injected; modules present: {:?}",
ws.modules.keys().collect::<Vec<_>>()
);
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]