From 559e591ab26590b6b2f4b1aa658171253cfa109b Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 10 May 2026 22:46:51 +0200 Subject: [PATCH] iter 23.2.4: e2e smoke fixture + ir-shape integration tests for eq__T mono symbols --- crates/ail/tests/e2e.rs | 12 +++ crates/ailang-codegen/src/lib.rs | 82 ++++++++++++++ examples/eq_primitives_smoke.ail.json | 147 ++++++++++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 examples/eq_primitives_smoke.ail.json diff --git a/crates/ail/tests/e2e.rs b/crates/ail/tests/e2e.rs index 273f280..5dce9bb 100644 --- a/crates/ail/tests/e2e.rs +++ b/crates/ail/tests/e2e.rs @@ -908,6 +908,18 @@ fn ordering_match_via_prelude_prints_1() { assert_eq!(stdout, "1\n"); } +/// Iter 23.2: end-to-end coverage for the auto-loaded `Eq` class +/// + three primitive instances. The fixture calls `eq` on Int / +/// Bool / Str with both an equal and an unequal pair each; the +/// monomorphiser synthesises `eq__Int` / `eq__Bool` / `eq__Str` +/// in the prelude module; the binary prints six lines, one per +/// call. Equal-call lines are `1`, unequal-call lines are `0`. +#[test] +fn eq_primitives_smoke_compiles_and_runs() { + let stdout = build_and_run("eq_primitives_smoke.ail.json"); + assert_eq!(stdout, "1\n0\n1\n0\n1\n0\n"); +} + /// Guards Iter 5d: `ail manifest --workspace --json` lists defs from all /// modules of the workspace — visible via a `module` field per entry. #[test] diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index b9ce52d..af62232 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -3563,4 +3563,86 @@ mod tests { "eq__Str must emit a static closure pair; ir was:\n{ir}" ); } + + /// Iter 23.2: integration with the auto-loaded prelude. Compiling + /// a workspace that calls `eq` on an Int pair must produce a + /// mono-synthesised `@ail_prelude_eq__Int` whose body lowers to + /// `icmp eq i64`. The test runs the full pipeline end-of-typecheck → + /// monomorphise → codegen, then asserts substring shape on the IR. + #[test] + fn eq_int_call_produces_icmp_i64_mono_fn() { + use ailang_core::load_workspace; + use ailang_check::{check_workspace, monomorphise_workspace}; + let manifest = env!("CARGO_MANIFEST_DIR"); + let entry = std::path::Path::new(manifest) + .parent().unwrap().parent().unwrap() + .join("examples").join("eq_primitives_smoke.ail.json"); + let ws = load_workspace(&entry).expect("load workspace"); + let diags = check_workspace(&ws); + assert!( + diags.iter().all(|d| !matches!(d.severity, ailang_check::Severity::Error)), + "unexpected check_workspace errors: {diags:?}" + ); + let ws = monomorphise_workspace(&ws).expect("monomorphise"); + let ir = lower_workspace(&ws).expect("lower"); + assert!( + ir.contains("@ail_prelude_eq__Int"), + "ir must contain mono-synthesised @ail_prelude_eq__Int; ir was:\n{ir}" + ); + assert!( + ir.contains("icmp eq i64"), + "eq__Int body must lower to icmp eq i64; ir was:\n{ir}" + ); + } + + /// Iter 23.2: same shape as `eq_int_call_produces_icmp_i64_mono_fn` + /// for Bool. Asserts the mono-synthesised `@ail_prelude_eq__Bool` + /// lowers to `icmp eq i1`. + #[test] + fn eq_bool_call_produces_icmp_i1_mono_fn() { + use ailang_core::load_workspace; + use ailang_check::{check_workspace, monomorphise_workspace}; + let manifest = env!("CARGO_MANIFEST_DIR"); + let entry = std::path::Path::new(manifest) + .parent().unwrap().parent().unwrap() + .join("examples").join("eq_primitives_smoke.ail.json"); + let ws = load_workspace(&entry).expect("load workspace"); + let _ = check_workspace(&ws); + let ws = monomorphise_workspace(&ws).expect("monomorphise"); + let ir = lower_workspace(&ws).expect("lower"); + assert!( + ir.contains("@ail_prelude_eq__Bool"), + "ir must contain mono-synthesised @ail_prelude_eq__Bool; ir was:\n{ir}" + ); + assert!( + ir.contains("icmp eq i1"), + "eq__Bool body must lower to icmp eq i1; ir was:\n{ir}" + ); + } + + /// Iter 23.2: Str's mono-symbol body is hand-rolled by the + /// `try_emit_primitive_instance_body` intercept. Asserts the + /// intercept fires for the prelude-loaded `eq__Str` synthesised + /// against a real user fixture that calls `eq` on Str. + #[test] + fn eq_str_call_produces_ail_str_eq_call_mono_fn() { + use ailang_core::load_workspace; + use ailang_check::{check_workspace, monomorphise_workspace}; + let manifest = env!("CARGO_MANIFEST_DIR"); + let entry = std::path::Path::new(manifest) + .parent().unwrap().parent().unwrap() + .join("examples").join("eq_primitives_smoke.ail.json"); + let ws = load_workspace(&entry).expect("load workspace"); + let _ = check_workspace(&ws); + let ws = monomorphise_workspace(&ws).expect("monomorphise"); + let ir = lower_workspace(&ws).expect("lower"); + assert!( + ir.contains("@ail_prelude_eq__Str"), + "ir must contain mono-synthesised @ail_prelude_eq__Str; ir was:\n{ir}" + ); + assert!( + ir.contains("call zeroext i1 @ail_str_eq("), + "eq__Str body must call @ail_str_eq; ir was:\n{ir}" + ); + } } diff --git a/examples/eq_primitives_smoke.ail.json b/examples/eq_primitives_smoke.ail.json new file mode 100644 index 0000000..756bb37 --- /dev/null +++ b/examples/eq_primitives_smoke.ail.json @@ -0,0 +1,147 @@ +{ + "schema": "ailang/v0", + "name": "eq_primitives_smoke", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "to_int", + "doc": "1 if the bool is true, 0 otherwise. Used to render Eq results as printable Ints.", + "type": { + "k": "fn", + "params": [{ "k": "con", "name": "Bool" }], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["b"], + "body": { + "t": "if", + "cond": { "t": "var", "name": "b" }, + "then": { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + "else": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + } + }, + { + "kind": "fn", + "name": "main", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "to_int" }, + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "eq" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { "t": "lit", "lit": { "kind": "int", "value": 7 } } + ] + }] + }] + }, + "rhs": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "to_int" }, + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "eq" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { "t": "lit", "lit": { "kind": "int", "value": 8 } } + ] + }] + }] + }, + "rhs": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "to_int" }, + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "eq" }, + "args": [ + { "t": "lit", "lit": { "kind": "bool", "value": true } }, + { "t": "lit", "lit": { "kind": "bool", "value": true } } + ] + }] + }] + }, + "rhs": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "to_int" }, + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "eq" }, + "args": [ + { "t": "lit", "lit": { "kind": "bool", "value": true } }, + { "t": "lit", "lit": { "kind": "bool", "value": false } } + ] + }] + }] + }, + "rhs": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "to_int" }, + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "eq" }, + "args": [ + { "t": "lit", "lit": { "kind": "str", "value": "hello" } }, + { "t": "lit", "lit": { "kind": "str", "value": "hello" } } + ] + }] + }] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "to_int" }, + "args": [{ + "t": "app", + "fn": { "t": "var", "name": "eq" }, + "args": [ + { "t": "lit", "lit": { "kind": "str", "value": "hello" } }, + { "t": "lit", "lit": { "kind": "str", "value": "world" } } + ] + }] + }] + } + } + } + } + } + } + } + ] +}