iter 22b.3.5: fix — quality-review (chained-call test, hoist class_index, drop noise comments)
This commit is contained in:
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 } }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user