iter 22b.3.5: workspace fixpoint loop — dedup + synth-append

This commit is contained in:
2026-05-09 20:52:07 +02:00
parent d2954010ea
commit dd87b36e00
5 changed files with 278 additions and 29 deletions
+130 -4
View File
@@ -74,10 +74,136 @@ pub fn monomorphise_workspace(ws: &Workspace) -> Result<Workspace> {
return Ok(ws.clone());
}
// Skeleton in Task 1: even with classes present, return the
// workspace clone unchanged. Subsequent tasks fill in collection,
// synthesis, and rewriting.
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);
// 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.
// Each round walks every fn body in every module — including
// bodies appended by the previous round, which is what makes
// the loop close on chained class-method calls (e.g.
// `Eq.ne x y = not (eq x y)`).
loop {
let new_targets = collect_targets_workspace_wide(&ws_owned, &env)?;
// Dedup within a round (multiple call sites of `show` at
// the same type produce N copies of the same MonoTarget)
// AND across rounds (already-synthesised keys filtered out).
// Stable iteration order — preserve first-seen target so
// any later debugging round-trips to a deterministic output.
let mut seen_this_round: BTreeSet<(String, String, String)> = BTreeSet::new();
let mut new: Vec<MonoTarget> = Vec::new();
for t in new_targets {
let k = mono_target_key(&t);
if synthesised.contains(&k) || !seen_this_round.insert(k) {
continue;
}
new.push(t);
}
if new.is_empty() {
break;
}
// Synthesise each new target; append its FnDef to the
// target's `defining_module`. Mark the key as synthesised
// so the next round won't re-collect.
//
// Iter 22b.3 uses ws_owned.registry as the source of
// 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_));
let entry = ws_owned
.registry
.entries
.get(&registry_key)
.ok_or_else(|| {
crate::CheckError::Internal(format!(
"monomorphise_workspace: target `{} {}` has no registry entry",
t.class,
ailang_core::pretty::type_to_string(&t.type_),
))
})?;
let class_def = class_index.get(&t.class).ok_or_else(|| {
crate::CheckError::Internal(format!(
"monomorphise_workspace: class `{}` not found",
t.class
))
})?;
let f = synthesise_mono_fn(t, class_def, &entry.instance)?;
let target_module = ws_owned
.modules
.get_mut(&t.defining_module)
.ok_or_else(|| {
crate::CheckError::Internal(format!(
"monomorphise_workspace: defining module `{}` missing",
t.defining_module
))
})?;
target_module.defs.push(Def::Fn(f));
synthesised.insert(key);
}
}
// Task 6 will splice the call-site rewrite here.
Ok(ws_owned)
}
/// Iter 22b.3: walk every fn / const body in the workspace,
/// returning the union of [`collect_mono_targets`] outputs. Const
/// bodies are wrapped in a synthetic zero-arg `FnDef` for the
/// collection call — the residual gathering only depends on body
/// shape, so the wrapping is harmless.
fn collect_targets_workspace_wide(
ws: &Workspace,
env: &crate::Env,
) -> Result<Vec<MonoTarget>> {
let mut out: Vec<MonoTarget> = Vec::new();
for (mname, m) in &ws.modules {
for d in &m.defs {
match d {
Def::Fn(f) => {
out.extend(collect_mono_targets(f, mname, env)?);
}
Def::Const(c) => {
let pseudo = AstFnDef {
name: c.name.clone(),
ty: c.ty.clone(),
params: Vec::new(),
body: c.value.clone(),
doc: None,
suppress: Vec::new(),
};
out.extend(collect_mono_targets(&pseudo, mname, env)?);
}
_ => {}
}
}
}
Ok(out)
}
/// Iter 22b.3: workspace-wide `class-name -> ClassDef` index.
/// Used by the fixpoint to look up the matching class definition
/// when synthesising a fn.
fn build_class_index(ws: &Workspace) -> BTreeMap<String, ClassDef> {
let mut idx = BTreeMap::new();
for m in ws.modules.values() {
for d in &m.defs {
if let Def::Class(c) = d {
idx.insert(c.name.clone(), c.clone());
}
}
}
idx
}
/// Iter 22b.3: returns `true` iff any module in `ws` declares at