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
+13
View File
@@ -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,
})),
),
}
}
+4 -1
View File
@@ -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
+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]