iter 22b.3.5: fix — quality-review (chained-call test, hoist class_index, drop noise comments)

This commit is contained in:
2026-05-09 20:59:43 +02:00
parent dd87b36e00
commit e8018d48b6
3 changed files with 126 additions and 7 deletions
+52
View File
@@ -390,3 +390,55 @@ fn fixpoint_dedupes_repeated_calls_at_same_type() {
}
assert_eq!(count, 1, "exactly one show#Int across the workspace");
}
/// Property: when a synthesised instance body itself calls another class
/// method (the canonical `Eq.ne x y = not (eq x y)` shape), the workspace
/// fixpoint closes over the chained call — Round 1 synthesises the outer
/// fn, Round 2 picks up the inner class-method residual on the freshly-
/// appended body and synthesises that. Without the multi-round loop, the
/// inner mono fn would be missing.
///
/// Fixture (`test_22b3_chained_calls`):
/// class B a where b_method : (a) -> Int
/// class A a where a_method : (a) -> Int default \x. b_method x
/// instance B Int { b_method = \y. y }
/// instance A Int { /* uses class default */ }
/// main : () -> Int = a_method 5
///
/// Expected post-mono workspace contents: BOTH `a_method#Int` (Round 1,
/// from the class default) AND `b_method#Int` (Round 2, from instance B's
/// body).
#[test]
fn fixpoint_handles_chained_class_method_calls() {
let entry = examples_dir().join("test_22b3_chained_calls.ail.json");
let ws = ailang_core::load_workspace(&entry).expect("load");
let diags = ailang_check::check_workspace(&ws);
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
let ws = ailang_check::monomorphise_workspace(&ws).expect("mono");
// Locate both synthesised fns workspace-wide.
let mut has_a_method_int = false;
let mut has_b_method_int = false;
for m in ws.modules.values() {
for d in &m.defs {
if let ailang_core::ast::Def::Fn(f) = d {
if f.name == "a_method#Int" {
has_a_method_int = true;
}
if f.name == "b_method#Int" {
has_b_method_int = true;
}
}
}
}
assert!(
has_a_method_int,
"a_method#Int must be synthesised in Round 1 from main's call site"
);
assert!(
has_b_method_int,
"b_method#Int must be synthesised in Round 2 from a_method#Int's body \
— multi-round fixpoint property"
);
}
+1 -7
View File
@@ -74,14 +74,10 @@ pub fn monomorphise_workspace(ws: &Workspace) -> Result<Workspace> {
return Ok(ws.clone());
}
// Take ownership of a mutable workspace clone; the pass
// appends synthesised defs to per-module def lists.
let mut ws_owned: Workspace = ws.clone();
let env = build_workspace_env(&ws_owned);
let class_index = build_class_index(&ws_owned);
// Dedup set: every (class, method, type-hash) we have already
// synthesised. Once a key is here, future collection rounds
// skip it.
let mut synthesised: BTreeSet<(String, String, String)> = BTreeSet::new();
// Fixpoint: keep collecting until a round adds nothing new.
@@ -117,7 +113,6 @@ pub fn monomorphise_workspace(ws: &Workspace) -> Result<Workspace> {
// truth for ClassDef + InstanceDef lookups. Both come
// from the un-modified workspace registry — synthesis
// never mutates the registry.
let class_index = build_class_index(&ws_owned);
for t in &new {
let key = mono_target_key(t);
let registry_key = (t.class.clone(), ailang_core::canonical::type_hash(&t.type_));
@@ -153,7 +148,6 @@ pub fn monomorphise_workspace(ws: &Workspace) -> Result<Workspace> {
}
}
// Task 6 will splice the call-site rewrite here.
Ok(ws_owned)
}
+73
View File
@@ -0,0 +1,73 @@
{
"schema": "ailang/v0",
"name": "test_22b3_chained_calls",
"imports": [],
"defs": [
{
"kind": "class", "name": "B", "param": "a",
"methods": [{
"name": "b_method",
"type": {
"k": "fn", "params": [{ "k": "var", "name": "a" }],
"ret": { "k": "con", "name": "Int" }, "effects": []
}
}]
},
{
"kind": "class", "name": "A", "param": "a",
"methods": [{
"name": "a_method",
"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": "app",
"fn": { "t": "var", "name": "b_method" },
"args": [{ "t": "var", "name": "x" }]
}
}
}]
},
{
"kind": "instance",
"class": "B",
"type": { "k": "con", "name": "Int" },
"methods": [{
"name": "b_method",
"body": {
"t": "lam",
"params": ["y"],
"paramTypes": [{ "k": "var", "name": "a" }],
"retType": { "k": "con", "name": "Int" },
"body": { "t": "var", "name": "y" }
}
}]
},
{
"kind": "instance",
"class": "A",
"type": { "k": "con", "name": "Int" },
"methods": []
},
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn", "params": [], "ret": { "k": "con", "name": "Int" },
"effects": []
},
"params": [],
"body": {
"t": "app",
"fn": { "t": "var", "name": "a_method" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 5 } }]
}
}
]
}