iter 24.2: class Show + 4 primitive instances + 22b TShow migration

Ships class Show a where show : (a borrow) -> Str in the prelude
plus primitive Show Int / Bool / Str / Float instances. Each
instance body is a single-application lambda invoking the
corresponding runtime primitive (int_to_str, bool_to_str,
str_clone, float_to_str) — first prelude instance bodies to call
runtime primitives directly. Float is included in Show (unlike
Eq/Ord) because textual representation is well-defined modulo the
NaN-spelling caveat at DESIGN.md §Float semantics.

The 22b typeclass test corpus is migrated preemptively: 21 fixture
files and 6 consumer files (typeclass_22b{2,3}.rs +
hash.rs ct4 pin + workspace.rs iter22b1 tests + the
instance_present.prose.txt snapshot) rename class Show / show
to class TShow / tshow, analogous to the existing TEq/TOrd
convention. Migration runs before the prelude additions so the
workspace stays green throughout.

Three new tests pin the post-mono shape: show_mono_synthesis.rs
(existence of show__Int/Bool/Str/Float as Def::Fn entries in the
prelude post-mono module), show_dispatch_pin.rs (bare show and
tshow both Step-2 singletons workspace-globally),
mono_hash_stability.rs::primitive_show_mono_symbol_hashes_stay_bit_identical
(body hashes pinned for the 4 new mono symbols).

DESIGN.md §Prelude (built-in) classes drops Show from the
deferred-features list and appends a milestone-24 paragraph
naming the class signature + the 4 instances + the body shape.
print rewire stays deferred to iter 24.3.

