Files
AILang/crates/ail/tests/mono_hash_stability.rs
T
Brummel a1692a4859 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.
2026-05-11 23:59:45 +02:00

53 lines
1.9 KiB
Rust

//! Regression pin for primitive Eq/Ord mono-symbol body hashes.
//!
//! Locks the six primitive mono symbols (`eq__Int`, `eq__Bool`, `eq__Str`,
//! `compare__Int`, `compare__Bool`, `compare__Str`) to their pre-iter-23.4
//! body hashes. The unified mono pass MUST produce bit-identical bodies.
use ailang_core::ast::Def;
use ailang_core::hash::def_hash;
use ailang_core::workspace::load_workspace;
use std::path::PathBuf;
fn fixture_path() -> PathBuf {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.pop();
d.pop();
d.join("examples").join("mono_hash_pin_smoke.ail.json")
}
#[test]
fn primitive_eq_ord_mono_symbol_hashes_stay_bit_identical() {
let ws = load_workspace(&fixture_path()).expect("workspace loads");
let diags = ailang_check::check_workspace(&ws);
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
let post_mono = ailang_check::monomorphise_workspace(&ws).expect("mono green");
// Mono symbols for class methods are appended to the instance's
// `defining_module` — i.e. the prelude module, since all six
// primitive Eq/Ord instances live there.
let prelude_mod = post_mono
.modules
.get("prelude")
.expect("prelude module present");
let pins: &[(&str, &str)] = &[
("eq__Int", "81d59ac38ab3663d"),
("eq__Bool", "11da98a358b5979b"),
("eq__Str", "277516bb7f195b2a"),
("compare__Int", "d5d3f66b86c7e758"),
("compare__Bool", "676e3ea0298a8795"),
("compare__Str", "a532710899cf14fe"),
];
for (sym, pin) in pins {
let def = prelude_mod
.defs
.iter()
.find(|d| matches!(d, Def::Fn(f) if f.name == *sym))
.unwrap_or_else(|| panic!("mono symbol {sym} not found in prelude module"));
let h = def_hash(def);
assert_eq!(&h, pin, "{sym} body hash drifted");
}
}