Iter 5b: Cross-Module-Typcheck mit qualifizierten Verweisen
Neue API check_workspace(&Workspace) -> Vec<Diagnostic>; check_module hebt das Modul intern in einen Trivial-Workspace. Term::Var mit genau einem Punkt im Namen ist ein qualifizierter Verweis <prefix>.<def>, aufgelöst über Import-Map (alias-oder-modulname → echter Modulname). Drei neue Diagnostic-Codes: unknown-module, unknown-import, invalid-def-name. ail check lädt jetzt immer via load_workspace; Loader-Fehler werden im JSON-Modus zu strukturierten Diagnostics (module-not-found, module-cycle, …). DESIGN.md und JOURNAL.md dokumentieren die Konvention. Hash-Stabilität: alle ir_snapshot_*- Tests bitidentisch grün, kein neuer AST-Knoten.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
//! Integrationstests für `check_workspace` (Iter 5b).
|
||||
//!
|
||||
//! Diese Tests fahren über die kanonischen `examples/ws_*.ail.json`-Files;
|
||||
//! der Loader und der Checker zusammen sind die Pipeline, die `ail check`
|
||||
//! im JSON-Modus auf einen Workspace wirft.
|
||||
|
||||
use ailang_check::{check_workspace, Severity};
|
||||
use ailang_core::load_workspace;
|
||||
use std::path::Path;
|
||||
|
||||
fn examples_dir() -> std::path::PathBuf {
|
||||
let manifest = env!("CARGO_MANIFEST_DIR");
|
||||
Path::new(manifest).parent().unwrap().parent().unwrap().join("examples")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn happy_path_resolves_qualified_import() {
|
||||
// ws_main importiert ws_lib und ruft `ws_lib.add` auf — vollständig
|
||||
// typisiert. Erwartet: keine Diagnostics.
|
||||
let entry = examples_dir().join("ws_main.ail.json");
|
||||
let ws = load_workspace(&entry).expect("load ws_main");
|
||||
let diags = check_workspace(&ws);
|
||||
assert!(
|
||||
diags.is_empty(),
|
||||
"expected no diagnostics; got: {}",
|
||||
serde_json::to_string_pretty(&diags).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_import_is_reported() {
|
||||
// ws_broken referenziert `ws_lib.bogus` — Modul ist da, Def nicht.
|
||||
let entry = examples_dir().join("ws_broken.ail.json");
|
||||
let ws = load_workspace(&entry).expect("load ws_broken");
|
||||
let diags = check_workspace(&ws);
|
||||
assert_eq!(diags.len(), 1, "got: {:?}", diags);
|
||||
assert!(matches!(diags[0].severity, Severity::Error));
|
||||
assert_eq!(diags[0].code, "unknown-import");
|
||||
// Kontext muss strukturiert auf Modul + Defname zeigen.
|
||||
assert_eq!(
|
||||
diags[0].ctx.get("module").and_then(|v| v.as_str()),
|
||||
Some("ws_lib")
|
||||
);
|
||||
assert_eq!(
|
||||
diags[0].ctx.get("name").and_then(|v| v.as_str()),
|
||||
Some("bogus")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_module_prefix_is_reported() {
|
||||
// ws_unknown_module hat keine Imports, referenziert aber `nope.x`.
|
||||
let entry = examples_dir().join("ws_unknown_module.ail.json");
|
||||
let ws = load_workspace(&entry).expect("load ws_unknown_module");
|
||||
let diags = check_workspace(&ws);
|
||||
assert_eq!(diags.len(), 1, "got: {:?}", diags);
|
||||
assert!(matches!(diags[0].severity, Severity::Error));
|
||||
assert_eq!(diags[0].code, "unknown-module");
|
||||
assert_eq!(
|
||||
diags[0].ctx.get("module").and_then(|v| v.as_str()),
|
||||
Some("nope")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_def_name_with_dot_is_reported() {
|
||||
// Synthetic: ein Modul mit einer Def, deren Name einen Punkt enthält.
|
||||
// Wir konstruieren das als Module direkt und füttern es in einen
|
||||
// Trivial-Workspace, weil die kanonische Konvention das gar nicht
|
||||
// erst auf Disk durchlassen sollte.
|
||||
use ailang_core::ast::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let m = Module {
|
||||
schema: ailang_core::SCHEMA.into(),
|
||||
name: "t".into(),
|
||||
imports: vec![],
|
||||
defs: vec![Def::Const(ConstDef {
|
||||
name: "weird.name".into(),
|
||||
ty: Type::int(),
|
||||
value: Term::Lit {
|
||||
lit: Literal::Int { value: 0 },
|
||||
},
|
||||
doc: None,
|
||||
})],
|
||||
};
|
||||
let mut modules = BTreeMap::new();
|
||||
modules.insert(m.name.clone(), m.clone());
|
||||
let ws = ailang_core::Workspace {
|
||||
entry: m.name.clone(),
|
||||
modules,
|
||||
root_dir: std::path::PathBuf::from("."),
|
||||
};
|
||||
let diags = check_workspace(&ws);
|
||||
assert_eq!(diags.len(), 1, "got: {:?}", diags);
|
||||
assert_eq!(diags[0].code, "invalid-def-name");
|
||||
assert_eq!(
|
||||
diags[0].ctx.get("reason").and_then(|v| v.as_str()),
|
||||
Some("contains-dot")
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user