iter 22b.3.1: mono pass skeleton — identity for class-free workspaces
This commit is contained in:
@@ -2032,6 +2032,13 @@ fn build_to(
|
|||||||
// load-time remains valid.
|
// load-time remains valid.
|
||||||
registry: ws.registry.clone(),
|
registry: ws.registry.clone(),
|
||||||
};
|
};
|
||||||
|
// Iter 22b.3: monomorphisation pass. After `lift_letrecs` and
|
||||||
|
// before codegen, every (class-method, concrete-type) call-site
|
||||||
|
// pair becomes a synthesised top-level fn; user call sites are
|
||||||
|
// rewritten to target those names. Class-free workspaces flow
|
||||||
|
// through bit-identically; see `ailang_check::monomorphise_workspace`.
|
||||||
|
let ws = ailang_check::monomorphise_workspace(&ws)
|
||||||
|
.map_err(|e| anyhow::anyhow!("monomorphise_workspace: {e}"))?;
|
||||||
let ir = ailang_codegen::lower_workspace_with_alloc(&ws, alloc)?;
|
let ir = ailang_codegen::lower_workspace_with_alloc(&ws, alloc)?;
|
||||||
let tmpdir = std::env::temp_dir().join(format!("ailang-{}", std::process::id()));
|
let tmpdir = std::env::temp_dir().join(format!("ailang-{}", std::process::id()));
|
||||||
std::fs::create_dir_all(&tmpdir)?;
|
std::fs::create_dir_all(&tmpdir)?;
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//! Iter 22b.3: monomorphisation pass tests.
|
||||||
|
//!
|
||||||
|
//! Co-located with `typeclass_22b2.rs` so the typeclass-feature
|
||||||
|
//! coverage is browsable in one directory. Tests use the same
|
||||||
|
//! `examples_dir()` pattern — `cargo test`-cwd is the crate dir,
|
||||||
|
//! the fixtures live in `<repo>/examples/`.
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn examples_dir() -> PathBuf {
|
||||||
|
// crates/ail/ → repo root → examples/
|
||||||
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
d.pop();
|
||||||
|
d.pop();
|
||||||
|
d.join("examples")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn monomorphise_workspace_is_identity_on_class_free_workspace() {
|
||||||
|
// Pick any class-free fixture; `examples/hello.ail.json` is
|
||||||
|
// the canonical class-free smoke test.
|
||||||
|
let entry = examples_dir().join("hello.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 on class-free workspace");
|
||||||
|
|
||||||
|
// Module set unchanged.
|
||||||
|
let before = ailang_core::load_workspace(&examples_dir().join("hello.ail.json"))
|
||||||
|
.expect("re-load");
|
||||||
|
assert_eq!(
|
||||||
|
ws.modules.keys().collect::<Vec<_>>(),
|
||||||
|
before.modules.keys().collect::<Vec<_>>(),
|
||||||
|
"module set must be identical"
|
||||||
|
);
|
||||||
|
// Per-module def list unchanged (compare by canonical-JSON hash).
|
||||||
|
for (mname, m) in &ws.modules {
|
||||||
|
let before_m = &before.modules[mname];
|
||||||
|
let h_after = ailang_core::module_hash(m);
|
||||||
|
let h_before = ailang_core::module_hash(before_m);
|
||||||
|
assert_eq!(
|
||||||
|
h_after, h_before,
|
||||||
|
"class-free module `{}` must hash-identical through mono",
|
||||||
|
mname
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -251,9 +251,11 @@ fn unify(a: &Type, b: &Type, subst: &mut Subst) -> Result<()> {
|
|||||||
pub mod builtins;
|
pub mod builtins;
|
||||||
pub mod diagnostic;
|
pub mod diagnostic;
|
||||||
pub mod lift;
|
pub mod lift;
|
||||||
|
mod mono;
|
||||||
|
|
||||||
pub use diagnostic::{Diagnostic, Severity};
|
pub use diagnostic::{Diagnostic, Severity};
|
||||||
pub use lift::lift_letrecs;
|
pub use lift::lift_letrecs;
|
||||||
|
pub use mono::monomorphise_workspace;
|
||||||
|
|
||||||
/// Internal error type produced by the typechecker.
|
/// Internal error type produced by the typechecker.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
//! Iter 22b.3: workspace monomorphisation pass.
|
||||||
|
//!
|
||||||
|
//! Slots into the build pipeline after [`crate::lift_letrecs`] and
|
||||||
|
//! before `ailang_codegen::lower_workspace_with_alloc`. The pass
|
||||||
|
//! consumes the workspace's typeclass [`Registry`] and the per-
|
||||||
|
//! module class-method tables (already populated by
|
||||||
|
//! [`crate::check_in_workspace`]) and produces 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.
|
||||||
|
//!
|
||||||
|
//! Class-free workspaces are byte-identical through the pass:
|
||||||
|
//! the early-out check at the top of [`monomorphise_workspace`]
|
||||||
|
//! returns the input clone untouched.
|
||||||
|
//!
|
||||||
|
//! ## Symbol-hashing invariant
|
||||||
|
//!
|
||||||
|
//! Synthesised FnDefs are post-typecheck artefacts. They do 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`].
|
||||||
|
|
||||||
|
use ailang_core::ast::Def;
|
||||||
|
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(_)))
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user