iter 22b.3.3: collect_mono_targets — synth-replay residual gathering
This commit is contained in:
@@ -111,6 +111,54 @@ fn mono_symbol_primitive_types_use_surface_form() {
|
||||
assert_eq!(ailang_check::mono::mono_symbol("foo", &unit_ty), "foo#Unit");
|
||||
}
|
||||
|
||||
/// Property: collect_mono_targets returns exactly the set of
|
||||
/// fully-concrete `(class, method, type)` triples observed at
|
||||
/// class-method call sites in a fn body, with the registry's
|
||||
/// `defining_module` attached to each. The fixture's `main` calls
|
||||
/// `show 42` once with `Int`, and the workspace declares
|
||||
/// `instance Show Int` in the same module — the expected output is
|
||||
/// a single MonoTarget naming `Show`, `show`, `Int`, and that
|
||||
/// module.
|
||||
#[test]
|
||||
fn collect_mono_targets_single_concrete_call_site() {
|
||||
use ailang_check::mono::collect_mono_targets;
|
||||
|
||||
let entry = examples_dir().join("test_22b2_instance_present.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);
|
||||
|
||||
// Build the workspace-wide env (class_methods, registry, ...)
|
||||
// exactly as `check_in_workspace` does. The mono pass exposes
|
||||
// this via `build_workspace_env`.
|
||||
let env = ailang_check::mono::build_workspace_env(&ws);
|
||||
|
||||
// The fixture has one user fn — `main`. Locate it.
|
||||
let main_module = ws.modules.get("test_22b2_instance_present").expect("module");
|
||||
let main_fn = main_module
|
||||
.defs
|
||||
.iter()
|
||||
.find_map(|d| match d {
|
||||
ailang_core::ast::Def::Fn(f) if f.name == "main" => Some(f),
|
||||
_ => None,
|
||||
})
|
||||
.expect("main fn");
|
||||
|
||||
let targets = collect_mono_targets(main_fn, "test_22b2_instance_present", &env)
|
||||
.expect("collect");
|
||||
|
||||
assert_eq!(targets.len(), 1, "one target expected: {:?}", targets);
|
||||
let t = &targets[0];
|
||||
assert_eq!(t.class, "Show");
|
||||
assert_eq!(t.method, "show");
|
||||
assert!(
|
||||
matches!(&t.type_, ailang_core::ast::Type::Con { name, args } if name == "Int" && args.is_empty()),
|
||||
"type must be Int, got {:?}",
|
||||
t.type_
|
||||
);
|
||||
assert_eq!(t.defining_module, "test_22b2_instance_present");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mono_symbol_compound_type_uses_hash_suffix() {
|
||||
// Compound types fall through to canonical type_hash with an
|
||||
|
||||
@@ -123,7 +123,7 @@ fn instantiate(forall_vars: &[String], body: &Type, counter: &mut u32) -> (Vec<T
|
||||
/// Substitutes named rigid vars throughout a type (used by
|
||||
/// `instantiate`). Unlike `Subst::apply`, this targets vars by name —
|
||||
/// it has no notion of metavar ids.
|
||||
fn substitute_rigids(t: &Type, mapping: &BTreeMap<String, Type>) -> Type {
|
||||
pub(crate) fn substitute_rigids(t: &Type, mapping: &BTreeMap<String, Type>) -> Type {
|
||||
match t {
|
||||
Type::Var { name } => mapping.get(name).cloned().unwrap_or_else(|| t.clone()),
|
||||
Type::Con { name, args } => Type::Con {
|
||||
@@ -1436,7 +1436,7 @@ fn check_fn(f: &FnDef, env: &Env) -> Result<()> {
|
||||
/// the no-instance lookup — anything carrying a metavar / rigid-var
|
||||
/// component is either covered by the missing-constraint check or
|
||||
/// genuinely under-specified at the call site.
|
||||
fn is_fully_concrete(t: &Type) -> bool {
|
||||
pub(crate) fn is_fully_concrete(t: &Type) -> bool {
|
||||
match t {
|
||||
Type::Con { args, .. } => args.iter().all(is_fully_concrete),
|
||||
Type::Var { .. } => false,
|
||||
|
||||
@@ -51,9 +51,11 @@
|
||||
//! the implementation so the constraint is visible to anyone
|
||||
//! extending the skeleton.
|
||||
|
||||
use ailang_core::ast::{Def, Type};
|
||||
use ailang_core::ast::{Def, FnDef as AstFnDef, Type};
|
||||
use ailang_core::workspace::Workspace;
|
||||
use crate::Result;
|
||||
use indexmap::IndexMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// Iter 22b.3: workspace-wide monomorphisation pass entry. See the
|
||||
/// module-level doc for the architecture and contract.
|
||||
@@ -129,3 +131,172 @@ fn primitive_surface_name(ty: &Type) -> Option<&'static str> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 22b.3: one synthesisation request — a fully-concrete
|
||||
/// `(class, method, type)` triple observed at a class-method call
|
||||
/// site, plus the registry's `defining_module` for the matching
|
||||
/// `InstanceDef` (the synthesised fn lives there). Equality of
|
||||
/// targets is compared via [`mono_target_key`] — `(class,
|
||||
/// method, type-hash)`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonoTarget {
|
||||
pub class: String,
|
||||
pub method: String,
|
||||
pub type_: Type,
|
||||
pub defining_module: String,
|
||||
}
|
||||
|
||||
/// Iter 22b.3: dedup key for a [`MonoTarget`] — the same triple
|
||||
/// `Registry::entries` uses for instance lookup, plus the method
|
||||
/// name. Two targets with the same key produce the same
|
||||
/// synthesised symbol and the same body. Consumed by the workspace
|
||||
/// fixpoint (Task 5) and the rewrite walker (Task 6); declared
|
||||
/// here in Task 3 so the dedup contract lives next to `MonoTarget`.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn mono_target_key(t: &MonoTarget) -> (String, String, String) {
|
||||
(
|
||||
t.class.clone(),
|
||||
t.method.clone(),
|
||||
ailang_core::canonical::type_hash(&t.type_),
|
||||
)
|
||||
}
|
||||
|
||||
/// Iter 22b.3: build the workspace-wide [`crate::Env`] used by
|
||||
/// the mono pass to re-run [`crate::synth`] on each fn body.
|
||||
/// Mirrors `check_in_workspace`'s setup: builtins + workspace
|
||||
/// registry + per-module globals + class-method table +
|
||||
/// superclass map. Cross-module type defs and consts are NOT
|
||||
/// installed here — `synth` for mono only needs to (a) recognise
|
||||
/// class-method names and (b) unify call-site arg types with
|
||||
/// declared param types. Module-resident fns are still needed
|
||||
/// for non-class-method calls inside instance bodies.
|
||||
pub fn build_workspace_env(ws: &Workspace) -> crate::Env {
|
||||
let mut env = crate::Env::default();
|
||||
crate::builtins::install(&mut env);
|
||||
env.workspace_registry = ws.registry.clone();
|
||||
|
||||
// Per-module globals + class method table + superclass map.
|
||||
// The construction order matches `check_in_workspace` so the
|
||||
// env shape is interchangeable. `build_module_globals` may
|
||||
// surface duplicate-def errors for malformed workspaces; the
|
||||
// mono pass's pre-condition is that typecheck has already
|
||||
// succeeded, so any error here means a caller violated that
|
||||
// contract — fall back to an empty per-module table to keep
|
||||
// this builder infallible.
|
||||
let module_globals_index = crate::build_module_globals(ws)
|
||||
.unwrap_or_default();
|
||||
env.module_globals = module_globals_index
|
||||
.iter()
|
||||
.map(|(mname, g)| (mname.clone(), g.fns.clone()))
|
||||
.collect();
|
||||
env.module_types = ws
|
||||
.modules
|
||||
.iter()
|
||||
.map(|(mname, m)| {
|
||||
let tys: IndexMap<String, ailang_core::ast::TypeDef> = m
|
||||
.defs
|
||||
.iter()
|
||||
.filter_map(|d| match d {
|
||||
Def::Type(td) => Some((td.name.clone(), td.clone())),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
(mname.clone(), tys)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Workspace-wide class-method table: merge per module's
|
||||
// ModuleGlobals.class_methods.
|
||||
for (_mname, g) in &module_globals_index {
|
||||
for (name, entry) in &g.class_methods {
|
||||
env.class_methods.insert(name.clone(), entry.clone());
|
||||
}
|
||||
}
|
||||
// Superclass map: walk every module's class defs.
|
||||
for m in ws.modules.values() {
|
||||
for d in &m.defs {
|
||||
if let Def::Class(c) = d {
|
||||
if let Some(sc) = &c.superclass {
|
||||
env.class_superclasses.insert(c.name.clone(), sc.class.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
env
|
||||
}
|
||||
|
||||
/// Iter 22b.3: re-run [`crate::synth`] on `f`'s body to recover
|
||||
/// the per-fn residual class constraints. Filter to fully-
|
||||
/// concrete residuals (the only ones eligible for monomorphisation),
|
||||
/// look up each `(class, type-hash)` in the registry to recover
|
||||
/// the `defining_module`, and return the list. Var-shaped or
|
||||
/// metavar-shaped residuals are silently skipped — the
|
||||
/// 22b.2 typecheck pass has already fired
|
||||
/// `MissingConstraint`/`NoInstance` for any that should not exist
|
||||
/// at this point.
|
||||
pub fn collect_mono_targets(
|
||||
f: &AstFnDef,
|
||||
module_name: &str,
|
||||
env: &crate::Env,
|
||||
) -> Result<Vec<MonoTarget>> {
|
||||
// Build the per-def env exactly as `check_fn` does — install
|
||||
// rigid vars, set the current module, etc. This mirrors
|
||||
// `crate::check_fn` minus the diagnostic emission.
|
||||
let (rigids, inner_ty): (Vec<String>, Type) = match &f.ty {
|
||||
Type::Forall { vars, constraints: _, body } => (vars.clone(), (**body).clone()),
|
||||
other => (vec![], other.clone()),
|
||||
};
|
||||
let (param_tys, _ret_ty, _eff): (Vec<Type>, Type, Vec<String>) = match &inner_ty {
|
||||
Type::Fn { params, ret, effects, .. } => {
|
||||
(params.clone(), (**ret).clone(), effects.clone())
|
||||
}
|
||||
_ => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
let mut env = env.clone();
|
||||
for v in &rigids {
|
||||
env.rigid_vars.insert(v.clone());
|
||||
}
|
||||
env.current_module = module_name.to_string();
|
||||
|
||||
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
||||
for (n, t) in f.params.iter().zip(param_tys.iter()) {
|
||||
locals.insert(n.clone(), t.clone());
|
||||
}
|
||||
let mut effects: BTreeSet<String> = BTreeSet::new();
|
||||
let mut subst = crate::Subst::default();
|
||||
let mut counter: u32 = 0;
|
||||
let mut residuals: Vec<crate::ResidualConstraint> = Vec::new();
|
||||
crate::synth(
|
||||
&f.body,
|
||||
&env,
|
||||
&mut locals,
|
||||
&mut effects,
|
||||
&f.name,
|
||||
&mut subst,
|
||||
&mut counter,
|
||||
&mut residuals,
|
||||
)?;
|
||||
|
||||
// Filter residuals to fully-concrete ones; look up
|
||||
// defining_module via the registry.
|
||||
let mut out: Vec<MonoTarget> = Vec::new();
|
||||
for r in residuals {
|
||||
let r_ty = subst.apply(&r.type_);
|
||||
if !crate::is_fully_concrete(&r_ty) {
|
||||
continue;
|
||||
}
|
||||
let key = (r.class.clone(), ailang_core::canonical::type_hash(&r_ty));
|
||||
let entry = match env.workspace_registry.entries.get(&key) {
|
||||
Some(e) => e,
|
||||
None => continue, // no-instance — Task 10 of 22b.2 fires; skip silently here.
|
||||
};
|
||||
out.push(MonoTarget {
|
||||
class: r.class.clone(),
|
||||
method: r.method.clone(),
|
||||
type_: r_ty,
|
||||
defining_module: entry.defining_module.clone(),
|
||||
});
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user