iter 22b.3.tester: e2e for coherence, default-keyword, cross-module mono
Adds three end-to-end tests for the 22b.3 mono pass, each with a matching one-property fixture under examples/. Defends mono-pass invariants 3, 4, and 6 from docs/specs/2026-05-09-22-typeclasses.md at the binary level (stdout-asserting, not AST-asserting): - coherence_two_instances_pick_correct_body_at_runtime — class R with instances at Int and Bool, both reachable from one main. Stdout `11\n22` proves both mono fns are synthesised AND each call site picks the matching body (no collapse). - class_default_method_runs_and_prints_default_value — empty instance body + class-level `default` clause must lower, link, and print the default value (`99`). Promotes the existing synthesise_mono_fn unit test to a binary-level guarantee. - cross_module_class_method_runs_and_prints_correct_value — class+instance in module A, called from module B's main. The qualified-name rewrite + cross-module synth-def placement was AST-tested in 22b.3.6; this pins down the emit-and-link path. All 18 typeclass_22b3 tests pass; full workspace green; 3 bench gates 0/0/0.
This commit is contained in:
@@ -687,3 +687,110 @@ fn rewrite_replaces_class_method_var_with_mono_symbol_same_module() {
|
||||
};
|
||||
assert_eq!(callee_name, "show__Int");
|
||||
}
|
||||
|
||||
/// Helper: build & run a workspace via `ail run` and capture stdout
|
||||
/// + stderr. The 22b.3 e2e tests below all funnel through this so the
|
||||
/// failure-mode reporting is uniform.
|
||||
fn ail_run(fixture_name: &str) -> (std::process::Output, String) {
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let ail_bin = std::env::var("CARGO_BIN_EXE_ail")
|
||||
.expect("CARGO_BIN_EXE_ail set by cargo test");
|
||||
let fixture = examples_dir().join(fixture_name);
|
||||
|
||||
let tmp = manifest_dir.join("target").join(format!(
|
||||
"test_22b3_e2e_{}_{}",
|
||||
fixture_name.replace('.', "_"),
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&tmp).expect("mkdir temp");
|
||||
|
||||
let output = std::process::Command::new(&ail_bin)
|
||||
.arg("run")
|
||||
.arg(&fixture)
|
||||
.current_dir(&tmp)
|
||||
.output()
|
||||
.expect("ail run");
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
(output, stdout)
|
||||
}
|
||||
|
||||
/// Property: when a single workspace declares two distinct instances of
|
||||
/// the SAME class (`R Int` and `R Bool`), and `main` calls the class
|
||||
/// method `r` at BOTH types, the binary picks the correct
|
||||
/// monomorphised body for each call site. Coherence is preserved
|
||||
/// end-to-end: instance `R Int { r _ = 11 }` reaches the `r 0` call,
|
||||
/// instance `R Bool { r _ = 22 }` reaches the `r True` call. A
|
||||
/// regression where coherence collapses (e.g. both call sites get
|
||||
/// rewritten to the same `r__<X>`, or one synth body shadows the other)
|
||||
/// would surface here as `11\n11\n`, `22\n22\n`, or build/runtime
|
||||
/// failure — all observable at stdout. Defends mono-pass invariant 3.
|
||||
#[test]
|
||||
fn coherence_two_instances_pick_correct_body_at_runtime() {
|
||||
let (output, stdout) = ail_run("test_22b3_coherence_two_instances.ail.json");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"ail run failed: stdout={}, stderr={}",
|
||||
stdout,
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
assert_eq!(
|
||||
stdout.trim_end_matches('\n'),
|
||||
"11\n22",
|
||||
"stdout = `{stdout}`, stderr = `{}`",
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
}
|
||||
|
||||
/// Property: a class method whose body comes from the class-level
|
||||
/// `default` clause (the instance omits the override) is reachable
|
||||
/// end-to-end through codegen and run. `class D a where dval : (a) ->
|
||||
/// Int default \x. 99` + empty `instance D Int {}` + `main` calling
|
||||
/// `dval 0` must produce `99\n`. Currently only `synthesise_mono_fn`'s
|
||||
/// unit test covers the default-fallback branch; this fixture pins
|
||||
/// down the *binary-level* property — the synthesised fn must lower
|
||||
/// and link, the call site must be rewritten to `dval__Int`, the
|
||||
/// constant 99 must reach stdout. Defends mono-pass invariant 4.
|
||||
#[test]
|
||||
fn class_default_method_runs_and_prints_default_value() {
|
||||
let (output, stdout) = ail_run("test_22b3_default_e2e.ail.json");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"ail run failed: stdout={}, stderr={}",
|
||||
stdout,
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
assert_eq!(
|
||||
stdout.trim_end(),
|
||||
"99",
|
||||
"stdout = `{stdout}`, stderr = `{}`",
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
}
|
||||
|
||||
/// Property: a class+instance declared in module A, called from module
|
||||
/// B's `main`, links and runs end-to-end. The mono pass appends the
|
||||
/// synthesised fn to module A (the instance's defining module), the
|
||||
/// call-site rewrite in module B emits the qualified name
|
||||
/// `<A>.<mono_symbol>`, and the linker resolves the reference across
|
||||
/// module boundaries. The AST-level property is already protected by
|
||||
/// `rewrite_uses_qualified_name_for_cross_module_call_site`, but the
|
||||
/// emit + link + run path is otherwise untested at the binary level.
|
||||
/// A regression that left the synthesised fn unemitted or the call
|
||||
/// site unqualified would surface as a link error or wrong stdout.
|
||||
/// Defends mono-pass invariant 6.
|
||||
#[test]
|
||||
fn cross_module_class_method_runs_and_prints_correct_value() {
|
||||
let (output, stdout) = ail_run("test_22b3_xmod_e2e_main.ail.json");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"ail run failed: stdout={}, stderr={}",
|
||||
stdout,
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
assert_eq!(
|
||||
stdout.trim_end(),
|
||||
"7",
|
||||
"stdout = `{stdout}`, stderr = `{}`",
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "test_22b3_coherence_two_instances",
|
||||
"imports": [],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "class", "name": "R", "param": "a",
|
||||
"methods": [{
|
||||
"name": "r",
|
||||
"type": {
|
||||
"k": "fn", "params": [{ "k": "var", "name": "a" }],
|
||||
"ret": { "k": "con", "name": "Int" }, "effects": []
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "R",
|
||||
"type": { "k": "con", "name": "Int" },
|
||||
"methods": [{
|
||||
"name": "r",
|
||||
"body": {
|
||||
"t": "lam",
|
||||
"params": ["x"],
|
||||
"paramTypes": [{ "k": "var", "name": "a" }],
|
||||
"retType": { "k": "con", "name": "Int" },
|
||||
"body": { "t": "lit", "lit": { "kind": "int", "value": 11 } }
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "R",
|
||||
"type": { "k": "con", "name": "Bool" },
|
||||
"methods": [{
|
||||
"name": "r",
|
||||
"body": {
|
||||
"t": "lam",
|
||||
"params": ["b"],
|
||||
"paramTypes": [{ "k": "var", "name": "a" }],
|
||||
"retType": { "k": "con", "name": "Int" },
|
||||
"body": { "t": "lit", "lit": { "kind": "int", "value": 22 } }
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"kind": "fn",
|
||||
"name": "main",
|
||||
"type": {
|
||||
"k": "fn", "params": [], "ret": { "k": "con", "name": "Unit" },
|
||||
"effects": ["IO"]
|
||||
},
|
||||
"params": [],
|
||||
"body": {
|
||||
"t": "seq",
|
||||
"lhs": {
|
||||
"t": "do",
|
||||
"op": "io/print_int",
|
||||
"args": [{
|
||||
"t": "app",
|
||||
"fn": { "t": "var", "name": "r" },
|
||||
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 0 } }]
|
||||
}]
|
||||
},
|
||||
"rhs": {
|
||||
"t": "do",
|
||||
"op": "io/print_int",
|
||||
"args": [{
|
||||
"t": "app",
|
||||
"fn": { "t": "var", "name": "r" },
|
||||
"args": [{ "t": "lit", "lit": { "kind": "bool", "value": true } }]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "test_22b3_default_e2e",
|
||||
"imports": [],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "class", "name": "D", "param": "a",
|
||||
"methods": [{
|
||||
"name": "dval",
|
||||
"type": {
|
||||
"k": "fn", "params": [{ "k": "var", "name": "a" }],
|
||||
"ret": { "k": "con", "name": "Int" }, "effects": []
|
||||
},
|
||||
"default": {
|
||||
"t": "lam",
|
||||
"params": ["x"],
|
||||
"paramTypes": [{ "k": "var", "name": "a" }],
|
||||
"retType": { "k": "con", "name": "Int" },
|
||||
"body": { "t": "lit", "lit": { "kind": "int", "value": 99 } }
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "D",
|
||||
"type": { "k": "con", "name": "Int" },
|
||||
"methods": []
|
||||
},
|
||||
{
|
||||
"kind": "fn",
|
||||
"name": "main",
|
||||
"type": {
|
||||
"k": "fn", "params": [], "ret": { "k": "con", "name": "Unit" },
|
||||
"effects": ["IO"]
|
||||
},
|
||||
"params": [],
|
||||
"body": {
|
||||
"t": "do",
|
||||
"op": "io/print_int",
|
||||
"args": [{
|
||||
"t": "app",
|
||||
"fn": { "t": "var", "name": "dval" },
|
||||
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 0 } }]
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "test_22b3_xmod_e2e_classmod",
|
||||
"imports": [],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "class", "name": "XR", "param": "a",
|
||||
"methods": [{
|
||||
"name": "xr",
|
||||
"type": {
|
||||
"k": "fn", "params": [{ "k": "var", "name": "a" }],
|
||||
"ret": { "k": "con", "name": "Int" }, "effects": []
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "XR",
|
||||
"type": { "k": "con", "name": "Int" },
|
||||
"methods": [{
|
||||
"name": "xr",
|
||||
"body": {
|
||||
"t": "lam",
|
||||
"params": ["x"],
|
||||
"paramTypes": [{ "k": "var", "name": "a" }],
|
||||
"retType": { "k": "con", "name": "Int" },
|
||||
"body": { "t": "lit", "lit": { "kind": "int", "value": 7 } }
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "test_22b3_xmod_e2e_main",
|
||||
"imports": [{ "module": "test_22b3_xmod_e2e_classmod" }],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "fn",
|
||||
"name": "main",
|
||||
"type": {
|
||||
"k": "fn", "params": [], "ret": { "k": "con", "name": "Unit" },
|
||||
"effects": ["IO"]
|
||||
},
|
||||
"params": [],
|
||||
"body": {
|
||||
"t": "do",
|
||||
"op": "io/print_int",
|
||||
"args": [{
|
||||
"t": "app",
|
||||
"fn": { "t": "var", "name": "xr" },
|
||||
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 0 } }]
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user