Tests: 552 passed (was 548 + 4 new). bench/compile_check +
bench/cross_lang exit 0. bench/check exits 1 on the recurring
noise envelope per the conservative-call lineage.
This commit is contained in:
2026-05-13 03:31:40 +02:00
parent 64cea0ef41
commit 3286117605
34 changed files with 703 additions and 169 deletions
@@ -0,0 +1,22 @@
{
"iter_id": "24.2",
"date": "2026-05-13",
"mode": "standard",
"outcome": "DONE",
"tasks_total": 8,
"tasks_completed": 8,
"reloops_per_task": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0,
"6": 0,
"7": 0,
"8": 0
},
"review_loops_spec": 0,
"review_loops_quality": 0,
"blocked_reason": null,
"notes": "Task 2 expanded scope inline to recover from a plan undercount on 22b migration consumers (4 additional files: hash.rs ct4 pin, workspace.rs iter22b1 tests, prose snapshot, one mono_symbol_compound_type assertion). Plan defects fixed inline per mq.1 / mq.3 precedent — no formal re-loop, no Boss dispatch. Plan Task 4 fixture literal schema was wrong ({lit: 'int'} flat vs {lit: {kind: 'int', value: 42}} nested) — fixed inline on first round-trip. Plan Task 5 API name was placeholder (Env::from_workspace) — used actual build_check_env. All other tasks first-shot."
}
+42
View File
@@ -16,6 +16,13 @@ fn fixture_path() -> PathBuf {
d.join("examples").join("mono_hash_pin_smoke.ail.json")
}
fn show_fixture_path() -> PathBuf {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.pop();
d.pop();
d.join("examples").join("show_mono_pin_smoke.ail.json")
}
#[test]
fn primitive_eq_ord_mono_symbol_hashes_stay_bit_identical() {
let ws = load_workspace(&fixture_path()).expect("workspace loads");
@@ -50,3 +57,38 @@ fn primitive_eq_ord_mono_symbol_hashes_stay_bit_identical() {
assert_eq!(&h, pin, "{sym} body hash drifted");
}
}
#[test]
fn primitive_show_mono_symbol_hashes_stay_bit_identical() {
// 24.2 hash-stability pin for the four primitive `show__T` mono
// symbols synthesised from `prelude.Show {Int|Bool|Str|Float}`
// instance bodies. Loaded from a separate fixture
// (`show_mono_pin_smoke.ail.json`) so the Eq/Ord pin above stays
// structurally isolated.
let ws = load_workspace(&show_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");
let prelude_mod = post_mono
.modules
.get("prelude")
.expect("prelude module present");
let pins: &[(&str, &str)] = &[
("show__Int", "891eaebe64b3180d"),
("show__Bool", "1bdb621494e50607"),
("show__Str", "3f1d57c90ddce081"),
("show__Float", "5ab0bdd40b9f8b8f"),
];
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; captured: {h}");
}
}
+42
View File
@@ -0,0 +1,42 @@
//! Mono-synthesis existence pin for milestone 24.2's Show class.
//!
//! Asserts that the four primitive Show instances (Int/Bool/Str/Float)
//! cause the mono pass to synthesise `show__Int`, `show__Bool`,
//! `show__Str`, `show__Float` as `Def::Fn` entries in the prelude
//! post-mono module.
use ailang_core::ast::Def;
use ailang_core::workspace::load_workspace;
use ailang_check::{check_workspace, monomorphise_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("show_mono_pin_smoke.ail.json")
}
#[test]
fn primitive_show_mono_symbols_synthesise() {
let ws = load_workspace(&fixture_path()).expect("workspace loads");
let diags = check_workspace(&ws);
assert!(diags.is_empty(), "typecheck diagnostics: {diags:#?}");
let post_mono = monomorphise_workspace(&ws).expect("mono green");
let prelude_mod = post_mono
.modules
.get("prelude")
.expect("prelude module present");
for sym in &["show__Int", "show__Bool", "show__Str", "show__Float"] {
let found = prelude_mod
.defs
.iter()
.any(|d| matches!(d, Def::Fn(f) if f.name == *sym));
assert!(
found,
"mono symbol {sym} not found in prelude module post-mono"
);
}
}
+26 -26
View File
@@ -19,7 +19,7 @@ fn examples_dir() -> PathBuf {
/// Property protected: a class method declared in a `Def::Class` must be
/// reachable as a name in the module's `ModuleGlobals` so that pass-2
/// (`Term::Var { name: "show" }`) does not fire `unbound-var` for
/// (`Term::Var { name: "tshow" }`) does not fire `unbound-var` for
/// well-formed class-method calls. The lookup must remember which class
/// the method belongs to so Task 9 can build the residual constraint at
/// the call site.
@@ -37,15 +37,15 @@ fn class_method_is_in_module_globals() {
// Pre-mq.3 callers passed the bare method name only; post-mq.3 they
// must disambiguate by class.
assert!(
mod_globals.has_class_method("test_22b2_class_method_lookup.Show", "show"),
"class method `show` (in class `Show`) must appear in module globals"
mod_globals.has_class_method("test_22b2_class_method_lookup.TShow", "tshow"),
"class method `tshow` (in class `TShow`) must appear in module globals"
);
// mq.1: class_method_class returns the qualified form
// `<defining_module>.<Class>`.
assert_eq!(
mod_globals.class_method_class("test_22b2_class_method_lookup.Show", "show"),
Some("test_22b2_class_method_lookup.Show"),
"class method `show` must remember its class is `Show` (qualified post-mq.1)"
mod_globals.class_method_class("test_22b2_class_method_lookup.TShow", "tshow"),
Some("test_22b2_class_method_lookup.TShow"),
"class method `tshow` must remember its class is `TShow` (qualified post-mq.1)"
);
}
@@ -54,7 +54,7 @@ fn class_method_is_in_module_globals() {
/// typecheck arms can find the matching `Registry` entries) and the
/// declared method signature (so a residual class constraint can be
/// built by instantiating the class param at the call site). The
/// fixture declares `class Show a { show: a -> Str }`, so the
/// fixture declares `class TShow a { show: a -> Str }`, so the
/// recovered `method_ty` must be exactly `Type::Var "a" -> Type::Con
/// "Str"` with empty effects.
#[test]
@@ -68,8 +68,8 @@ fn class_method_entry_carries_full_metadata() {
.get("test_22b2_class_method_lookup")
.expect("module globals present");
let cm_entry = mod_globals
.class_method("test_22b2_class_method_lookup.Show", "show")
.expect("class method `show` (in class `Show`) must be registered");
.class_method("test_22b2_class_method_lookup.TShow", "tshow")
.expect("class method `tshow` (in class `TShow`) must be registered");
assert_eq!(
cm_entry.defining_module, "test_22b2_class_method_lookup",
@@ -90,7 +90,7 @@ fn class_method_entry_carries_full_metadata() {
assert_eq!(
params.len(),
1,
"method `show` must have exactly one parameter"
"method `tshow` must have exactly one parameter"
);
match &params[0] {
Type::Var { name } => assert_eq!(
@@ -122,7 +122,7 @@ fn class_method_entry_carries_full_metadata() {
}
assert!(
effects.is_empty(),
"method `show` must have empty effects, got {:?}",
"method `tshow` must have empty effects, got {:?}",
effects
);
}
@@ -134,7 +134,7 @@ fn class_method_entry_carries_full_metadata() {
}
/// Property protected: a polymorphic FnDef that calls a class method
/// (e.g. `show x` where `x: a`) but does not declare the matching class
/// (e.g. `tshow x` where `x: a`) but does not declare the matching class
/// constraint in its `Forall.constraints` MUST receive a
/// `missing-constraint` diagnostic. This is the principal soundness
/// guarantee of class-method calls inside polymorphic functions: every
@@ -160,7 +160,7 @@ fn fn_calling_class_method_without_declared_constraint_fires_missing_constraint(
/// method typechecks clean — the residual constraint produced at the
/// call site is satisfied by the in-scope declared constraint, and no
/// diagnostic fires. This is the positive counterpart to the
/// missing-constraint check: declaring `Show a =>` makes `show x`
/// missing-constraint check: declaring `Show a =>` makes `tshow x`
/// well-formed inside the body.
#[test]
fn fn_with_correct_constraint_typechecks_green() {
@@ -225,7 +225,7 @@ fn fn_with_superclass_constraint_calling_subclass_method_fires_missing_constrain
/// concrete type, the fn cannot push the obligation to a caller via a
/// `Forall.constraints` annotation — it can only be discharged by an
/// existing instance. Without this check, monomorphisation in 22b.3 would
/// have nothing to specialise to. Fixture: `main` calls `show 42` (so the
/// have nothing to specialise to. Fixture: `main` calls `tshow 42` (so the
/// residual is `(Show, Int)`) without any `instance Show Int` declared in
/// the workspace.
#[test]
@@ -250,7 +250,7 @@ fn fn_calling_class_method_at_type_without_instance_fires_no_instance() {
/// otherwise sneak past the negative-only test: a registry that is empty
/// at typecheck time, or keyed under the wrong shape, would silently fire
/// `no-instance` even when the instance is present. Fixture: `main` calls
/// `show 42` with `instance Show Int` declared in the same workspace.
/// `tshow 42` with `instance Show Int` declared in the same workspace.
#[test]
fn fn_calling_class_method_at_type_with_instance_typechecks_green() {
let entry = examples_dir().join("test_22b2_instance_present.ail.json");
@@ -268,13 +268,13 @@ fn fn_calling_class_method_at_type_with_instance_typechecks_green() {
/// reachable from a fn in module B that imports A — i.e. the workspace-
/// wide merge of `module_globals.class_methods` into `Env.class_methods`
/// (per `check_in_workspace`) is what call-site resolution actually
/// consults. Without this merge, a `Term::Var { name: "show" }` in B would
/// fire `unbound-var`. The discriminator: when B's `main` calls `show 42`
/// consults. Without this merge, a `Term::Var { name: "tshow" }` in B would
/// fire `unbound-var`. The discriminator: when B's `main` calls `tshow 42`
/// at a fully-concrete type and A has NO matching instance, the diagnostic
/// must be `no-instance` (proving the class method resolved cross-module),
/// NOT `unbound-var` (which would mean cross-module class-method lookup
/// failed silently). Fixture: A = `test_22b2_xmod_classmod_noinst` declares
/// `class Show a`; B = `test_22b2_xmod_no_instance` calls `show 42`.
/// `class TShow a`; B = `test_22b2_xmod_no_instance` calls `tshow 42`.
#[test]
fn cross_module_class_method_resolves_and_fires_no_instance() {
let entry = examples_dir().join("test_22b2_xmod_no_instance.ail.json");
@@ -289,7 +289,7 @@ fn cross_module_class_method_resolves_and_fires_no_instance() {
);
assert!(
!codes.contains(&"unbound-var"),
"must NOT fire unbound-var — class method `show` from module A is in scope in module B; got: {:?}",
"must NOT fire unbound-var — class method `tshow` from module A is in scope in module B; got: {:?}",
codes
);
}
@@ -297,14 +297,14 @@ fn cross_module_class_method_resolves_and_fires_no_instance() {
/// Property protected: a polymorphic fn in module B calling a class method
/// declared in module A (without declaring the matching constraint) MUST
/// fire `missing-constraint`. This pins the cross-module class-superclass
/// expansion path: B's `Forall.constraints` is empty, A's `class Show a`
/// expansion path: B's `Forall.constraints` is empty, A's `class TShow a`
/// is in `Env.class_methods` via the workspace merge, and the residual
/// `(Show, a)` collected at the `show x` call site must reach `check_fn`'s
/// `(Show, a)` collected at the `tshow x` call site must reach `check_fn`'s
/// missing-constraint arm. Without the cross-module merge, the call would
/// produce `unbound-var`; with the merge but a broken superclass-table
/// merge, the residual could be spuriously satisfied. Fixture: A =
/// `test_22b2_xmod_classmod_noinst`; B = `test_22b2_xmod_missing_constraint`
/// declares `forall a. (a) -> Str` and calls `show x`.
/// declares `forall a. (a) -> Str` and calls `tshow x`.
#[test]
fn cross_module_polymorphic_fn_without_constraint_fires_missing_constraint() {
let entry = examples_dir().join("test_22b2_xmod_missing_constraint.ail.json");
@@ -319,11 +319,11 @@ fn cross_module_polymorphic_fn_without_constraint_fires_missing_constraint() {
}
/// Property protected: cross-module instance lookup for the no-instance
/// arm. Module A declares `class Show a` AND `instance Show Int`; module
/// B imports A and calls `show 42`. The call must typecheck clean — the
/// arm. Module A declares `class TShow a` AND `instance Show Int`; module
/// B imports A and calls `tshow 42`. The call must typecheck clean — the
/// workspace registry built at load time over all modules must include A's
/// `instance Show Int`, and B's `check_fn` must find it under
/// `(class="Show", type_hash(Int))`. Without cross-module registry
/// `(class="TShow", type_hash(Int))`. Without cross-module registry
/// population, B would spuriously fire `no-instance` at a type for which
/// an instance demonstrably exists. Fixture: A =
/// `test_22b2_xmod_classmod`; B = `test_22b2_xmod_instance_present`.
@@ -342,7 +342,7 @@ fn cross_module_instance_satisfies_concrete_call() {
/// Property protected: `check_workspace` aggregates diagnostics across
/// distinct fns rather than stopping at the first failing fn. When two
/// independent polymorphic fns each call `show x` without declaring `Show
/// independent polymorphic fns each call `tshow x` without declaring `Show
/// a`, the resulting diagnostic vec must contain TWO `missing-constraint`
/// entries — one per fn — with the right `def` field on each. Without
/// per-def aggregation in the `for def in &m.defs` loop of
+40 -40
View File
@@ -56,11 +56,11 @@ fn monomorphise_workspace_is_identity_on_class_free_workspace() {
/// so no FnDefs are appended.
///
/// The fixture (`test_22b3_no_call_sites.ail.json`) declares
/// `class Show` + `instance Show Int` with a Lam-shaped instance
/// `class TShow` + `instance Show Int` with a Lam-shaped instance
/// body, but `main` is a literal — there is no `show <x>` call at any
/// type. Updated in Task 5 from `test_22b2_instance_present.ail.json`,
/// which had a `show 42` call site that the real fixpoint now
/// (correctly) materialises into `show__Int`.
/// which had a `tshow 42` call site that the real fixpoint now
/// (correctly) materialises into `tshow__Int`.
#[test]
fn monomorphise_workspace_is_identity_when_no_concrete_call_sites() {
let entry = examples_dir().join("test_22b3_no_call_sites.ail.json");
@@ -102,9 +102,9 @@ fn mono_symbol_primitive_types_use_surface_form() {
let str_ty = Type::str_();
let unit_ty = Type::unit();
assert_eq!(ailang_check::mono::mono_symbol("show", &int_ty), "show__Int");
assert_eq!(ailang_check::mono::mono_symbol("tshow", &int_ty), "tshow__Int");
assert_eq!(ailang_check::mono::mono_symbol("eq", &bool_ty), "eq__Bool");
assert_eq!(ailang_check::mono::mono_symbol("show", &str_ty), "show__Str");
assert_eq!(ailang_check::mono::mono_symbol("tshow", &str_ty), "tshow__Str");
assert_eq!(ailang_check::mono::mono_symbol("foo", &unit_ty), "foo__Unit");
}
@@ -112,9 +112,9 @@ fn mono_symbol_primitive_types_use_surface_form() {
/// fully-concrete `(class, method, type)` triples observed at
/// class-method call sites in a fn body, with the registry's
/// `defining_module` attached to each. The fixture's `main` calls
/// `show 42` once with `Int`, and the workspace declares
/// `tshow 42` once with `Int`, and the workspace declares
/// `instance Show Int` in the same module — the expected output is
/// a single MonoTarget naming `Show`, `show`, `Int`, and that
/// a single MonoTarget naming `TShow`, `tshow`, `Int`, and that
/// module.
#[test]
fn collect_mono_targets_single_concrete_call_site() {
@@ -149,8 +149,8 @@ fn collect_mono_targets_single_concrete_call_site() {
match t {
ailang_check::mono::MonoTarget::ClassMethod { class, method, type_, defining_module } => {
// mq.1: MonoTarget.class carries the qualified workspace-key shape.
assert_eq!(class, "test_22b2_instance_present.Show");
assert_eq!(method, "show");
assert_eq!(class, "test_22b2_instance_present.TShow");
assert_eq!(method, "tshow");
assert!(
matches!(type_, ailang_core::ast::Type::Con { name, args } if name == "Int" && args.is_empty()),
"type must be Int, got {:?}",
@@ -171,11 +171,11 @@ fn mono_symbol_compound_type_uses_hash_suffix() {
name: "Pair".into(),
args: vec![Type::int(), Type::bool_()],
};
let name = ailang_check::mono::mono_symbol("show", &pair_ty);
let name = ailang_check::mono::mono_symbol("tshow", &pair_ty);
// Format: `show__<8-hex-chars>`.
assert!(name.starts_with("show__"), "name = {name}");
let suffix = &name["show__".len()..];
// Format: `tshow__<8-hex-chars>`.
assert!(name.starts_with("tshow__"), "name = {name}");
let suffix = &name["tshow__".len()..];
assert_eq!(suffix.len(), 8, "compound suffix is 8 hex chars: {suffix}");
assert!(
suffix.chars().all(|c| c.is_ascii_hexdigit()),
@@ -183,7 +183,7 @@ fn mono_symbol_compound_type_uses_hash_suffix() {
);
// Stability: a second call with the same type produces the same name.
let again = ailang_check::mono::mono_symbol("show", &pair_ty);
let again = ailang_check::mono::mono_symbol("tshow", &pair_ty);
assert_eq!(name, again, "mono_symbol must be deterministic");
}
@@ -370,9 +370,9 @@ fn synthesise_mono_fn_zero_arg_method_uses_body_verbatim() {
);
}
/// Property: when two call sites of `show` apply at the same concrete
/// Property: when two call sites of `tshow` apply at the same concrete
/// type (Int), the workspace fixpoint synthesises the matching FnDef
/// (`show__Int`) exactly once across the whole workspace. Verifies the
/// (`tshow__Int`) exactly once across the whole workspace. Verifies the
/// dedup contract — the pass must not emit one FnDef per call site.
#[test]
fn fixpoint_dedupes_repeated_calls_at_same_type() {
@@ -383,18 +383,18 @@ fn fixpoint_dedupes_repeated_calls_at_same_type() {
let ws = ailang_check::monomorphise_workspace(&ws).expect("mono");
// Count `show__Int` fns workspace-wide.
// Count `tshow__Int` fns workspace-wide.
let mut count = 0usize;
for m in ws.modules.values() {
for d in &m.defs {
if let ailang_core::ast::Def::Fn(f) = d {
if f.name == "show__Int" {
if f.name == "tshow__Int" {
count += 1;
}
}
}
}
assert_eq!(count, 1, "exactly one show__Int across the workspace");
assert_eq!(count, 1, "exactly one tshow__Int across the workspace");
}
/// Property: when a synthesised instance body itself calls another class
@@ -450,7 +450,7 @@ fn fixpoint_handles_chained_class_method_calls() {
}
/// Property: when a fn's body locally shadows a class-method name
/// (here `show` is rebound by a `let`-binding), synth resolves the
/// (here `tshow` is rebound by a `let`-binding), synth resolves the
/// shadowed `Var` against the local and pushes NO residual for it.
/// The rewrite walker must mirror that exact lookup-precedence rule
/// — when a `Term::Var { name }` is in the local scope, the walker
@@ -463,14 +463,14 @@ fn fixpoint_handles_chained_class_method_calls() {
///
/// Body shape (`main`):
/// let _t = show 5 in -- residual #0: show@Int
/// let show = "shadow" in -- shadow `show`
/// let show = "shadow" in -- shadow `tshow`
/// let _u = show in -- shadowed Var (no residual)
/// foo 7 -- residual #1: foo@Int
///
/// `synth` pushes exactly two residuals (in order: show@Int, foo@Int).
/// The shadow-aware walker must:
/// - rewrite the outer `show` callee → `show__Int`,
/// - leave the inner shadowed `show` Var literally as `show`,
/// - rewrite the outer `tshow` callee → `tshow__Int`,
/// - leave the inner shadowed `tshow` Var literally as `tshow`,
/// - rewrite the `foo` callee → `foo__Int`.
///
/// A shadow-blind walker would advance the cursor on the inner Var,
@@ -483,7 +483,7 @@ fn rewrite_walker_skips_locally_shadowed_class_method() {
let entry = examples_dir().join("test_22b3_shadow_class_method.ail.json");
let ws = ailang_core::load_workspace(&entry).expect("load");
let diags = ailang_check::check_workspace(&ws);
// mq.3: this fixture intentionally shadows the class method `show`
// mq.3: this fixture intentionally shadows the class method `tshow`
// with a local `let show = "shadow"`, which fires the new
// `class-method-shadowed-by-fn` warning. The warning is an
// expected correct consequence; filter it out so the assertion
@@ -519,14 +519,14 @@ fn rewrite_walker_skips_locally_shadowed_class_method() {
other => panic!("outer value is not App: {:?}", other),
};
assert_eq!(
outer_callee, "show__Int",
"the outer class-method call site must be rewritten to show__Int"
outer_callee, "tshow__Int",
"the outer class-method call site must be rewritten to tshow__Int"
);
// Step inside `let show = "shadow" in <body>`.
let shadow_let_body = match outer_body {
Term::Let { name, body, .. } => {
assert_eq!(name, "show", "expected shadowing let to bind `show`");
assert_eq!(name, "tshow", "expected shadowing let to bind `tshow`");
body.as_ref()
}
other => panic!("expected shadowing Let, got {:?}", other),
@@ -540,10 +540,10 @@ fn rewrite_walker_skips_locally_shadowed_class_method() {
Term::Var { name } => name.as_str(),
other => panic!("inner value is not Var: {:?}", other),
};
// The locally-shadowed `show` Var must NOT be rewritten.
// The locally-shadowed `tshow` Var must NOT be rewritten.
assert_eq!(
shadowed_var_name, "show",
"locally-shadowed `show` Var must remain literal `show`, \
shadowed_var_name, "tshow",
"locally-shadowed `tshow` Var must remain literal `tshow`, \
not be re-aligned to a later residual slot"
);
// The real `foo 7` call site — the second residual — must be
@@ -596,24 +596,24 @@ fn rewrite_uses_qualified_name_for_cross_module_call_site() {
};
// class+instance live in test_22b2_xmod_classmod; caller in
// test_22b2_xmod_instance_present → qualified call.
assert_eq!(callee_name, "test_22b2_xmod_classmod.show__Int");
assert_eq!(callee_name, "test_22b2_xmod_classmod.tshow__Int");
// Placement: the synthesised def must live in the defining module,
// not the caller's module.
let classmod = ws.modules.get("test_22b2_xmod_classmod").unwrap();
let has_synth = classmod.defs.iter().any(|d| {
matches!(d, ailang_core::ast::Def::Fn(f) if f.name == "show__Int")
matches!(d, ailang_core::ast::Def::Fn(f) if f.name == "tshow__Int")
});
assert!(
has_synth,
"show__Int must live in the instance's defining module"
"tshow__Int must live in the instance's defining module"
);
let caller_has_synth = main_module.defs.iter().any(|d| {
matches!(d, ailang_core::ast::Def::Fn(f) if f.name == "show__Int")
matches!(d, ailang_core::ast::Def::Fn(f) if f.name == "tshow__Int")
});
assert!(
!caller_has_synth,
"show__Int must NOT live in the caller's module"
"tshow__Int must NOT live in the caller's module"
);
}
@@ -670,8 +670,8 @@ fn synthetic_fixture_runs_and_prints_five() {
}
/// After the mono pass, the body of `main` in
/// test_22b2_instance_present must reference `show__Int` instead
/// of `show`.
/// test_22b2_instance_present must reference `tshow__Int` instead
/// of `tshow`.
#[test]
fn rewrite_replaces_class_method_var_with_mono_symbol_same_module() {
let entry = examples_dir().join("test_22b2_instance_present.ail.json");
@@ -691,8 +691,8 @@ fn rewrite_replaces_class_method_var_with_mono_symbol_same_module() {
})
.expect("main");
// The body is `App { fn: Var "show", args: [Lit 42] }`. After
// rewrite, the callee Var must be `show__Int`.
// The body is `App { fn: Var "tshow", args: [Lit 42] }`. After
// rewrite, the callee Var must be `tshow__Int`.
let callee_name: &str = match &main_fn.body {
ailang_core::ast::Term::App { callee, .. } => match callee.as_ref() {
ailang_core::ast::Term::Var { name } => name.as_str(),
@@ -700,7 +700,7 @@ fn rewrite_replaces_class_method_var_with_mono_symbol_same_module() {
},
other => panic!("body is not App: {:?}", other),
};
assert_eq!(callee_name, "show__Int");
assert_eq!(callee_name, "tshow__Int");
}
/// Helper: build & run a workspace via `ail run` and capture stdout
@@ -0,0 +1,71 @@
//! Singleton dispatch pin for milestone 24.2's Show class.
//!
//! Asserts that with `prelude.Show` shipping `show` and a user `TShow`
//! shipping `tshow`, the bare method-name dispatcher resolves each via
//! Step-2 singleton (each method has exactly one candidate class globally).
use ailang_check::{build_check_env, check_workspace};
use ailang_core::workspace::load_workspace;
use std::path::PathBuf;
fn fixture_dir() -> PathBuf {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.pop();
d.pop();
d.join("examples")
}
#[test]
fn bare_show_routes_to_prelude_show_via_singleton() {
// Workspace = auto-loaded prelude (containing class Show) plus a
// non-prelude entry that has zero `Show` declarations of its own.
// Asserts: env.method_to_candidate_classes["show"] == {"prelude.Show"}
// (a singleton), so Step-2 of resolve_method_dispatch resolves bare
// "show" without needing Steps 3-4.
let ws_path = fixture_dir().join("show_mono_pin_smoke.ail.json");
let ws = load_workspace(&ws_path).expect("workspace loads");
let diags = check_workspace(&ws);
assert!(diags.is_empty(), "typecheck diagnostics: {diags:#?}");
let env = build_check_env(&ws);
let candidates = env
.method_to_candidate_classes
.get("show")
.expect("method 'show' has candidates");
assert_eq!(
candidates.len(),
1,
"post-24.2-migration, 'show' has exactly one candidate class globally: got {candidates:?}"
);
assert!(
candidates.iter().any(|c| c == "prelude.Show"),
"candidates do not include prelude.Show: {candidates:?}"
);
}
#[test]
fn bare_tshow_routes_to_user_tshow_via_singleton() {
// Workspace = auto-loaded prelude + the migrated 22b1_dup_classmod
// (which now declares class TShow with method tshow).
// Asserts: env.method_to_candidate_classes["tshow"] has exactly one
// candidate (the user TShow), workspace-globally.
let ws_path = fixture_dir().join("test_22b1_dup_classmod.ail.json");
let ws = load_workspace(&ws_path).expect("workspace loads");
let diags = check_workspace(&ws);
assert!(diags.is_empty(), "typecheck diagnostics: {diags:#?}");
let env = build_check_env(&ws);
let candidates = env
.method_to_candidate_classes
.get("tshow")
.expect("method 'tshow' has candidates");
assert_eq!(
candidates.len(),
1,
"post-22b-migration, 'tshow' is owned by exactly one class globally: got {candidates:?}"
);
assert!(
candidates.iter().any(|c| c.ends_with(".TShow")),
"candidates do not include a *.TShow class: {candidates:?}"
);
}
+16 -14
View File
@@ -294,33 +294,35 @@ mod tests {
.expect("examples/test_22b1_dup_a.ail.json present");
let dup_a_mod: crate::ast::Module = serde_json::from_slice(&dup_a_src).unwrap();
// mq.1: test_22b1_dup_a has one def post-migration — the instance
// of `test_22b1_dup_classmod.Show` for `test_22b1_dup_b.MyInt`.
// The `Show` class itself moved to `test_22b1_dup_classmod` to
// of `test_22b1_dup_classmod.TShow` for `test_22b1_dup_b.MyInt`.
// The `TShow` class itself moved to `test_22b1_dup_classmod` to
// break the dup_a ↔ dup_b cycle that the pre-mq.1 bare-class
// shape required for cross-module duplicate-instance coverage.
// 24.2: class renamed `Show` → `TShow` and method renamed
// `show` → `tshow` workspace-wide so prelude.Show can ship; the
// hash captures new bytes accordingly.
assert_eq!(dup_a_mod.defs.len(), 1, "test_22b1_dup_a expected to have exactly 1 def (the instance)");
// Hash captured post-mq.1 migration; the instance carries the
// canonical qualified `class` field. Computed by running this
// test once with a length-only assertion and recording the
// observed value. A future schema-touching change that shifts
// this hash without intent triggers this pin.
// Hash captured post-24.2 TShow/tshow rename. A future
// schema-touching change that shifts this hash without intent
// triggers this pin.
assert_eq!(
def_hash(&dup_a_mod.defs[0]),
"bdefb4ec75e046e4",
"test_22b1_dup_a instance hash drifted; expected post-mq.1 captured value"
"392c247f07de6517",
"test_22b1_dup_a instance hash drifted; expected post-24.2 captured value"
);
// Also pin the new test_22b1_dup_classmod fixture's `Show` class
// Also pin the new test_22b1_dup_classmod fixture's `TShow` class
// hash: the class moved here from dup_a verbatim (same method
// signature), so the hash is independent of the migration.
// signature), so the hash is independent of the migration but
// does shift with the 24.2 Show→TShow / show→tshow rename.
let dup_classmod_src = std::fs::read(examples.join("test_22b1_dup_classmod.ail.json"))
.expect("examples/test_22b1_dup_classmod.ail.json present");
let dup_classmod_mod: crate::ast::Module = serde_json::from_slice(&dup_classmod_src).unwrap();
assert_eq!(dup_classmod_mod.defs.len(), 1, "test_22b1_dup_classmod expected to have exactly 1 def (class Show)");
assert_eq!(dup_classmod_mod.defs.len(), 1, "test_22b1_dup_classmod expected to have exactly 1 def (class TShow)");
assert_eq!(
def_hash(&dup_classmod_mod.defs[0]),
"1c2573661ffd3da3",
"test_22b1_dup_classmod class Show canonical hash must equal the pre-mq.1 dup_a class Show hash (same bytes)"
"b8bca96c2d09ed93",
"test_22b1_dup_classmod class TShow canonical hash; post-24.2 captured value"
);
}
+10 -9
View File
@@ -1838,20 +1838,21 @@ mod tests {
assert_eq!(fixture_entries.len(), 1);
let (key, entry) = fixture_entries[0];
// mq.1: registry key is keyed by the qualified class name.
assert_eq!(&key.0, "test_22b1_orphan_class.Show");
// 24.2: class renamed `Show` → `TShow` workspace-wide.
assert_eq!(&key.0, "test_22b1_orphan_class.TShow");
assert_eq!(entry.defining_module, "test_22b1_orphan_class");
// `instance.class` carries the canonical-form on-disk value
// (bare for same-module per the canonical-form rule).
assert_eq!(entry.instance.class, "Show");
assert_eq!(entry.instance.class, "TShow");
}
/// Iter 22b.1: an instance declared in a module that is neither
/// the class's module nor the type's module fires `OrphanInstance`.
/// The fixture imports `test_22b1_orphan_third_classmod` (which
/// owns `class Show`) and the entry module declares `instance
/// Show Int` itself — but the entry is not the class's module
/// owns `class TShow`) and the entry module declares `instance
/// TShow Int` itself — but the entry is not the class's module
/// and `Int` is primitive, so neither leg of coherence is
/// satisfied.
/// satisfied. (24.2: class renamed `Show` → `TShow`.)
#[test]
fn iter22b1_orphan_instance_fires_diagnostic() {
let entry = examples_dir().join("test_22b1_orphan_third.ail.json");
@@ -1865,7 +1866,7 @@ mod tests {
} => {
// mq.1: `class` carries the canonical form (qualified
// for cross-module class refs).
assert_eq!(class, "test_22b1_orphan_third_classmod.Show");
assert_eq!(class, "test_22b1_orphan_third_classmod.TShow");
assert_eq!(type_repr, "Int");
assert_eq!(defining_module, "test_22b1_orphan_third");
}
@@ -1887,8 +1888,8 @@ mod tests {
/// canonical key is to have them in the same module (the class's
/// or the type's module). The fixture now declares both
/// instances in `test_22b1_dup_same_module` — both class-leg
/// coherent, both collide on `(test_22b1_dup_same_module.Show,
/// type_hash(Int))`.
/// coherent, both collide on `(test_22b1_dup_same_module.TShow,
/// type_hash(Int))`. (24.2: class renamed `Show` → `TShow`.)
#[test]
fn iter22b1_duplicate_instance_fires_diagnostic() {
let entry = examples_dir().join("test_22b1_dup_same_module.ail.json");
@@ -1902,7 +1903,7 @@ mod tests {
} => {
// `class` is the on-disk `inst.class` value; the
// fixture is intra-module so it stays bare.
assert_eq!(class, "Show");
assert_eq!(class, "TShow");
assert_eq!(type_repr, "Int");
// Both instances live in the same module post-mq.1,
// by structural necessity — see test doc-comment.
+20 -6
View File
@@ -1888,12 +1888,26 @@ end-to-end path is the milestone's typeclass acceptance gate
Milestone 23 amends the above: the prelude now ships the `Ordering`
ADT, the `Eq` and `Ord` classes, primitive `Eq Int/Bool/Str` and
`Ord Int/Bool/Str` instances, and the five polymorphic free-fn helpers
`ne` / `lt` / `le` / `gt` / `ge`. `Show`, operator routing through
`Eq`/`Ord`, and `print`-rewire remain out of scope per their original
substantive reasons (heap-`Str` ABI, bench rebaseline). Float has
neither `Eq` nor `Ord` instance per §"Float semantics"; a polymorphic
helper invoked at Float fires `NoInstance` at typecheck with a
Float-aware diagnostic cross-referencing this section.
`ne` / `lt` / `le` / `gt` / `ge`. Operator routing through `Eq`/`Ord`
and `print`-rewire remain out of scope per their original substantive
reasons (bench rebaseline, milestone-24 dependency on the post-mq
dispatcher); `Show` itself ships in milestone 24. Float has neither
`Eq` nor `Ord` instance per §"Float semantics"; a polymorphic helper
invoked at Float fires `NoInstance` at typecheck with a Float-aware
diagnostic cross-referencing this section.
Milestone 24 amends the above further: the prelude ships `class Show
a where show : (a borrow) -> Str` and primitive `Show Int`,
`Show Bool`, `Show Str`, `Show Float` instances. Float **is** included
in Show (unlike Eq/Ord) — IEEE-754 makes structural equality and total
ordering semantically dubious, but textual representation of a Float
is well-defined modulo the NaN-spelling caveat in §"Float semantics".
Each `Show <T>` instance body is a single-application lambda invoking
the corresponding runtime primitive (`int_to_str`, `bool_to_str`,
`str_clone`, `float_to_str`); no codegen intercept is required. The
polymorphic helper `print : forall a. Show a => a -> () !IO` ships in
the same milestone as the second iteration (24.3) and routes through
`show` and `io/print_str`.
Primitive output goes through `io/print_int` / `io/print_bool` /
`io/print_str` directly.
+189
View File
@@ -0,0 +1,189 @@
# iter 24.2 — Show class + 4 primitive instances + 22b TShow/tshow migration
**Date:** 2026-05-13
**Started from:** 64cea0e
**Status:** DONE
**Tasks completed:** 8 of 8
## Summary
Ships `class Show a where show : (a borrow) -> Str` in
`examples/prelude.ail.json` alongside primitive instances `Show Int`,
`Show Bool`, `Show Str`, `Show Float`. Each instance body is a
single-application lambda invoking the corresponding runtime primitive
(`int_to_str`, `bool_to_str`, `str_clone`, `float_to_str`) — first
prelude instance bodies to call runtime primitives directly
(prior Eq/Ord bodies used operator + codegen intercept). The 22b
typeclass test corpus migrates `class Show` / `show`
`class TShow` / `tshow` preemptively (Tasks 12 before Task 3 lands
`prelude.Show`) so the workspace stays green throughout. Three new
tests pin the post-mono shape: mono-synthesis existence
(`show_mono_synthesis.rs`), dispatch singleton (`show_dispatch_pin.rs`
× 2 cases), and body-hash stability (`mono_hash_stability.rs::primitive_show_mono_symbol_hashes_stay_bit_identical`).
DESIGN.md §"Prelude (built-in) classes" gains a milestone-24 paragraph
naming Show + instances + the Float caveat. `print`-rewire stays
deferred to iter 24.3.
## Per-task notes
- iter 24.2.1: Renamed `"Show"`/`"show"``"TShow"`/`"tshow"` across 21
`test_22b*_*.ail.json` fixture files; cross-module qualifier forms
(`"<modname>.Show"``"<modname>.TShow"`) handled by regex in 3
fixtures (`test_22b1_dup_a`, `test_22b1_dup_b`,
`test_22b1_orphan_third`). False-positive scan empty; fixture
parse round-trip green.
- iter 24.2.2: Renamed literals in `crates/ail/tests/typeclass_22b{2,3}.rs`
(`"Show"``"TShow"`, `"show"``"tshow"`, `show__T``tshow__T`),
including comment-prose forms (`` `Show` `` → `` `TShow` ``,
`class Show` → `class TShow`, etc.) and one assertion-message string
("exactly one show__Int across the workspace" → "tshow__Int...").
Plan undercount surfaced: the rename touches more than the 2 named
test files — see Concerns below.
- iter 24.2.3: Inserted 5 new top-level defs into `examples/prelude.ail.json`
immediately before `fn ne` (line 223 pre-edit). The `instance Show Str`
body invokes `str_clone` as `Term::Var` — first prelude instance body
to do so. Round-trip green; `primitive_eq_ord_mono_symbol_hashes_stay_bit_identical`
stays green (Eq/Ord hashes unaffected by Show defs).
- iter 24.2.4: New fixture `examples/show_mono_pin_smoke.ail.json`
exercises `show 42`, `show true`, `show "x"`, `show 1.5` in a
let-chain ending in `io/print_str s1`. New test
`crates/ail/tests/show_mono_synthesis.rs::primitive_show_mono_symbols_synthesise`
asserts `show__Int`/`show__Bool`/`show__Str`/`show__Float` materialise
as `Def::Fn` entries in the `prelude` post-mono module. `str_clone`
reachability in instance-lambda position verified implicitly:
`show__Str` materialises, which means the lambda body's `Term::Var
"str_clone"` resolved against the checker's builtin table during
typecheck.
- iter 24.2.5: New pin test `crates/ailang-check/tests/show_dispatch_pin.rs`
asserts `env.method_to_candidate_classes["show"] == {"prelude.Show"}`
(singleton) and `env.method_to_candidate_classes["tshow"] == {"*.TShow"}`
(singleton). API spelled `build_check_env(&ws)` not `Env::from_workspace`
(plan's placeholder name). 2/2 cases pass first-shot.
- iter 24.2.6: Extended `crates/ail/tests/mono_hash_stability.rs` with
a second test function `primitive_show_mono_symbol_hashes_stay_bit_identical`
loading `show_mono_pin_smoke.ail.json` (kept separate from the
Eq/Ord fixture to avoid coupling). Hashes captured first-run:
`show__Int 891eaebe64b3180d`, `show__Bool 1bdb621494e50607`,
`show__Str 3f1d57c90ddce081`, `show__Float 5ab0bdd40b9f8b8f`.
- iter 24.2.7: DESIGN.md §"Prelude (built-in) classes" amended:
milestone-23 paragraph drops "Show" from the deferred list and notes
Show ships in milestone 24; new milestone-24 paragraph names class
signature, instance set (including Float), body shape (single-app
lambda invoking runtime primitive), and the iter-24.3 carry-forward
for `print`-rewire.
- iter 24.2.8: Workspace tests 552 passed / 0 failed
(was 548 pre-iter, +4 new: 1 `show_mono_synthesis` + 2 `show_dispatch_pin`
+ 1 hash-stability). `bench/compile_check.py` exit 0 (24/24 stable).
`bench/cross_lang.py` exit 0 (25/25 stable). `bench/check.py` exit 1
with 2 noise-class metrics (`bench_list_sum.bump_s` +11.71% — 4th
consecutive sighting per `audit-mq` / `mq.tidy` lineage;
`latency.implicit_at_rc.max_us` +145.55% — 7th consecutive sighting of
the documented noise envelope, metric-identity-migrating across runs).
Baseline pristine per the conservative-call convention.
## Concerns
- **Plan undercount on 22b migration scope.** The plan's "2 Rust test
files" carrier (typeclass_22b{2,3}.rs) missed 4 additional consumer
sites that hardcode the renamed literals: (a)
`crates/ailang-core/src/hash.rs` ct4 canonical-form hash pin (2
entries); (b) `crates/ailang-core/src/workspace.rs` 3 in-crate iter22b1
tests; (c) `crates/ailang-prose/tests/snapshot.rs` (1 snapshot file at
`examples/test_22b2_instance_present.prose.txt`); (d) one
`mono_symbol_compound_type_uses_hash_suffix` assertion in typeclass_22b3.rs
that called `mono_symbol("tshow", ...)` (post my batch rename) but
still asserted `name.starts_with("show__")`. All fixed inline per the
precedent of mq.1 / mq.3 "Plan defects fixed inline" — fixture parse
was the only "green" pre-iter signal the plan's Step 3/4 anchored on,
which masked the broader downstream consumer surface. Suggested
Boss-side reaction: future migration plans run an upfront
`grep -rln '"<rename target>"' crates/` to enumerate the full
consumer set before committing to a per-file enumeration.
- **`str_clone` reachability is implicit.** Pre-flight note 3 flagged
this as needing first-round-trip verification; verification is
implicit in `primitive_show_mono_symbols_synthesise` passing
(mono of `show 42 / true / "x" / 1.5` requires the four `show__T`
bodies to typecheck, including the `str_clone` call in `show__Str`).
No explicit isolated unit test for `str_clone`-in-lambda was added
because the integration pin is strictly stronger; consider an
isolated unit pin only if a future regression makes the integration
test slow to bisect.
- **Hash-stability pin uses a separate fixture, not the existing
`mono_hash_pin_smoke.ail.json`.** Plan Task 6 offered both options;
the separate-fixture path was chosen because `show_mono_pin_smoke.ail.json`
already existed (minted in Task 4) and reusing it kept the Eq/Ord
pin structurally isolated. Both fixtures live side-by-side; the
second test function in `mono_hash_stability.rs` mirrors the first
byte-for-byte modulo the fixture path and pin table.
- **`bench/check.py` exit 1 is the documented recurring noise envelope.**
`latency.implicit_at_rc.*` max-tail metric has been migrating
between runs since `audit-cma` (2026-05-12) — 7th consecutive
observation; `bench_list_sum.bump_s` is the 4th since `audit-mq`.
Plan Task 8 Step 4 explicitly anticipates "1-3 regressed metrics
ratify-without-attribution per conservative-call convention"; this
iter follows that. The baseline stays pristine.
## Known debt
- **iter 24.3 still pending.** Carries `print : forall a. Show a => a -> () !IO`
rewire to route through `show` + `io/print_str`. Spec at
`docs/specs/2026-05-13-24-show-print.md`. 24.2 is structurally
complete; 24.3 is operationally distinct.
- **mq1 cross-module fixtures unmigrated by design.**
`examples/mq1_xmod_constraint_class.ail.json` and `..._dep.ail.json`
reference `mq1_xmod_constraint_class_dep.Show` (cross-module, fully
qualified) and ship zero `show` call sites; no dispatch ambiguity
arises once `prelude.Show` lands. They stay as-is per the plan's
pre-flight ratification.
- **mq3 fixtures deliberately retain two-Show shapes.**
`examples/mq3_two_show_*` fixtures test the multi-class dispatch
scenarios (ambiguity + qualifier resolution) and stay unchanged in
24.2 by design.
## Files touched
**Created (3):**
- `crates/ail/tests/show_mono_synthesis.rs`
- `crates/ailang-check/tests/show_dispatch_pin.rs`
- `examples/show_mono_pin_smoke.ail.json`
**Modified (29):**
Prelude + DESIGN:
- `examples/prelude.ail.json` (+5 defs)
- `docs/DESIGN.md` (§"Prelude (built-in) classes" amendment)
22b fixture migration (21 files):
- `examples/test_22b1_dup_a.ail.json`
- `examples/test_22b1_dup_b.ail.json`
- `examples/test_22b1_dup_classmod.ail.json`
- `examples/test_22b1_dup_same_module.ail.json`
- `examples/test_22b1_orphan_class.ail.json`
- `examples/test_22b1_orphan_third.ail.json`
- `examples/test_22b1_orphan_third_classmod.ail.json`
- `examples/test_22b2_class_method_lookup.ail.json`
- `examples/test_22b2_constraint_declared.ail.json`
- `examples/test_22b2_instance_present.ail.json`
- `examples/test_22b2_missing_constraint.ail.json`
- `examples/test_22b2_no_instance.ail.json`
- `examples/test_22b2_two_fns_missing_constraint.ail.json`
- `examples/test_22b2_xmod_classmod.ail.json`
- `examples/test_22b2_xmod_classmod_noinst.ail.json`
- `examples/test_22b2_xmod_instance_present.ail.json`
- `examples/test_22b2_xmod_missing_constraint.ail.json`
- `examples/test_22b2_xmod_no_instance.ail.json`
- `examples/test_22b3_dup_call_sites.ail.json`
- `examples/test_22b3_no_call_sites.ail.json`
- `examples/test_22b3_shadow_class_method.ail.json`
22b consumer-test migration (5 source files + 1 snapshot):
- `crates/ail/tests/typeclass_22b2.rs`
- `crates/ail/tests/typeclass_22b3.rs`
- `crates/ailang-core/src/hash.rs` (ct4 hash pin)
- `crates/ailang-core/src/workspace.rs` (3 iter22b1 tests)
- `crates/ail/tests/mono_hash_stability.rs` (new show test fn)
- `examples/test_22b2_instance_present.prose.txt` (snapshot accept)
## Stats
`bench/orchestrator-stats/2026-05-13-iter-24.2.json`
+102
View File
@@ -219,6 +219,108 @@
}
]
},
{
"kind": "class",
"name": "Show",
"param": "a",
"doc": "Producer of a human-readable Str representation. Ships in milestone 24 with primitive instances for Int/Bool/Str/Float; user types declare their own instance.",
"methods": [
{
"name": "show",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
"param_modes": ["borrow"],
"ret": { "k": "con", "name": "Str" },
"effects": []
}
}
]
},
{
"kind": "instance",
"class": "Show",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "show",
"body": {
"t": "lam",
"params": ["x"],
"paramTypes": [{ "k": "con", "name": "Int" }],
"retType": { "k": "con", "name": "Str" },
"body": {
"t": "app",
"fn": { "t": "var", "name": "int_to_str" },
"args": [{ "t": "var", "name": "x" }]
}
}
}
]
},
{
"kind": "instance",
"class": "Show",
"type": { "k": "con", "name": "Bool" },
"methods": [
{
"name": "show",
"body": {
"t": "lam",
"params": ["x"],
"paramTypes": [{ "k": "con", "name": "Bool" }],
"retType": { "k": "con", "name": "Str" },
"body": {
"t": "app",
"fn": { "t": "var", "name": "bool_to_str" },
"args": [{ "t": "var", "name": "x" }]
}
}
}
]
},
{
"kind": "instance",
"class": "Show",
"type": { "k": "con", "name": "Str" },
"methods": [
{
"name": "show",
"body": {
"t": "lam",
"params": ["x"],
"paramTypes": [{ "k": "con", "name": "Str" }],
"retType": { "k": "con", "name": "Str" },
"body": {
"t": "app",
"fn": { "t": "var", "name": "str_clone" },
"args": [{ "t": "var", "name": "x" }]
}
}
}
]
},
{
"kind": "instance",
"class": "Show",
"type": { "k": "con", "name": "Float" },
"methods": [
{
"name": "show",
"body": {
"t": "lam",
"params": ["x"],
"paramTypes": [{ "k": "con", "name": "Float" }],
"retType": { "k": "con", "name": "Str" },
"body": {
"t": "app",
"fn": { "t": "var", "name": "float_to_str" },
"args": [{ "t": "var", "name": "x" }]
}
}
}
]
},
{
"kind": "fn",
"name": "ne",
+49
View File
@@ -0,0 +1,49 @@
{
"schema": "ailang/v0",
"name": "show_mono_pin_smoke",
"imports": [],
"defs": [
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn",
"params": [],
"ret": { "k": "con", "name": "Unit" },
"effects": ["IO"]
},
"params": [],
"body": {
"t": "let",
"name": "s1",
"value": { "t": "app",
"fn": { "t": "var", "name": "show" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 42 } }] },
"body": {
"t": "let",
"name": "s2",
"value": { "t": "app",
"fn": { "t": "var", "name": "show" },
"args": [{ "t": "lit", "lit": { "kind": "bool", "value": true } }] },
"body": {
"t": "let",
"name": "s3",
"value": { "t": "app",
"fn": { "t": "var", "name": "show" },
"args": [{ "t": "lit", "lit": { "kind": "str", "value": "x" } }] },
"body": {
"t": "let",
"name": "s4",
"value": { "t": "app",
"fn": { "t": "var", "name": "show" },
"args": [{ "t": "lit", "lit": { "kind": "float", "bits": "3ff8000000000000" } }] },
"body": { "t": "do",
"op": "io/print_str",
"args": [{ "t": "var", "name": "s1" }] }
}
}
}
}
}
]
}
+2 -2
View File
@@ -12,14 +12,14 @@
"defs": [
{
"kind": "instance",
"class": "test_22b1_dup_classmod.Show",
"class": "test_22b1_dup_classmod.TShow",
"type": {
"k": "con",
"name": "test_22b1_dup_b.MyInt"
},
"methods": [
{
"name": "show",
"name": "tshow",
"body": {
"t": "lit",
"lit": {
+2 -2
View File
@@ -17,11 +17,11 @@
},
{
"kind": "instance",
"class": "test_22b1_dup_classmod.Show",
"class": "test_22b1_dup_classmod.TShow",
"type": { "k": "con", "name": "MyInt" },
"methods": [
{
"name": "show",
"name": "tshow",
"body": { "t": "lit", "lit": { "kind": "str", "value": "from-b" } }
}
]
+2 -2
View File
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
+6 -6
View File
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
@@ -21,22 +21,22 @@
},
{
"kind": "instance",
"class": "Show",
"class": "TShow",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "show",
"name": "tshow",
"body": { "t": "lit", "lit": { "kind": "str", "value": "first" } }
}
]
},
{
"kind": "instance",
"class": "Show",
"class": "TShow",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "show",
"name": "tshow",
"body": { "t": "lit", "lit": { "kind": "str", "value": "second" } }
}
]
+4 -4
View File
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
@@ -21,11 +21,11 @@
},
{
"kind": "instance",
"class": "Show",
"class": "TShow",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "show",
"name": "tshow",
"body": { "t": "lit", "lit": { "kind": "str", "value": "<int>" } }
}
]
+2 -2
View File
@@ -7,11 +7,11 @@
"defs": [
{
"kind": "instance",
"class": "test_22b1_orphan_third_classmod.Show",
"class": "test_22b1_orphan_third_classmod.TShow",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "show",
"name": "tshow",
"body": { "t": "lit", "lit": { "kind": "str", "value": "<int>" } }
}
]
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
@@ -21,11 +21,11 @@
},
{
"kind": "instance",
"class": "Show",
"class": "TShow",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "show",
"name": "tshow",
"body": { "t": "lit", "lit": { "kind": "str", "value": "n" } }
}
]
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
@@ -26,7 +26,7 @@
"k": "forall",
"vars": ["a"],
"constraints": [
{ "class": "Show", "type": { "k": "var", "name": "a" } }
{ "class": "TShow", "type": { "k": "var", "name": "a" } }
],
"body": {
"k": "fn",
@@ -38,7 +38,7 @@
"params": ["x"],
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "var", "name": "x" }]
}
}
+5 -5
View File
@@ -4,9 +4,9 @@
"imports": [],
"defs": [
{
"kind": "class", "name": "Show", "param": "a",
"kind": "class", "name": "TShow", "param": "a",
"methods": [{
"name": "show",
"name": "tshow",
"type": {
"k": "fn", "params": [{ "k": "var", "name": "a" }],
"ret": { "k": "con", "name": "Str" }, "effects": []
@@ -15,10 +15,10 @@
},
{
"kind": "instance",
"class": "Show",
"class": "TShow",
"type": { "k": "con", "name": "Int" },
"methods": [{
"name": "show",
"name": "tshow",
"body": {
"t": "lam",
"params": ["x"],
@@ -38,7 +38,7 @@
"params": [],
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 42 } }]
}
}
@@ -1,11 +1,11 @@
// module test_22b2_instance_present
class Show a {
fn show(x: a) -> Str
class TShow a {
fn tshow(x: a) -> Str
}
instance Show Int {
fn show {
instance TShow Int {
fn tshow {
|x: a| -> Str {
"n"
}
@@ -13,5 +13,5 @@ instance Show Int {
}
fn main() -> Str {
show(42)
tshow(42)
}
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
@@ -35,7 +35,7 @@
"params": ["x"],
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "var", "name": "x" }]
}
}
+3 -3
View File
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
@@ -31,7 +31,7 @@
"params": [],
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 42 } }]
}
}
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
@@ -35,7 +35,7 @@
"params": ["x"],
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "var", "name": "x" }]
}
},
@@ -55,7 +55,7 @@
"params": ["y"],
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "var", "name": "y" }]
}
}
+4 -4
View File
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
@@ -21,11 +21,11 @@
},
{
"kind": "instance",
"class": "Show",
"class": "TShow",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "show",
"name": "tshow",
"body": {
"t": "lam",
"params": ["x"],
@@ -5,11 +5,11 @@
"defs": [
{
"kind": "class",
"name": "Show",
"name": "TShow",
"param": "a",
"methods": [
{
"name": "show",
"name": "tshow",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
@@ -15,7 +15,7 @@
"params": [],
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 42 } }]
}
}
@@ -19,7 +19,7 @@
"params": ["x"],
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "var", "name": "x" }]
}
}
+1 -1
View File
@@ -15,7 +15,7 @@
"params": [],
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 42 } }]
}
}
+6 -6
View File
@@ -4,9 +4,9 @@
"imports": [],
"defs": [
{
"kind": "class", "name": "Show", "param": "a",
"kind": "class", "name": "TShow", "param": "a",
"methods": [{
"name": "show",
"name": "tshow",
"type": {
"k": "fn", "params": [{ "k": "var", "name": "a" }],
"ret": { "k": "con", "name": "Str" }, "effects": []
@@ -15,10 +15,10 @@
},
{
"kind": "instance",
"class": "Show",
"class": "TShow",
"type": { "k": "con", "name": "Int" },
"methods": [{
"name": "show",
"name": "tshow",
"body": {
"t": "lam",
"params": ["x"],
@@ -41,12 +41,12 @@
"name": "_a",
"value": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 5 } }]
},
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 7 } }]
}
}
+4 -4
View File
@@ -4,9 +4,9 @@
"imports": [],
"defs": [
{
"kind": "class", "name": "Show", "param": "a",
"kind": "class", "name": "TShow", "param": "a",
"methods": [{
"name": "show",
"name": "tshow",
"type": {
"k": "fn", "params": [{ "k": "var", "name": "a" }],
"ret": { "k": "con", "name": "Str" }, "effects": []
@@ -15,10 +15,10 @@
},
{
"kind": "instance",
"class": "Show",
"class": "TShow",
"type": { "k": "con", "name": "Int" },
"methods": [{
"name": "show",
"name": "tshow",
"body": {
"t": "lam",
"params": ["x"],
@@ -4,9 +4,9 @@
"imports": [],
"defs": [
{
"kind": "class", "name": "Show", "param": "a",
"kind": "class", "name": "TShow", "param": "a",
"methods": [{
"name": "show",
"name": "tshow",
"type": {
"k": "fn", "params": [{ "k": "var", "name": "a" }],
"ret": { "k": "con", "name": "Str" }, "effects": []
@@ -25,10 +25,10 @@
},
{
"kind": "instance",
"class": "Show",
"class": "TShow",
"type": { "k": "con", "name": "Int" },
"methods": [{
"name": "show",
"name": "tshow",
"body": {
"t": "lam",
"params": ["x"],
@@ -66,17 +66,17 @@
"name": "_t",
"value": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"fn": { "t": "var", "name": "tshow" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 5 } }]
},
"body": {
"t": "let",
"name": "show",
"name": "tshow",
"value": { "t": "lit", "lit": { "kind": "str", "value": "shadow" } },
"body": {
"t": "let",
"name": "_u",
"value": { "t": "var", "name": "show" },
"value": { "t": "var", "name": "tshow" },
"body": {
"t": "app",
"fn": { "t": "var", "name": "foo" },