//! Iter 22b.3: workspace monomorphisation pass. //! //! Slots into the build pipeline after [`crate::lift_letrecs`] and //! before `ailang_codegen::lower_workspace_with_alloc`. //! //! ## What Task 1 delivers //! //! Skeleton only. [`monomorphise_workspace`] is a no-op pass with //! two branches: //! //! - Class-free workspaces (no [`Def::Class`] / [`Def::Instance`] //! anywhere) take the early-out and the input workspace is //! cloned unchanged. //! - Class-present workspaces fall through to a second //! `Ok(ws.clone())`, dead-equivalent today but the placeholder //! for the real fixpoint body that later tasks will fill in. //! //! Both branches return a byte-identical workspace clone, so //! `module_hash` is preserved end-to-end through the pass at this //! stage of the iteration. //! //! ## Planned (later tasks in iter 22b.3) //! //! See `docs/plans/2026-05-09-22b3-monomorphisation.md` for the //! full task list. The eventual contract is for the pass to //! consume the workspace's typeclass [`Registry`] and the per- //! module class-method tables (already populated by //! [`crate::check_in_workspace`]) and produce a workspace with: //! //! 1. Synthesised [`Def::Fn`] entries — one per unique //! `(class, method, type-hash)` triple observed at any //! class-method call site — appended to the [`Registry`]'s //! `defining_module` for that instance. //! 2. Rewritten call sites — every `Term::Var { name }` whose //! `name` resolves through [`Env::class_methods`] is replaced //! by the corresponding mono-symbol name (qualified with the //! instance's `defining_module` iff that module differs from //! the calling module). //! //! Pre-existing `Def::Class` and `Def::Instance` entries are //! preserved verbatim — codegen ignores them, but downstream //! tooling (e.g. `ail describe`) may still consult them. //! //! ## Symbol-hashing invariant (eventual-state) //! //! Once the synthesis body lands, synthesised FnDefs will be //! post-typecheck artefacts. They will NOT enter //! `CheckedModule.symbols` (built from the original module at //! typecheck time and used by `ail diff` / `ail manifest`). Same //! convention as [`crate::lift_letrecs`]. Documented here ahead of //! the implementation so the constraint is visible to anyone //! extending the skeleton. use ailang_core::ast::{Def, Type}; use ailang_core::workspace::Workspace; use crate::Result; /// Iter 22b.3: workspace-wide monomorphisation pass entry. See the /// module-level doc for the architecture and contract. /// /// Pre-condition: `ws` has been typechecked (`check_workspace(ws)` /// returned no errors) and lifted (`lift_letrecs` per module). The /// pass does not perform new type checking — it queries types via /// `synth` on already-typechecked bodies. pub fn monomorphise_workspace(ws: &Workspace) -> Result { // Fast path — no class / instance defs anywhere → nothing to do. // The pass also has no targets when there are class defs but no // instance defs (no callable methods at concrete types), but // the body walks would still be a wasted traversal; the // class-free check is the cheap way to opt out. if !workspace_has_typeclasses(ws) { 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()) } /// Iter 22b.3: returns `true` iff any module in `ws` declares at /// least one [`Def::Class`] or [`Def::Instance`]. Cheap workspace /// scan; the early-out keeps class-free workspaces byte-identical /// through the pass. fn workspace_has_typeclasses(ws: &Workspace) -> bool { ws.modules.values().any(|m| { m.defs.iter().any(|d| matches!(d, Def::Class(_) | Def::Instance(_))) }) } /// Iter 22b.3: deterministic mono-symbol name for a `(method, /// type)` pair. Primitive types (`Int`, `Bool`, `Str`, `Unit`) /// produce `#` for diagnostic and /// ABI legibility. All other types — parameterised cons, /// user-defined ADTs, function types — fall to /// `#<8-hex-prefix-of-canonical-type-hash>`. The hash /// route ensures uniqueness without requiring a flattened /// surface form for arbitrarily nested types. /// /// Determinism: `ailang_core::canonical::type_hash` is the same /// function `workspace::build_registry` uses to key /// [`Registry::entries`], so a registry-key match implies a /// `mono_symbol` match. pub fn mono_symbol(method: &str, ty: &Type) -> String { if let Some(prim) = primitive_surface_name(ty) { return format!("{method}#{prim}"); } let full_hash = ailang_core::canonical::type_hash(ty); // 8-hex prefix is enough for low-collision keying across the // workspace; the full hash remains in the registry for // disambiguation should one ever be needed. format!("{method}#{}", &full_hash[..8]) } /// Iter 22b.3: returns the surface name iff `ty` is a zero-arity /// primitive `Type::Con`. Used by [`mono_symbol`] to gate the /// human-readable form. The match is intentionally narrow: /// `Int` (which is malformed but parser-accepting) is /// treated as compound, so it falls to the hash form. fn primitive_surface_name(ty: &Type) -> Option<&'static str> { match ty { Type::Con { name, args } if args.is_empty() => match name.as_str() { "Int" => Some("Int"), "Bool" => Some("Bool"), "Str" => Some("Str"), "Unit" => Some("Unit"), _ => None, }, _ => None, } }