iter 23.4: mono-pass unification — class methods + polymorphic free fns in one fixpoint
Restructure ailang-check/src/mono.rs::monomorphise_workspace into a single specialiser over every Type::Forall-quantified Def::Fn, covering both class-method residuals AND polymorphic free-fn call sites in one fixpoint pass. Remove the codegen-time specialiser (lower_polymorphic_call, module_polymorphic_fns, mono_queue, mono_emitted, emit_specialised_fn, plus the now-dead helpers apply_subst_to_term, descriptor_for_subst, type_descriptor) entirely. Codegen sees only monomorphic Def::Fns after this iter — no polymorphic call sites, no codegen-time queue. Architecture changes: - MonoTarget: struct → enum with ClassMethod / FreeFn variants; dedup keys are guaranteed-disjoint via 'class'/'free' first component. - collect_mono_targets: new FreeFnCall channel through synth (cleaner than the plan's separate-walker proposal — one less data flow, substitution tracking comes 'for free' via fresh metavars + post-synth subst.apply). Plan Step 4.4 (polymorphic_free_fns env field) consequently obsolete. - synthesise_mono_fn_for_free_fn: new free-fn entry point; substitutes rigid vars in BOTH the type AND the body (body substitution required because free-fn bodies carry inner Lams whose param_tys reference outer Forall vars — adapted from plan's 'body unchanged' sketch). - mono_symbol_n: N-ary extension of mono_symbol; bit-stable for the single-type-var case (hash-stability pin Task 1 fires zero times through all eight refactor tasks). - rewrite_class_method_calls → rewrite_mono_calls; interleaved-slot collector preserves the positional-cursor invariant across both channels. - workspace_has_typeclasses → workspace_has_specialisable_targets (principled correctness; old predicate happened to already be true for every user workspace because the prelude is auto-injected). Codegen cleanup: - Two call sites of lower_polymorphic_call deleted (codegen/src/lib.rs lines ~1939-1945, ~1965-1973). - lower_polymorphic_call body, module_polymorphic_fns field + population + Emitter wiring, mono_queue / mono_emitted, drain loop, emit_specialised_fn — all removed. - is_static_callee collapsed to module_user_fns-only. - subst.rs apply_subst_to_term + descriptor_for_subst removed; synth.rs type_descriptor removed (all dead post-removal). - ail emit-ir command pipeline gains lift_letrecs + monomorphise_workspace pre-passes (pre-iter-23.4 the codegen-time poly path was masking this dependency — emit-ir is now consistent with build). - Net codegen reduction: -285 lines lib.rs, -93 lines subst.rs. Tests: - mono_hash_stability.rs (new): regression pin for six primitive Eq/Ord mono-symbol body hashes; stayed bit-stable through all eight refactor tasks. - mono_unification.rs (new): six tests covering variant-key disjointness, free-fn target emission, free-fn synthesis with rigid substitution, rewrite walker on free fns, identity for poly-free-fn-only workspaces, end-to-end cmp_max_smoke runtime correctness. - typeclass_22b3.rs: three test sites updated to MonoTarget::ClassMethod enum form. - 73 e2e + 18 typeclass + 81 codegen tests all green workspace-wide. DESIGN.md §'Monomorphisation (post-typecheck, pre-codegen)' amended: two-arm fixpoint described, disjoint-keyed dedup, cursor-aligned walker, removed codegen-time path. Bench check (all exit 0, 112 metrics stable): bench/check.py + compile_check.py + cross_lang.py. Plan parent: docs/plans/2026-05-11-iter-23.4.md (fab1685). Spec parent: docs/specs/2026-05-11-23-eq-ord-prelude.md (841d65d). Per-iter journal documents two adapted deviations from the plan (synth-channel vs separate walker; body substitution; emit-ir fix); no spec defects.
This commit is contained in:
@@ -49,7 +49,7 @@ mod synth;
|
||||
|
||||
use escape::NonEscapeSet;
|
||||
use subst::{
|
||||
apply_subst_to_term, apply_subst_to_type, derive_substitution, descriptor_for_subst,
|
||||
apply_subst_to_type, derive_substitution,
|
||||
qualify_local_types_codegen, unify_for_subst,
|
||||
};
|
||||
use synth::{
|
||||
@@ -285,16 +285,16 @@ fn lower_workspace_inner(ws: &Workspace, alloc: AllocStrategy) -> Result<String>
|
||||
|
||||
// Pass 1: per-module top-level symbol tables.
|
||||
// - `module_user_fns`: LLVM-typed FnSig for monomorphic fns. Used by
|
||||
// the call resolver. Polymorphic fns are deliberately excluded —
|
||||
// they don't have a single LLVM signature; specialised entries
|
||||
// appear here on demand during monomorphisation (Iter 12b).
|
||||
// - `module_def_ail_types`: AILang `Type` for every fn-typed def
|
||||
// (poly or mono). Used by the codegen-side type tracker to derive
|
||||
// substitutions at polymorphic call sites and to look up the
|
||||
// original `Forall` body when specialising.
|
||||
// the call resolver. Post-iter-23.4 the workspace contains ONLY
|
||||
// monomorphic `Def::Fn`s (the typecheck-time mono pass synthesises
|
||||
// one per concrete instantiation); any residual `Type::Forall` ty
|
||||
// is a stale source def kept for round-trip / `ail diff` purposes
|
||||
// and is intentionally not lowered.
|
||||
// - `module_def_ail_types`: AILang `Type` for every fn-typed def.
|
||||
// Retained for codegen-side type-tracking utilities (e.g.
|
||||
// `synth_arg_type` for ctor-arg type inference).
|
||||
let mut module_user_fns: BTreeMap<String, BTreeMap<String, FnSig>> = BTreeMap::new();
|
||||
let mut module_def_ail_types: BTreeMap<String, BTreeMap<String, Type>> = BTreeMap::new();
|
||||
let mut module_polymorphic_fns: BTreeMap<String, BTreeMap<String, FnDef>> = BTreeMap::new();
|
||||
// Iter 15a: cross-module ctor table. Maps module name → ctor name →
|
||||
// CtorRef (with `type_name` *unqualified*, since the ctor is defined
|
||||
// in that module). Cross-module ctor lookups resolve through this
|
||||
@@ -309,26 +309,20 @@ fn lower_workspace_inner(ws: &Workspace, alloc: AllocStrategy) -> Result<String>
|
||||
for (mname, m) in &ws.modules {
|
||||
let mut user_fns = BTreeMap::new();
|
||||
let mut ail_types = BTreeMap::new();
|
||||
let mut poly_fns = BTreeMap::new();
|
||||
let mut ctors = BTreeMap::new();
|
||||
for def in &m.defs {
|
||||
if let Def::Fn(f) = def {
|
||||
ail_types.insert(f.name.clone(), f.ty.clone());
|
||||
match &f.ty {
|
||||
Type::Fn { params, ret, .. } => {
|
||||
let psig: Result<Vec<String>> = params.iter().map(llvm_type).collect();
|
||||
let rsig = llvm_type(ret);
|
||||
if let (Ok(params), Ok(ret)) = (psig, rsig) {
|
||||
user_fns.insert(f.name.clone(), FnSig { params, ret });
|
||||
}
|
||||
if let Type::Fn { params, ret, .. } = &f.ty {
|
||||
let psig: Result<Vec<String>> = params.iter().map(llvm_type).collect();
|
||||
let rsig = llvm_type(ret);
|
||||
if let (Ok(params), Ok(ret)) = (psig, rsig) {
|
||||
user_fns.insert(f.name.clone(), FnSig { params, ret });
|
||||
}
|
||||
Type::Forall { .. } => {
|
||||
// Polymorphic def — no LLVM sig now; specialised
|
||||
// versions get queued as call sites are lowered.
|
||||
poly_fns.insert(f.name.clone(), f.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// iter 23.4: `Type::Forall`-quantified defs are
|
||||
// intentionally skipped — the mono pass has already
|
||||
// produced their monomorphic counterparts.
|
||||
}
|
||||
if let Def::Type(td) = def {
|
||||
for (i, c) in td.ctors.iter().enumerate() {
|
||||
@@ -360,7 +354,6 @@ fn lower_workspace_inner(ws: &Workspace, alloc: AllocStrategy) -> Result<String>
|
||||
}
|
||||
module_user_fns.insert(mname.clone(), user_fns);
|
||||
module_def_ail_types.insert(mname.clone(), ail_types);
|
||||
module_polymorphic_fns.insert(mname.clone(), poly_fns);
|
||||
module_ctor_index.insert(mname.clone(), ctors);
|
||||
module_consts.insert(mname.clone(), consts);
|
||||
}
|
||||
@@ -396,7 +389,6 @@ fn lower_workspace_inner(ws: &Workspace, alloc: AllocStrategy) -> Result<String>
|
||||
mname,
|
||||
&module_user_fns,
|
||||
&module_def_ail_types,
|
||||
&module_polymorphic_fns,
|
||||
&module_ctor_index,
|
||||
&module_consts,
|
||||
import_map,
|
||||
@@ -555,15 +547,6 @@ struct Emitter<'a> {
|
||||
/// `Forall` for polymorphic defs (used to derive substitutions at
|
||||
/// monomorphic call sites). Populated in pass 1 of `lower_workspace`.
|
||||
module_def_ail_types: &'a BTreeMap<String, BTreeMap<String, Type>>,
|
||||
/// Polymorphic defs per module — full FnDef so we can specialise
|
||||
/// the body when monomorphising. Mono fns aren't included.
|
||||
module_polymorphic_fns: &'a BTreeMap<String, BTreeMap<String, FnDef>>,
|
||||
/// Iter 12b: queue of (module, def, substitution) tuples that need
|
||||
/// to be emitted as specialised versions of polymorphic defs.
|
||||
/// `mono_emitted` tracks the same keys to deduplicate. The descriptor
|
||||
/// string is the deterministic name suffix (`Int`, `Int_Bool`, ...).
|
||||
mono_queue: Vec<(String, String, BTreeMap<String, Type>, String)>,
|
||||
mono_emitted: BTreeSet<(String, String, String)>, // (module, def, descriptor)
|
||||
/// Import map of the current module (alias/module name → actual module name).
|
||||
import_map: BTreeMap<String, String>,
|
||||
/// ADT table: type_name -> list of ctors in definition order.
|
||||
@@ -723,7 +706,6 @@ impl<'a> Emitter<'a> {
|
||||
module_name: &'a str,
|
||||
module_user_fns: &'a BTreeMap<String, BTreeMap<String, FnSig>>,
|
||||
module_def_ail_types: &'a BTreeMap<String, BTreeMap<String, Type>>,
|
||||
module_polymorphic_fns: &'a BTreeMap<String, BTreeMap<String, FnDef>>,
|
||||
module_ctor_index: &'a BTreeMap<String, BTreeMap<String, CtorRef>>,
|
||||
module_consts: &'a BTreeMap<String, BTreeMap<String, ConstDef>>,
|
||||
import_map: BTreeMap<String, String>,
|
||||
@@ -774,9 +756,6 @@ impl<'a> Emitter<'a> {
|
||||
str_counter: 0,
|
||||
module_user_fns,
|
||||
module_def_ail_types,
|
||||
module_polymorphic_fns,
|
||||
mono_queue: Vec::new(),
|
||||
mono_emitted: BTreeSet::new(),
|
||||
import_map,
|
||||
types,
|
||||
module_ctor_index,
|
||||
@@ -836,18 +815,13 @@ impl<'a> Emitter<'a> {
|
||||
Def::Class(_) | Def::Instance(_) => {}
|
||||
}
|
||||
}
|
||||
// Drain the monomorphisation queue. Specialised fns may
|
||||
// themselves invoke polymorphic defs and queue further entries,
|
||||
// so iterate until empty.
|
||||
while let Some((owner_module, def_name, subst, descriptor)) = self.mono_queue.pop() {
|
||||
self.emit_specialised_fn(&owner_module, &def_name, &subst, &descriptor)
|
||||
.map_err(|e| {
|
||||
CodegenError::Def(
|
||||
format!("{def_name}__{descriptor}"),
|
||||
Box::new(e),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
// iter 23.4: the monomorphisation queue is gone — the
|
||||
// typecheck-time mono pass synthesises every specialised
|
||||
// `Def::Fn` before codegen runs (see
|
||||
// `ailang_check::mono::monomorphise_workspace`). Codegen
|
||||
// sees only monomorphic defs; the drain loop and
|
||||
// `emit_specialised_fn` have been removed.
|
||||
|
||||
// Iter 18c.4: per-ADT drop functions. Emitted only under
|
||||
// `--alloc=rc`. One `void @drop_<module>_<TypeName>(ptr)` per
|
||||
// `Def::Type` in the current module — the call site for
|
||||
@@ -890,65 +864,6 @@ impl<'a> Emitter<'a> {
|
||||
/// calls `emit_fn` against a synthetic FnDef whose `name` already
|
||||
/// contains the descriptor — the existing mangling concatenates
|
||||
/// `ail_<module>_<name>` and produces the desired symbol.
|
||||
fn emit_specialised_fn(
|
||||
&mut self,
|
||||
owner_module: &str,
|
||||
def_name: &str,
|
||||
subst: &BTreeMap<String, Type>,
|
||||
descriptor: &str,
|
||||
) -> Result<()> {
|
||||
let fdef = self
|
||||
.module_polymorphic_fns
|
||||
.get(owner_module)
|
||||
.and_then(|m| m.get(def_name))
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
CodegenError::Internal(format!(
|
||||
"emit_specialised_fn: `{owner_module}.{def_name}` not registered"
|
||||
))
|
||||
})?;
|
||||
let inner_ty = match &fdef.ty {
|
||||
Type::Forall { body, .. } => (**body).clone(),
|
||||
other => other.clone(),
|
||||
};
|
||||
let mono_ty = apply_subst_to_type(&inner_ty, subst);
|
||||
let mono_body = apply_subst_to_term(&fdef.body, subst);
|
||||
let synthetic = FnDef {
|
||||
name: format!("{def_name}__{descriptor}"),
|
||||
ty: mono_ty,
|
||||
params: fdef.params.clone(),
|
||||
body: mono_body,
|
||||
suppress: vec![],
|
||||
doc: fdef.doc.clone(),
|
||||
};
|
||||
// Specialised def belongs to the polymorphic def's owner
|
||||
// module, not necessarily self.module_name. Swap module_name
|
||||
// briefly so mangling stays correct.
|
||||
let saved_module = self.module_name;
|
||||
// SAFETY: we rebind module_name through a raw pointer cast
|
||||
// because the field is `&'a str`. Equivalent: hand
|
||||
// emit_fn the mangling target via a parameter. Simpler to
|
||||
// restore.
|
||||
// Instead of unsafe, we just call emit_fn directly — the
|
||||
// mangling uses self.module_name which is &'a str but the
|
||||
// owner_module string lives inside self.module_polymorphic_fns,
|
||||
// also borrowed for 'a, so we can re-borrow it.
|
||||
let owner_ref: &'a str = self
|
||||
.module_polymorphic_fns
|
||||
.keys()
|
||||
.find(|k| k.as_str() == owner_module)
|
||||
.map(|s| s.as_str())
|
||||
.ok_or_else(|| {
|
||||
CodegenError::Internal(format!(
|
||||
"owner module `{owner_module}` not in module_polymorphic_fns"
|
||||
))
|
||||
})?;
|
||||
self.module_name = owner_ref;
|
||||
let r = self.emit_fn(&synthetic);
|
||||
self.module_name = saved_module;
|
||||
r
|
||||
}
|
||||
|
||||
fn emit_const(&mut self, c: &ConstDef) -> Result<()> {
|
||||
// Iter 15b: non-literal const values (e.g. ctor expressions) are
|
||||
// not emitted as globals. They are inlined at every `Term::Var`
|
||||
@@ -1935,14 +1850,12 @@ impl<'a> Emitter<'a> {
|
||||
"cross-module call `{name}`: prefix `{prefix}` not in import map"
|
||||
))
|
||||
})?;
|
||||
// Polymorphic def in target module? Monomorphise on demand.
|
||||
if self
|
||||
.module_polymorphic_fns
|
||||
.get(&target_module)
|
||||
.is_some_and(|m| m.contains_key(suffix))
|
||||
{
|
||||
return self.lower_polymorphic_call(&target_module, suffix, args, tail);
|
||||
}
|
||||
// iter 23.4: codegen-time poly-call dispatch removed. Post-mono
|
||||
// every poly call site has been rewritten by `rewrite_mono_calls`
|
||||
// to a monomorphic symbol, so the lookup-ladder below sees only
|
||||
// user fns / consts / builtins. The pre-iter-23.4 path checked
|
||||
// `module_polymorphic_fns` and dispatched to `lower_polymorphic_call`;
|
||||
// both are gone (and the supporting Emitter fields with them).
|
||||
let target_fns = self
|
||||
.module_user_fns
|
||||
.get(&target_module)
|
||||
@@ -1962,15 +1875,7 @@ impl<'a> Emitter<'a> {
|
||||
return self.emit_call(&target_module, suffix, &sig, args, tail);
|
||||
}
|
||||
|
||||
// Polymorphic def in the current module?
|
||||
if self
|
||||
.module_polymorphic_fns
|
||||
.get(self.module_name)
|
||||
.is_some_and(|m| m.contains_key(name))
|
||||
{
|
||||
let owner = self.module_name.to_string();
|
||||
return self.lower_polymorphic_call(&owner, name, args, tail);
|
||||
}
|
||||
// iter 23.4: codegen-time poly-call dispatch removed (see above).
|
||||
|
||||
// User function in the current module?
|
||||
if let Some(sig) = self
|
||||
@@ -1987,122 +1892,6 @@ impl<'a> Emitter<'a> {
|
||||
)))
|
||||
}
|
||||
|
||||
/// Iter 12b: lower a direct call to a polymorphic def. Derives the
|
||||
/// substitution from the actual arg types, queues the (def,
|
||||
/// substitution) pair for specialisation if not yet emitted, and
|
||||
/// emits a direct call to the mangled name `@ail_<m>_<def>__<descr>`.
|
||||
fn lower_polymorphic_call(
|
||||
&mut self,
|
||||
owner_module: &str,
|
||||
def_name: &str,
|
||||
args: &[Term],
|
||||
tail: bool,
|
||||
) -> Result<(String, String)> {
|
||||
let fdef = self
|
||||
.module_polymorphic_fns
|
||||
.get(owner_module)
|
||||
.and_then(|m| m.get(def_name))
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
CodegenError::Internal(format!(
|
||||
"lower_polymorphic_call: `{owner_module}.{def_name}` not registered"
|
||||
))
|
||||
})?;
|
||||
let (forall_vars, params, ret) = match &fdef.ty {
|
||||
Type::Forall { vars, constraints: _, body } => match body.as_ref() {
|
||||
Type::Fn { params, ret, .. } => {
|
||||
(vars.clone(), params.clone(), (**ret).clone())
|
||||
}
|
||||
_ => {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"lower_polymorphic_call: `{def_name}` Forall body is not Fn"
|
||||
)));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"lower_polymorphic_call: `{def_name}` is not polymorphic"
|
||||
)));
|
||||
}
|
||||
};
|
||||
// Iter 15a: when we're calling into another module, the fn's
|
||||
// params and ret reference local type names that — from this
|
||||
// call site's perspective — are qualified `module.T`. Qualify
|
||||
// before deriving the substitution so the unification mirrors
|
||||
// what the typechecker has already validated.
|
||||
let (params, ret) = if owner_module != self.module_name {
|
||||
let owner_types = self.collect_owner_local_types(owner_module);
|
||||
(
|
||||
params
|
||||
.iter()
|
||||
.map(|p| qualify_local_types_codegen(p, owner_module, &owner_types))
|
||||
.collect::<Vec<_>>(),
|
||||
qualify_local_types_codegen(&ret, owner_module, &owner_types),
|
||||
)
|
||||
} else {
|
||||
(params, ret)
|
||||
};
|
||||
|
||||
// Derive the substitution by comparing the declared param
|
||||
// types against the actual arg types.
|
||||
let arg_ail_tys: Vec<Type> = args
|
||||
.iter()
|
||||
.map(|a| self.synth_arg_type(a))
|
||||
.collect::<Result<_>>()?;
|
||||
let subst = derive_substitution(&forall_vars, ¶ms, &arg_ail_tys)?;
|
||||
|
||||
// Specialised types and signature.
|
||||
let mono_params: Vec<Type> = params
|
||||
.iter()
|
||||
.map(|p| apply_subst_to_type(p, &subst))
|
||||
.collect();
|
||||
let mono_ret = apply_subst_to_type(&ret, &subst);
|
||||
let llvm_params: Vec<String> = mono_params.iter().map(llvm_type).collect::<Result<_>>()?;
|
||||
let llvm_ret = llvm_type(&mono_ret)?;
|
||||
let descriptor = descriptor_for_subst(&forall_vars, &subst);
|
||||
let mangled = format!("ail_{owner_module}_{def_name}__{descriptor}");
|
||||
|
||||
// Queue specialisation (deduplicated).
|
||||
let key = (owner_module.to_string(), def_name.to_string(), descriptor.clone());
|
||||
if self.mono_emitted.insert(key) {
|
||||
self.mono_queue.push((
|
||||
owner_module.to_string(),
|
||||
def_name.to_string(),
|
||||
subst.clone(),
|
||||
descriptor,
|
||||
));
|
||||
}
|
||||
|
||||
// Lower args and emit a direct call to the mangled name.
|
||||
let mut compiled_args = Vec::new();
|
||||
for (a, exp_ty) in args.iter().zip(llvm_params.iter()) {
|
||||
let (v, vty) = self.lower_term(a)?;
|
||||
if &vty != exp_ty {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"poly call `{owner_module}.{def_name}` arg type mismatch: expected {exp_ty}, got {vty}"
|
||||
)));
|
||||
}
|
||||
compiled_args.push((v, vty));
|
||||
}
|
||||
let arglist = compiled_args
|
||||
.iter()
|
||||
.map(|(v, t)| format!("{t} {v}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let dst = self.fresh_ssa();
|
||||
let call_kw = if tail { "musttail call" } else { "call" };
|
||||
self.body.push_str(&format!(
|
||||
" {dst} = {call_kw} {ret} @{mangled}({arglist})\n",
|
||||
ret = llvm_ret,
|
||||
));
|
||||
if tail {
|
||||
self.body
|
||||
.push_str(&format!(" ret {ret} {dst}\n", ret = llvm_ret));
|
||||
self.block_terminated = true;
|
||||
}
|
||||
Ok((dst, llvm_ret))
|
||||
}
|
||||
|
||||
fn emit_call(
|
||||
&mut self,
|
||||
target_module: &str,
|
||||
@@ -2250,14 +2039,10 @@ impl<'a> Emitter<'a> {
|
||||
if name.matches('.').count() == 1 {
|
||||
return true;
|
||||
}
|
||||
if self
|
||||
.module_user_fns
|
||||
.get(self.module_name)
|
||||
.is_some_and(|m| m.contains_key(name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
self.module_polymorphic_fns
|
||||
// iter 23.4: poly-def arm dropped — post-mono there are no
|
||||
// `Type::Forall` defs in `module_user_fns` to begin with, and
|
||||
// `module_polymorphic_fns` no longer exists.
|
||||
self.module_user_fns
|
||||
.get(self.module_name)
|
||||
.is_some_and(|m| m.contains_key(name))
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ use ailang_core::ast::*;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use super::{CodegenError, Result};
|
||||
use crate::synth::type_descriptor;
|
||||
|
||||
/// Iter 12b: derive a name → concrete-type substitution from the
|
||||
/// declared params of a `Forall` body and the actual arg types at a
|
||||
@@ -233,89 +232,9 @@ pub(crate) fn apply_subst_to_type(t: &Type, subst: &BTreeMap<String, Type>) -> T
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 12b: substitute rigid type vars throughout a Term. Only
|
||||
/// `Term::Lam` carries types in the AST (params/ret), so most arms
|
||||
/// just recurse. `Term::Var` contains a name string only and is
|
||||
/// left untouched.
|
||||
pub(crate) fn apply_subst_to_term(t: &Term, subst: &BTreeMap<String, Type>) -> Term {
|
||||
match t {
|
||||
Term::Lit { .. } | Term::Var { .. } => t.clone(),
|
||||
Term::App { callee, args, tail } => Term::App {
|
||||
callee: Box::new(apply_subst_to_term(callee, subst)),
|
||||
args: args.iter().map(|a| apply_subst_to_term(a, subst)).collect(),
|
||||
tail: *tail,
|
||||
},
|
||||
Term::Let { name, value, body } => Term::Let {
|
||||
name: name.clone(),
|
||||
value: Box::new(apply_subst_to_term(value, subst)),
|
||||
body: Box::new(apply_subst_to_term(body, subst)),
|
||||
},
|
||||
Term::If { cond, then, else_ } => Term::If {
|
||||
cond: Box::new(apply_subst_to_term(cond, subst)),
|
||||
then: Box::new(apply_subst_to_term(then, subst)),
|
||||
else_: Box::new(apply_subst_to_term(else_, subst)),
|
||||
},
|
||||
Term::Do { op, args, tail } => Term::Do {
|
||||
op: op.clone(),
|
||||
args: args.iter().map(|a| apply_subst_to_term(a, subst)).collect(),
|
||||
tail: *tail,
|
||||
},
|
||||
Term::Ctor { type_name, ctor, args } => Term::Ctor {
|
||||
type_name: type_name.clone(),
|
||||
ctor: ctor.clone(),
|
||||
args: args.iter().map(|a| apply_subst_to_term(a, subst)).collect(),
|
||||
},
|
||||
Term::Match { scrutinee, arms } => Term::Match {
|
||||
scrutinee: Box::new(apply_subst_to_term(scrutinee, subst)),
|
||||
arms: arms
|
||||
.iter()
|
||||
.map(|a| Arm {
|
||||
pat: a.pat.clone(),
|
||||
body: apply_subst_to_term(&a.body, subst),
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
Term::Lam { params, param_tys, ret_ty, effects, body } => Term::Lam {
|
||||
params: params.clone(),
|
||||
param_tys: param_tys
|
||||
.iter()
|
||||
.map(|t| apply_subst_to_type(t, subst))
|
||||
.collect(),
|
||||
ret_ty: Box::new(apply_subst_to_type(ret_ty, subst)),
|
||||
effects: effects.clone(),
|
||||
body: Box::new(apply_subst_to_term(body, subst)),
|
||||
},
|
||||
Term::Seq { lhs, rhs } => Term::Seq {
|
||||
lhs: Box::new(apply_subst_to_term(lhs, subst)),
|
||||
rhs: Box::new(apply_subst_to_term(rhs, subst)),
|
||||
},
|
||||
Term::LetRec { .. } => {
|
||||
// Iter 16b.1: eliminated by desugar before any
|
||||
// monomorphisation pass runs.
|
||||
unreachable!("Term::LetRec eliminated by desugar")
|
||||
}
|
||||
Term::Clone { value } => Term::Clone {
|
||||
// Iter 18c.1: structural recursion through the wrapper.
|
||||
value: Box::new(apply_subst_to_term(value, subst)),
|
||||
},
|
||||
Term::ReuseAs { source, body } => Term::ReuseAs {
|
||||
// Iter 18d.1: structural recursion through both children.
|
||||
source: Box::new(apply_subst_to_term(source, subst)),
|
||||
body: Box::new(apply_subst_to_term(body, subst)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 12b: deterministic descriptor string for a substitution. Used
|
||||
/// as the suffix in the mangled name `@ail_<m>_<def>__<descriptor>`.
|
||||
/// Vars are emitted in the order given by the FnDef's forall vars
|
||||
/// (so two call sites with the same instantiation map to the same
|
||||
/// descriptor regardless of internal BTreeMap ordering).
|
||||
pub(crate) fn descriptor_for_subst(vars: &[String], subst: &BTreeMap<String, Type>) -> String {
|
||||
let mut parts: Vec<String> = Vec::with_capacity(vars.len());
|
||||
for v in vars {
|
||||
let ty = subst.get(v).cloned().unwrap_or_else(|| Type::unit());
|
||||
parts.push(type_descriptor(&ty));
|
||||
}
|
||||
parts.join("_")
|
||||
}
|
||||
// iter 23.4: `apply_subst_to_term` and `descriptor_for_subst` were
|
||||
// the codegen-side body-substitution and symbol-mangling helpers used
|
||||
// by `lower_polymorphic_call` / `emit_specialised_fn`. Both are gone:
|
||||
// the typecheck-time mono pass synthesises every monomorphic body
|
||||
// (via `ailang_check::substitute_rigids_in_term`) and produces
|
||||
// surface-named mono symbols (via `ailang_check::mono::mono_symbol_n`).
|
||||
|
||||
@@ -196,48 +196,11 @@ pub(crate) fn builtin_effect_op_ret(op: &str) -> Option<Type> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Iter 12b: a stable, identifier-safe descriptor for a `Type`.
|
||||
/// Maps `Int → I`, `Bool → B`, `Unit → U`, `Str → S`, ADT name `Foo →
|
||||
/// FFoo`, fn → `F<params...>R<ret>` (no recursion guard since types in
|
||||
/// the MVP are non-recursive at the type level).
|
||||
pub(crate) fn type_descriptor(t: &Type) -> String {
|
||||
match t {
|
||||
Type::Con { name, args } => {
|
||||
let head = match name.as_str() {
|
||||
"Int" => "I".into(),
|
||||
"Bool" => "B".into(),
|
||||
"Unit" => "U".into(),
|
||||
"Str" => "S".into(),
|
||||
"Float" => "Fl".into(),
|
||||
other => format!("F{other}"),
|
||||
};
|
||||
if args.is_empty() {
|
||||
head
|
||||
} else {
|
||||
// Iter 13a: parameterised ADTs get their type-arg
|
||||
// descriptors appended, e.g. `FBox` of `Int` → `FBox_I`.
|
||||
let mut s = head;
|
||||
for a in args {
|
||||
s.push('_');
|
||||
s.push_str(&type_descriptor(a));
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
Type::Fn { params, ret, .. } => {
|
||||
let mut s = String::from("Fn");
|
||||
for p in params {
|
||||
s.push('_');
|
||||
s.push_str(&type_descriptor(p));
|
||||
}
|
||||
s.push_str("__r_");
|
||||
s.push_str(&type_descriptor(ret));
|
||||
s
|
||||
}
|
||||
Type::Var { name } => format!("V{name}"),
|
||||
Type::Forall { .. } => "FORALL".into(),
|
||||
}
|
||||
}
|
||||
// iter 23.4: `type_descriptor` was the helper that mangled a `Type`
|
||||
// into an identifier-safe suffix for the codegen-side mono mangling
|
||||
// (`Int → I`, `Bool → B`, etc.). The unified mono pass produces
|
||||
// surface-named mono symbols instead (`Int → "Int"`, parameterised
|
||||
// via 8-hex hash) — see `ailang_check::mono::mono_symbol_n`.
|
||||
|
||||
/// Floats iter 4.2: arithmetic / comparison ops are type-dispatched
|
||||
/// over `{Int, Float}`. Caller (`lower_app`) resolves the arg type
|
||||
|
||||
Reference in New Issue
Block a user