c77fd1b85e
Float is referenced nowhere else in the codebase; the plan-text listed it aspirationally. Restored parity with the four other primitive enumerations across the crate. When Float lands as a real type (post-22b.4 Prelude work, if at all), the arm gets re-added in the same iter that introduces Type::float(). Deferred (not part of this fix): - Shared-constant extraction across the four primitive enumeration sites (linearity.rs:143, lib.rs:1232/2214, mono.rs) — workspace- level cleanup belonging to the milestone-22 audit phase. - pub mod mono vs sibling pub use lift::lift_letrecs style — plan prescribed pub mod for 22b.3 because Tasks 3-7 add multiple pub items reached by integration tests; per-item pub use would proliferate.
132 lines
5.5 KiB
Rust
132 lines
5.5 KiB
Rust
//! 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<Workspace> {
|
|
// 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 `<method>#<surface-name>` for diagnostic and
|
|
/// ABI legibility. All other types — parameterised cons,
|
|
/// user-defined ADTs, function types — fall to
|
|
/// `<method>#<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<args>` (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,
|
|
}
|
|
}
|