From 108115f7ddb7c1f0e61c888af5cef83b77abe4c1 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sat, 9 May 2026 20:18:20 +0200 Subject: [PATCH] =?UTF-8?q?iter=2022b.3.1:=20fix=20=E2=80=94=20quality-rev?= =?UTF-8?q?iew=20(doc=20reframe=20+=20class-present=20test)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/ail/tests/typeclass_22b3.rs | 46 ++++++++++++++++++++++++++++++ crates/ailang-check/src/mono.rs | 44 ++++++++++++++++++++-------- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/crates/ail/tests/typeclass_22b3.rs b/crates/ail/tests/typeclass_22b3.rs index d5c0927..b201b97 100644 --- a/crates/ail/tests/typeclass_22b3.rs +++ b/crates/ail/tests/typeclass_22b3.rs @@ -47,3 +47,49 @@ fn monomorphise_workspace_is_identity_on_class_free_workspace() { ); } } + +/// Property: the Task 1 skeleton's class-present branch is also a +/// no-op. The fixture has a `Def::Class` and a `Def::Instance` (so +/// the early-out does NOT fire) but no concrete class-method call +/// sites yet wired through the pipeline; under the skeleton, the +/// fall-through `Ok(ws.clone())` must preserve every module's +/// canonical hash. +/// +/// NOTE: this test will need updating in Task 5 once the skeleton +/// is replaced with the real fixpoint — at that point synth fns +/// will be appended to the `defining_module`, breaking module-hash +/// equality on that module by design. The test as written +/// guarantees the Task 1 contract and catches accidental partial +/// rewrites landed before Task 5. +#[test] +fn monomorphise_workspace_is_identity_when_no_concrete_call_sites() { + // `test_22b2_instance_present.ail.json` has `class Show` + + // `instance Show Int` and typechecks cleanly; reused across + // 22b.2 tests so its shape is stable. + let entry = examples_dir().join("test_22b2_instance_present.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_after = ailang_check::monomorphise_workspace(&ws) + .expect("mono on class-present workspace"); + + // Module set unchanged. + let before = ailang_core::load_workspace(&entry).expect("re-load"); + assert_eq!( + ws_after.modules.keys().collect::>(), + before.modules.keys().collect::>(), + "module set must be identical" + ); + // Each module hashes byte-identical (skeleton clones unchanged). + for (mname, m) in &ws_after.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-present module `{}` must hash-identical through Task 1 skeleton", + mname + ); + } +} diff --git a/crates/ailang-check/src/mono.rs b/crates/ailang-check/src/mono.rs index d4602ff..9d398db 100644 --- a/crates/ailang-check/src/mono.rs +++ b/crates/ailang-check/src/mono.rs @@ -1,10 +1,31 @@ //! 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- +//! 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 produces a workspace with: +//! [`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 @@ -20,16 +41,15 @@ //! 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 (eventual-state) //! -//! ## 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`]. +//! 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; use ailang_core::workspace::Workspace;