iter 22b.3.6: call-site rewrite — same/cross module qualified names
This commit is contained in:
@@ -442,3 +442,37 @@ fn fixpoint_handles_chained_class_method_calls() {
|
|||||||
— multi-round fixpoint property"
|
— multi-round fixpoint property"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// After the mono pass, the body of `main` in
|
||||||
|
/// test_22b2_instance_present must reference `show#Int` instead
|
||||||
|
/// of `show`.
|
||||||
|
#[test]
|
||||||
|
fn rewrite_replaces_class_method_var_with_mono_symbol_same_module() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
let ws = ailang_check::monomorphise_workspace(&ws).expect("mono");
|
||||||
|
|
||||||
|
let main_module = ws.modules.get("test_22b2_instance_present").unwrap();
|
||||||
|
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");
|
||||||
|
|
||||||
|
// The body is `App { fn: Var "show", args: [Lit 42] }`. After
|
||||||
|
// rewrite, the callee Var must be `show#Int`.
|
||||||
|
let callee_name: &str = match &main_fn.body {
|
||||||
|
ailang_core::ast::Term::App { callee, .. } => match callee.as_ref() {
|
||||||
|
ailang_core::ast::Term::Var { name } => name.as_str(),
|
||||||
|
other => panic!("callee is not Var: {:?}", other),
|
||||||
|
},
|
||||||
|
other => panic!("body is not App: {:?}", other),
|
||||||
|
};
|
||||||
|
assert_eq!(callee_name, "show#Int");
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
//! the implementation so the constraint is visible to anyone
|
//! the implementation so the constraint is visible to anyone
|
||||||
//! extending the skeleton.
|
//! extending the skeleton.
|
||||||
|
|
||||||
use ailang_core::ast::{ClassDef, Def, FnDef as AstFnDef, InstanceDef, Term, Type};
|
use ailang_core::ast::{Arm, ClassDef, Def, FnDef as AstFnDef, InstanceDef, Term, Type};
|
||||||
use ailang_core::workspace::Workspace;
|
use ailang_core::workspace::Workspace;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
@@ -148,6 +148,71 @@ pub fn monomorphise_workspace(ws: &Workspace) -> Result<Workspace> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 3: rewrite call sites in every fn / const body. Walk in
|
||||||
|
// the same pre-order as collect_residuals_ordered so the cursor
|
||||||
|
// and the per-callsite target list align position-by-position.
|
||||||
|
let env = build_workspace_env(&ws_owned); // re-build: ws_owned has new defs.
|
||||||
|
let module_names: Vec<String> = ws_owned.modules.keys().cloned().collect();
|
||||||
|
for mname in &module_names {
|
||||||
|
let n_defs = ws_owned.modules[mname].defs.len();
|
||||||
|
for i in 0..n_defs {
|
||||||
|
let (ordered, defining_module_name): (Vec<Option<MonoTarget>>, String) = {
|
||||||
|
let m = &ws_owned.modules[mname];
|
||||||
|
let d = &m.defs[i];
|
||||||
|
match d {
|
||||||
|
Def::Fn(f) => (collect_residuals_ordered(f, mname, &env)?, mname.clone()),
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
(collect_residuals_ordered(&pseudo, mname, &env)?, mname.clone())
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let m = ws_owned.modules.get_mut(mname).unwrap();
|
||||||
|
let d = &mut m.defs[i];
|
||||||
|
let ordered_concrete: Vec<MonoTarget> = ordered
|
||||||
|
.into_iter()
|
||||||
|
.map(|o| {
|
||||||
|
o.unwrap_or_else(|| MonoTarget {
|
||||||
|
class: String::new(),
|
||||||
|
method: String::new(),
|
||||||
|
type_: Type::unit(),
|
||||||
|
defining_module: defining_module_name.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut cursor = 0usize;
|
||||||
|
match d {
|
||||||
|
Def::Fn(f) => {
|
||||||
|
rewrite_class_method_calls(
|
||||||
|
&mut f.body,
|
||||||
|
&env.class_methods,
|
||||||
|
mname,
|
||||||
|
&ordered_concrete,
|
||||||
|
&mut cursor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Def::Const(c) => {
|
||||||
|
rewrite_class_method_calls(
|
||||||
|
&mut c.value,
|
||||||
|
&env.class_methods,
|
||||||
|
mname,
|
||||||
|
&ordered_concrete,
|
||||||
|
&mut cursor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ws_owned)
|
Ok(ws_owned)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,3 +567,167 @@ pub fn synthesise_mono_fn(
|
|||||||
suppress: Vec::new(),
|
suppress: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iter 22b.3: rewrite every class-method call site in `body` to
|
||||||
|
/// the corresponding mono symbol, using `ordered_targets` —
|
||||||
|
/// the per-call-site resolved targets collected by a parallel
|
||||||
|
/// synth-replay run on the same body.
|
||||||
|
///
|
||||||
|
/// The walker increments a positional counter at each
|
||||||
|
/// class-method-named `Term::Var` it encounters. The counter must
|
||||||
|
/// match the order in which `synth` pushes residuals (pre-order
|
||||||
|
/// AST walk; child evaluation order matches the `match` arms in
|
||||||
|
/// `crate::synth`). When this invariant holds, `ordered_targets[idx]`
|
||||||
|
/// is the target resolved at the i-th class-method call site.
|
||||||
|
///
|
||||||
|
/// `caller_module`: name of the module enclosing this body. If
|
||||||
|
/// the target's `defining_module` differs, the rewritten name is
|
||||||
|
/// qualified `<defining_module>.<mono_symbol>` (cross-module
|
||||||
|
/// resolution path); otherwise unqualified. Targets whose `class`
|
||||||
|
/// is empty are sentinel placeholders for non-concrete residuals
|
||||||
|
/// — the cursor advances over them and the name is left unchanged.
|
||||||
|
fn rewrite_class_method_calls(
|
||||||
|
body: &mut Term,
|
||||||
|
class_methods: &BTreeMap<String, crate::ClassMethodEntry>,
|
||||||
|
caller_module: &str,
|
||||||
|
ordered_targets: &[MonoTarget],
|
||||||
|
cursor: &mut usize,
|
||||||
|
) {
|
||||||
|
match body {
|
||||||
|
Term::Var { name } => {
|
||||||
|
if class_methods.contains_key(name) {
|
||||||
|
if let Some(t) = ordered_targets.get(*cursor) {
|
||||||
|
if !t.class.is_empty() {
|
||||||
|
let sym = mono_symbol(&t.method, &t.type_);
|
||||||
|
let new_name = if t.defining_module == caller_module {
|
||||||
|
sym
|
||||||
|
} else {
|
||||||
|
format!("{}.{}", t.defining_module, sym)
|
||||||
|
};
|
||||||
|
*name = new_name;
|
||||||
|
}
|
||||||
|
// else: residual was non-concrete → leave name unchanged.
|
||||||
|
}
|
||||||
|
*cursor += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Term::App { callee, args, .. } => {
|
||||||
|
rewrite_class_method_calls(callee, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
for a in args {
|
||||||
|
rewrite_class_method_calls(a, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Term::Let { value, body, .. } => {
|
||||||
|
rewrite_class_method_calls(value, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
rewrite_class_method_calls(body, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
Term::LetRec { body, in_term, .. } => {
|
||||||
|
rewrite_class_method_calls(body, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
rewrite_class_method_calls(in_term, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
Term::If { cond, then, else_ } => {
|
||||||
|
rewrite_class_method_calls(cond, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
rewrite_class_method_calls(then, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
rewrite_class_method_calls(else_, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
Term::Do { args, .. } => {
|
||||||
|
for a in args {
|
||||||
|
rewrite_class_method_calls(a, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Term::Ctor { args, .. } => {
|
||||||
|
for a in args {
|
||||||
|
rewrite_class_method_calls(a, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Term::Match { scrutinee, arms } => {
|
||||||
|
rewrite_class_method_calls(scrutinee, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
for Arm { body, .. } in arms {
|
||||||
|
rewrite_class_method_calls(body, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Term::Lam { body, .. } => {
|
||||||
|
rewrite_class_method_calls(body, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
Term::Seq { lhs, rhs } => {
|
||||||
|
rewrite_class_method_calls(lhs, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
rewrite_class_method_calls(rhs, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
Term::Clone { value } => {
|
||||||
|
rewrite_class_method_calls(value, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
Term::ReuseAs { source, body } => {
|
||||||
|
rewrite_class_method_calls(source, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
rewrite_class_method_calls(body, class_methods, caller_module, ordered_targets, cursor);
|
||||||
|
}
|
||||||
|
Term::Lit { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 22b.3: traversal-ordered residual collection — used by
|
||||||
|
/// the rewrite walker to align cursor positions. Unlike
|
||||||
|
/// [`collect_mono_targets`], this includes non-concrete residuals
|
||||||
|
/// as `None`, preserving one-entry-per-callsite alignment.
|
||||||
|
pub(crate) fn collect_residuals_ordered(
|
||||||
|
f: &AstFnDef,
|
||||||
|
module_name: &str,
|
||||||
|
env: &crate::Env,
|
||||||
|
) -> Result<Vec<Option<MonoTarget>>> {
|
||||||
|
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: Vec<Type> = match &inner_ty {
|
||||||
|
Type::Fn { params, .. } => params.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,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut out: Vec<Option<MonoTarget>> = Vec::new();
|
||||||
|
for r in residuals {
|
||||||
|
let r_ty = subst.apply(&r.type_);
|
||||||
|
if !crate::is_fully_concrete(&r_ty) {
|
||||||
|
out.push(None);
|
||||||
|
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 => {
|
||||||
|
out.push(None);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
out.push(Some(MonoTarget {
|
||||||
|
class: r.class.clone(),
|
||||||
|
method: r.method.clone(),
|
||||||
|
type_: r_ty,
|
||||||
|
defining_module: entry.defining_module.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,13 @@
|
|||||||
"type": { "k": "con", "name": "Int" },
|
"type": { "k": "con", "name": "Int" },
|
||||||
"methods": [{
|
"methods": [{
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"body": { "t": "lit", "lit": { "kind": "str", "value": "n" } }
|
"body": {
|
||||||
|
"t": "lam",
|
||||||
|
"params": ["x"],
|
||||||
|
"paramTypes": [{ "k": "var", "name": "a" }],
|
||||||
|
"retType": { "k": "con", "name": "Str" },
|
||||||
|
"body": { "t": "lit", "lit": { "kind": "str", "value": "n" } }
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user