iter 23.1.2: load_workspace auto-injects prelude module
This commit is contained in:
@@ -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,
|
||||
})),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user