iter form-a.1 (Tasks 1-5): additive phase + relocation
First half of the form-a-default-authoring milestone-close iter (Boss-decided strategy C, big-bang). All five tasks DONE; cargo test --workspace green at every per-task boundary. T1 — Add three new tests: - parse_is_deterministic_over_every_ail_fixture (round_trip.rs) - cli_parse_then_render_then_parse_is_idempotent (roundtrip_cli.rs) - carve_out_inventory.rs (new file; #[ignore]'d until T8 deletion) T2 — Bulk-render the 99 missing examples/<stem>.ail files via `ail render`. Corpus 58 .ail (pre-iter) -> 157 .ail. Eight .ail.json carve-outs (7 §C4(a) subject-matter + 1 §C4(b) prelude) preserved. One re-loop triggered: load_workspace prefers .ail siblings since ext-cli.1, so the newly-rendered imports broke seven Group-A entries whose JSON entry-paths now resolved imports to fresh .ail. Repair: pre-emptive forward-pull of five T3 migrations + 4 transient #[ignore]s on workspace.rs mod tests (cleanly relocated in T5). T3 — 14 Group-A test files migrated from ailang_core::load_workspace to ailang_surface::load_workspace + .ail paths. Carve-out lines preserved verbatim (7 sites in typeclass_22b2.rs / typeclass_22b3.rs). T4 — 12 Group-B test files: bulk regex flip on build_and_run / build_and_run_with_alloc / build_and_run_with_rc_stats call sites (~70 e2e.rs invocations + 11 subprocess sites). Four files mis-classified Group-A as Group-B in plan recon (mono_hash_stability, prelude_free_fns, print_mono_body_shape, show_mono_synthesis); two files mis-classified Group-B as Group-A (mono_recursive_fn, mono_xmod_qualified_ref). Migrated per actual shape, not plan label. T5 — Relocated #[cfg(test)] mod tests from production source to integration test crates with ailang-surface dev-dependency: - crates/ailang-core/tests/hash_pin.rs (10 tests from hash.rs) - crates/ailang-core/tests/workspace_pin.rs (10 non-carve-out tests from workspace.rs) - crates/ailang-codegen/tests/eq_primitives_pin.rs (3 tests from codegen/src/lib.rs:3717-3799) - ailang-prose/tests/snapshot.rs migrated (helper + 8 fixtures) to .ail + ailang_surface::load_module Carve-out tests in workspace.rs mod tests preserved in-place (3× 22b2 + 3× ct1 = 6 tests). Tempdir-based loader-mechanism tests (3 sites) also preserved — they don't consume examples/. Tests: 560 passed, 0 failed, 4 ignored (was 558 + 3 T1 new - 1 transit carve_out_inventory #[ignore] = 560 active). Tasks 6-12 (bench-driver suffix flips, e2e diff-test rewrite + 4 additional raw-JSON-inspect handlers, .ail.json deletion, retiring obsolete roundtrip tests + schema_coverage corpus flip, §C3 DESIGN.md restatement, §A4 doctrine edits, WhatsNew + roadmap strike) ship in the next dispatch on this iter ID. Known debt inherited to T6-12: 4 raw-JSON-inspect tests in e2e.rs (borrow_own_demo / reuse_as_demo / render_parse_round_trip_canonical / ail_run_accepts_ail_source_with_same_stdout_as_ail_json dual-form smoke) need rewrite or #[ignore] before T8 deletion; recorded in journal Concerns + Known debt sections.
This commit is contained in:
Generated
+3
@@ -36,6 +36,7 @@ version = "0.0.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"ailang-check",
|
"ailang-check",
|
||||||
"ailang-core",
|
"ailang-core",
|
||||||
|
"ailang-surface",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
@@ -44,6 +45,7 @@ dependencies = [
|
|||||||
name = "ailang-core"
|
name = "ailang-core"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ailang-surface",
|
||||||
"blake3",
|
"blake3",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -57,6 +59,7 @@ name = "ailang-prose"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ailang-core",
|
"ailang-core",
|
||||||
|
"ailang-surface",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"iter_id": "form-a.1",
|
||||||
|
"date": "2026-05-13",
|
||||||
|
"mode": "standard",
|
||||||
|
"outcome": "DONE",
|
||||||
|
"task_range": [1, 5],
|
||||||
|
"tasks_total": 5,
|
||||||
|
"tasks_completed": 5,
|
||||||
|
"reloops_per_task": {
|
||||||
|
"1": 0,
|
||||||
|
"2": 1,
|
||||||
|
"3": 0,
|
||||||
|
"4": 0,
|
||||||
|
"5": 0
|
||||||
|
},
|
||||||
|
"review_loops_spec": 0,
|
||||||
|
"review_loops_quality": 0,
|
||||||
|
"blocked_reason": null,
|
||||||
|
"notes": {
|
||||||
|
"task2_reloop": "Plan recon defect: load_workspace prefers .ail siblings since ext-cli.1, so bulk-render of .ail siblings broke 7 Group-A tests whose entry was .ail.json. Inline forward-pull of Task-3 migrations + 4 transient #[ignore]'s on workspace.rs mod tests (relocated cleanly in Task 5).",
|
||||||
|
"test_count_pre_iter": 558,
|
||||||
|
"test_count_post_iter_half": 560,
|
||||||
|
"test_count_ignored_post_iter_half": 4,
|
||||||
|
"test_count_ignored_breakdown": "1 carve_out_inventory (T8 un-ignores) + 3 doctests (pre-existing)",
|
||||||
|
"corpus_ail_pre_iter": 58,
|
||||||
|
"corpus_ail_post_iter_half": 157,
|
||||||
|
"corpus_ail_json_post_iter_half": 164,
|
||||||
|
"corpus_ail_json_target_post_T8": 8
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,13 +19,13 @@
|
|||||||
|
|
||||||
use ailang_check::{check_workspace, monomorphise_workspace};
|
use ailang_check::{check_workspace, monomorphise_workspace};
|
||||||
use ailang_core::ast::{Def, Term};
|
use ailang_core::ast::{Def, Term};
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn fixture_path() -> PathBuf {
|
fn fixture_path() -> PathBuf {
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
.join("../../examples")
|
.join("../../examples")
|
||||||
.join("show_user_adt.ail.json")
|
.join("show_user_adt.ail")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ fn check_human_mode_emits_actionable_message_to_stderr() {
|
|||||||
/// form used to succeed.
|
/// form used to succeed.
|
||||||
#[test]
|
#[test]
|
||||||
fn check_ordering_match_post_migration_is_clean() {
|
fn check_ordering_match_post_migration_is_clean() {
|
||||||
let fixture = examples_dir().join("ordering_match.ail.json");
|
let fixture = examples_dir().join("ordering_match.ail");
|
||||||
let output = Command::new(ail_bin())
|
let output = Command::new(ail_bin())
|
||||||
.args(["check", "--json", fixture.to_str().unwrap()])
|
.args(["check", "--json", fixture.to_str().unwrap()])
|
||||||
.output()
|
.output()
|
||||||
|
|||||||
+69
-69
@@ -78,7 +78,7 @@ fn build_and_run_with_alloc(example: &str, alloc: &str) -> String {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sum_1_to_10_is_55() {
|
fn sum_1_to_10_is_55() {
|
||||||
let stdout = build_and_run("sum.ail.json");
|
let stdout = build_and_run("sum.ail");
|
||||||
assert_eq!(stdout.trim(), "55");
|
assert_eq!(stdout.trim(), "55");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,20 +86,20 @@ fn sum_1_to_10_is_55() {
|
|||||||
/// phi block tracking would produce a wrong result here.
|
/// phi block tracking would produce a wrong result here.
|
||||||
#[test]
|
#[test]
|
||||||
fn max3_picks_largest() {
|
fn max3_picks_largest() {
|
||||||
let stdout = build_and_run("max3.ail.json");
|
let stdout = build_and_run("max3.ail");
|
||||||
assert_eq!(stdout.trim(), "17");
|
assert_eq!(stdout.trim(), "17");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hello_world_str_lit() {
|
fn hello_world_str_lit() {
|
||||||
let stdout = build_and_run("hello.ail.json");
|
let stdout = build_and_run("hello.ail");
|
||||||
assert_eq!(stdout.trim(), "Hello, AILang.");
|
assert_eq!(stdout.trim(), "Hello, AILang.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Guards ADT codegen + match: recursive list, sum_list via match on Cons/Nil.
|
/// Guards ADT codegen + match: recursive list, sum_list via match on Cons/Nil.
|
||||||
#[test]
|
#[test]
|
||||||
fn list_sum_via_match() {
|
fn list_sum_via_match() {
|
||||||
let stdout = build_and_run("list.ail.json");
|
let stdout = build_and_run("list.ail");
|
||||||
assert_eq!(stdout.trim(), "42");
|
assert_eq!(stdout.trim(), "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ fn list_sum_via_match() {
|
|||||||
/// produce 42, exercising fn-typed parameters and indirect call.
|
/// produce 42, exercising fn-typed parameters and indirect call.
|
||||||
#[test]
|
#[test]
|
||||||
fn higher_order_apply_inc() {
|
fn higher_order_apply_inc() {
|
||||||
let stdout = build_and_run("hof.ail.json");
|
let stdout = build_and_run("hof.ail");
|
||||||
assert_eq!(stdout.trim(), "42");
|
assert_eq!(stdout.trim(), "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ fn higher_order_apply_inc() {
|
|||||||
/// and apply's indirect call unpacks both halves.
|
/// and apply's indirect call unpacks both halves.
|
||||||
#[test]
|
#[test]
|
||||||
fn closure_captures_let_n() {
|
fn closure_captures_let_n() {
|
||||||
let stdout = build_and_run("closure.ail.json");
|
let stdout = build_and_run("closure.ail");
|
||||||
assert_eq!(stdout.trim(), "42");
|
assert_eq!(stdout.trim(), "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ fn closure_captures_let_n() {
|
|||||||
/// `map (\x. x * 2)` over `[1, 2, 3]` then print each element.
|
/// `map (\x. x * 2)` over `[1, 2, 3]` then print each element.
|
||||||
#[test]
|
#[test]
|
||||||
fn list_map_doubles_then_prints() {
|
fn list_map_doubles_then_prints() {
|
||||||
let stdout = build_and_run("list_map.ail.json");
|
let stdout = build_and_run("list_map.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["2", "4", "6"]);
|
assert_eq!(lines, vec!["2", "4", "6"]);
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ fn list_map_doubles_then_prints() {
|
|||||||
/// effectful tail call.
|
/// effectful tail call.
|
||||||
#[test]
|
#[test]
|
||||||
fn list_map_poly_inc_then_prints() {
|
fn list_map_poly_inc_then_prints() {
|
||||||
let stdout = build_and_run("list_map_poly.ail.json");
|
let stdout = build_and_run("list_map_poly.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["2", "3", "4"]);
|
assert_eq!(lines, vec!["2", "3", "4"]);
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ fn list_map_poly_inc_then_prints() {
|
|||||||
/// scrutinee with a wildcard fallback is not currently in the grammar.
|
/// scrutinee with a wildcard fallback is not currently in the grammar.
|
||||||
#[test]
|
#[test]
|
||||||
fn gc_handles_recursive_list_construction() {
|
fn gc_handles_recursive_list_construction() {
|
||||||
let stdout = build_and_run("gc_stress.ail.json");
|
let stdout = build_and_run("gc_stress.ail");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stdout.trim(),
|
stdout.trim(),
|
||||||
"1275",
|
"1275",
|
||||||
@@ -177,7 +177,7 @@ fn gc_handles_recursive_list_construction() {
|
|||||||
/// for the Box and zero `@GC_malloc` calls inside `peek`/`count`.
|
/// for the Box and zero `@GC_malloc` calls inside `peek`/`count`.
|
||||||
#[test]
|
#[test]
|
||||||
fn iter17a_local_box_alloca() {
|
fn iter17a_local_box_alloca() {
|
||||||
let stdout = build_and_run("escape_local_demo.ail.json");
|
let stdout = build_and_run("escape_local_demo.ail");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stdout.lines().collect::<Vec<_>>(),
|
stdout.lines().collect::<Vec<_>>(),
|
||||||
vec!["42", "42", "5", "0"],
|
vec!["42", "42", "5", "0"],
|
||||||
@@ -190,7 +190,7 @@ fn iter17a_local_box_alloca() {
|
|||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let src = workspace
|
let src = workspace
|
||||||
.join("examples")
|
.join("examples")
|
||||||
.join("escape_local_demo.ail.json");
|
.join("escape_local_demo.ail");
|
||||||
let tmp = std::env::temp_dir().join(format!(
|
let tmp = std::env::temp_dir().join(format!(
|
||||||
"ailang_iter17a_alloca_{}",
|
"ailang_iter17a_alloca_{}",
|
||||||
std::process::id()
|
std::process::id()
|
||||||
@@ -237,7 +237,7 @@ fn iter17a_local_box_alloca() {
|
|||||||
fn iter14e_print_list_recursion_emits_musttail() {
|
fn iter14e_print_list_recursion_emits_musttail() {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let src = workspace.join("examples").join("list_map_poly.ail.json");
|
let src = workspace.join("examples").join("list_map_poly.ail");
|
||||||
let tmp = std::env::temp_dir().join(format!(
|
let tmp = std::env::temp_dir().join(format!(
|
||||||
"ailang_iter14e_musttail_{}",
|
"ailang_iter14e_musttail_{}",
|
||||||
std::process::id()
|
std::process::id()
|
||||||
@@ -290,7 +290,7 @@ fn iter14e_print_list_recursion_emits_musttail() {
|
|||||||
/// recursion + branching without surprises.
|
/// recursion + branching without surprises.
|
||||||
#[test]
|
#[test]
|
||||||
fn insertion_sort_orders_list() {
|
fn insertion_sort_orders_list() {
|
||||||
let stdout = build_and_run("sort.ail.json");
|
let stdout = build_and_run("sort.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lines,
|
lines,
|
||||||
@@ -303,7 +303,7 @@ fn insertion_sort_orders_list() {
|
|||||||
/// triggers a separate specialised LLVM fn. Output: 42, then "true".
|
/// triggers a separate specialised LLVM fn. Output: 42, then "true".
|
||||||
#[test]
|
#[test]
|
||||||
fn polymorphic_id_at_int_and_bool() {
|
fn polymorphic_id_at_int_and_bool() {
|
||||||
let stdout = build_and_run("poly_id.ail.json");
|
let stdout = build_and_run("poly_id.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["42", "true"]);
|
assert_eq!(lines, vec!["42", "true"]);
|
||||||
}
|
}
|
||||||
@@ -314,7 +314,7 @@ fn polymorphic_id_at_int_and_bool() {
|
|||||||
/// monomorphised body (a fn-typed param survives substitution).
|
/// monomorphised body (a fn-typed param survives substitution).
|
||||||
#[test]
|
#[test]
|
||||||
fn polymorphic_apply_with_fn_param() {
|
fn polymorphic_apply_with_fn_param() {
|
||||||
let stdout = build_and_run("poly_apply.ail.json");
|
let stdout = build_and_run("poly_apply.ail");
|
||||||
assert_eq!(stdout.trim(), "42");
|
assert_eq!(stdout.trim(), "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ fn polymorphic_apply_with_fn_param() {
|
|||||||
/// down into a polymorphic fn body.
|
/// down into a polymorphic fn body.
|
||||||
#[test]
|
#[test]
|
||||||
fn parameterised_box_round_trip() {
|
fn parameterised_box_round_trip() {
|
||||||
let stdout = build_and_run("box.ail.json");
|
let stdout = build_and_run("box.ail");
|
||||||
assert_eq!(stdout.trim(), "42");
|
assert_eq!(stdout.trim(), "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ fn parameterised_box_round_trip() {
|
|||||||
/// `x : Int`, not `x : a`).
|
/// `x : Int`, not `x : a`).
|
||||||
#[test]
|
#[test]
|
||||||
fn parameterised_maybe_match() {
|
fn parameterised_maybe_match() {
|
||||||
let stdout = build_and_run("maybe_int.ail.json");
|
let stdout = build_and_run("maybe_int.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["7", "99"]);
|
assert_eq!(lines, vec!["7", "99"]);
|
||||||
}
|
}
|
||||||
@@ -351,7 +351,7 @@ fn parameterised_maybe_match() {
|
|||||||
/// flows end-to-end through check, codegen, and runtime.
|
/// flows end-to-end through check, codegen, and runtime.
|
||||||
#[test]
|
#[test]
|
||||||
fn cross_module_maybe_demo() {
|
fn cross_module_maybe_demo() {
|
||||||
let stdout = build_and_run("std_maybe_demo.ail.json");
|
let stdout = build_and_run("std_maybe_demo.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["7", "99", "true", "true", "42"]);
|
assert_eq!(lines, vec!["7", "99", "true", "true", "42"]);
|
||||||
}
|
}
|
||||||
@@ -365,7 +365,7 @@ fn cross_module_maybe_demo() {
|
|||||||
/// and fail to unify against the qualified scrutinee args.
|
/// and fail to unify against the qualified scrutinee args.
|
||||||
#[test]
|
#[test]
|
||||||
fn std_list_demo() {
|
fn std_list_demo() {
|
||||||
let stdout = build_and_run("std_list_demo.ail.json");
|
let stdout = build_and_run("std_list_demo.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lines,
|
lines,
|
||||||
@@ -395,7 +395,7 @@ fn std_list_demo() {
|
|||||||
/// happens at N=1000.
|
/// happens at N=1000.
|
||||||
#[test]
|
#[test]
|
||||||
fn std_list_stress_1000_element_folds() {
|
fn std_list_stress_1000_element_folds() {
|
||||||
let stdout = build_and_run("std_list_stress.ail.json");
|
let stdout = build_and_run("std_list_stress.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lines,
|
lines,
|
||||||
@@ -457,7 +457,7 @@ fn render_parse_round_trip_canonical() {
|
|||||||
/// the inner binders would be silently dropped by the codegen.
|
/// the inner binders would be silently dropped by the codegen.
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_ctor_pattern_first_two_sum() {
|
fn nested_ctor_pattern_first_two_sum() {
|
||||||
let stdout = build_and_run("nested_pat.ail.json");
|
let stdout = build_and_run("nested_pat.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["30"]);
|
assert_eq!(lines, vec!["30"]);
|
||||||
}
|
}
|
||||||
@@ -471,7 +471,7 @@ fn nested_ctor_pattern_first_two_sum() {
|
|||||||
/// for the substituted MkPair output.
|
/// for the substituted MkPair output.
|
||||||
#[test]
|
#[test]
|
||||||
fn std_pair_demo() {
|
fn std_pair_demo() {
|
||||||
let stdout = build_and_run("std_pair_demo.ail.json");
|
let stdout = build_and_run("std_pair_demo.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["7", "9", "9", "7", "8", "18"]);
|
assert_eq!(lines, vec!["7", "9", "9", "7", "8", "18"]);
|
||||||
}
|
}
|
||||||
@@ -488,7 +488,7 @@ fn std_pair_demo() {
|
|||||||
/// another instantiation.
|
/// another instantiation.
|
||||||
#[test]
|
#[test]
|
||||||
fn std_either_demo() {
|
fn std_either_demo() {
|
||||||
let stdout = build_and_run("std_either_demo.ail.json");
|
let stdout = build_and_run("std_either_demo.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lines,
|
lines,
|
||||||
@@ -513,7 +513,7 @@ fn std_either_demo() {
|
|||||||
/// `[Left 1, Right 10, Left 2, Right 20, Right 30]`.
|
/// `[Left 1, Right 10, Left 2, Right 20, Right 30]`.
|
||||||
#[test]
|
#[test]
|
||||||
fn std_either_list_demo() {
|
fn std_either_list_demo() {
|
||||||
let stdout = build_and_run("std_either_list_demo.ail.json");
|
let stdout = build_and_run("std_either_list_demo.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["2", "3", "2", "3"]);
|
assert_eq!(lines, vec!["2", "3", "2", "3"]);
|
||||||
}
|
}
|
||||||
@@ -528,7 +528,7 @@ fn std_either_list_demo() {
|
|||||||
/// n>length) on each combinator. Expected stdout: 0, 3, 5, 5, 3, 0.
|
/// n>length) on each combinator. Expected stdout: 0, 3, 5, 5, 3, 0.
|
||||||
#[test]
|
#[test]
|
||||||
fn std_list_more_demo() {
|
fn std_list_more_demo() {
|
||||||
let stdout = build_and_run("std_list_more_demo.ail.json");
|
let stdout = build_and_run("std_list_more_demo.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["0", "3", "5", "5", "3", "0"]);
|
assert_eq!(lines, vec!["0", "3", "5", "5", "3", "0"]);
|
||||||
}
|
}
|
||||||
@@ -546,7 +546,7 @@ fn std_list_more_demo() {
|
|||||||
/// wrong values.
|
/// wrong values.
|
||||||
#[test]
|
#[test]
|
||||||
fn local_rec_factorial_demo() {
|
fn local_rec_factorial_demo() {
|
||||||
let stdout = build_and_run("local_rec_demo.ail.json");
|
let stdout = build_and_run("local_rec_demo.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["1", "6", "120"]);
|
assert_eq!(lines, vec!["1", "6", "120"]);
|
||||||
}
|
}
|
||||||
@@ -563,7 +563,7 @@ fn local_rec_factorial_demo() {
|
|||||||
/// sum_below(10)=45.
|
/// sum_below(10)=45.
|
||||||
#[test]
|
#[test]
|
||||||
fn local_rec_capture_demo() {
|
fn local_rec_capture_demo() {
|
||||||
let stdout = build_and_run("local_rec_capture.ail.json");
|
let stdout = build_and_run("local_rec_capture.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["0", "10", "45"]);
|
assert_eq!(lines, vec!["0", "10", "45"]);
|
||||||
}
|
}
|
||||||
@@ -581,7 +581,7 @@ fn local_rec_capture_demo() {
|
|||||||
/// count_below(15)=9 (i in 1..9 are below threshold 10).
|
/// count_below(15)=9 (i in 1..9 are below threshold 10).
|
||||||
#[test]
|
#[test]
|
||||||
fn local_rec_let_capture_demo() {
|
fn local_rec_let_capture_demo() {
|
||||||
let stdout = build_and_run("local_rec_let_capture.ail.json");
|
let stdout = build_and_run("local_rec_let_capture.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["0", "5", "9"]);
|
assert_eq!(lines, vec!["0", "5", "9"]);
|
||||||
}
|
}
|
||||||
@@ -601,7 +601,7 @@ fn local_rec_let_capture_demo() {
|
|||||||
/// count_below(MkPair 10 15)=9.
|
/// count_below(MkPair 10 15)=9.
|
||||||
#[test]
|
#[test]
|
||||||
fn local_rec_match_capture_demo() {
|
fn local_rec_match_capture_demo() {
|
||||||
let stdout = build_and_run("local_rec_match_capture.ail.json");
|
let stdout = build_and_run("local_rec_match_capture.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["0", "5", "9"]);
|
assert_eq!(lines, vec!["0", "5", "9"]);
|
||||||
}
|
}
|
||||||
@@ -617,7 +617,7 @@ fn local_rec_match_capture_demo() {
|
|||||||
/// Expected stdout: 120 (= apply5 factorial = factorial 5).
|
/// Expected stdout: 120 (= apply5 factorial = factorial 5).
|
||||||
#[test]
|
#[test]
|
||||||
fn local_rec_as_value_demo() {
|
fn local_rec_as_value_demo() {
|
||||||
let stdout = build_and_run("local_rec_as_value.ail.json");
|
let stdout = build_and_run("local_rec_as_value.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["120"]);
|
assert_eq!(lines, vec!["120"]);
|
||||||
}
|
}
|
||||||
@@ -633,7 +633,7 @@ fn local_rec_as_value_demo() {
|
|||||||
/// Expected stdout: 1320 (= 5*4*3*2*(1+10)), 12120 (= 5*4*3*2*(1+100)).
|
/// Expected stdout: 1320 (= 5*4*3*2*(1+10)), 12120 (= 5*4*3*2*(1+100)).
|
||||||
#[test]
|
#[test]
|
||||||
fn local_rec_as_value_capture_demo() {
|
fn local_rec_as_value_capture_demo() {
|
||||||
let stdout = build_and_run("local_rec_as_value_capture.ail.json");
|
let stdout = build_and_run("local_rec_as_value_capture.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["1320", "12120"]);
|
assert_eq!(lines, vec!["1320", "12120"]);
|
||||||
}
|
}
|
||||||
@@ -661,7 +661,7 @@ fn local_rec_as_value_capture_demo() {
|
|||||||
/// applied 4 times to false — even count of flips ⇒ false).
|
/// applied 4 times to false — even count of flips ⇒ false).
|
||||||
#[test]
|
#[test]
|
||||||
fn poly_rec_capture_demo() {
|
fn poly_rec_capture_demo() {
|
||||||
let stdout = build_and_run("poly_rec_capture.ail.json");
|
let stdout = build_and_run("poly_rec_capture.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["5", "false"]);
|
assert_eq!(lines, vec!["5", "false"]);
|
||||||
}
|
}
|
||||||
@@ -686,7 +686,7 @@ fn poly_rec_capture_demo() {
|
|||||||
/// 1..=n. Expected stdout: 1, 6, 15 for n in {1, 3, 5}.
|
/// 1..=n. Expected stdout: 1, 6, 15 for n in {1, 3, 5}.
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_let_rec_demo() {
|
fn nested_let_rec_demo() {
|
||||||
let stdout = build_and_run("nested_let_rec.ail.json");
|
let stdout = build_and_run("nested_let_rec.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["1", "6", "15"]);
|
assert_eq!(lines, vec!["1", "6", "15"]);
|
||||||
}
|
}
|
||||||
@@ -781,7 +781,7 @@ fn diff_detects_changed_def() {
|
|||||||
fn diff_no_changes_exit_zero() {
|
fn diff_no_changes_exit_zero() {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let src = workspace.join("examples").join("sum.ail.json");
|
let src = workspace.join("examples").join("sum.ail");
|
||||||
|
|
||||||
let output = Command::new(ail_bin())
|
let output = Command::new(ail_bin())
|
||||||
.args([
|
.args([
|
||||||
@@ -822,7 +822,7 @@ fn diff_no_changes_exit_zero() {
|
|||||||
fn workspace_lists_imported_modules() {
|
fn workspace_lists_imported_modules() {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let entry = workspace.join("examples").join("ws_main.ail.json");
|
let entry = workspace.join("examples").join("ws_main.ail");
|
||||||
|
|
||||||
let output = Command::new(ail_bin())
|
let output = Command::new(ail_bin())
|
||||||
.args(["workspace", entry.to_str().unwrap(), "--json"])
|
.args(["workspace", entry.to_str().unwrap(), "--json"])
|
||||||
@@ -866,7 +866,7 @@ fn workspace_lists_imported_modules() {
|
|||||||
fn check_workspace_resolves_import() {
|
fn check_workspace_resolves_import() {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let entry = workspace.join("examples").join("ws_main.ail.json");
|
let entry = workspace.join("examples").join("ws_main.ail");
|
||||||
|
|
||||||
let output = Command::new(ail_bin())
|
let output = Command::new(ail_bin())
|
||||||
.args(["check", entry.to_str().unwrap(), "--json"])
|
.args(["check", entry.to_str().unwrap(), "--json"])
|
||||||
@@ -891,7 +891,7 @@ fn check_workspace_resolves_import() {
|
|||||||
/// `@ail_ws_main_main` must call `@ail_ws_lib_add(2,3)` and print `5`.
|
/// `@ail_ws_main_main` must call `@ail_ws_lib_add(2,3)` and print `5`.
|
||||||
#[test]
|
#[test]
|
||||||
fn workspace_build_runs_imported_fn() {
|
fn workspace_build_runs_imported_fn() {
|
||||||
let stdout = build_and_run("ws_main.ail.json");
|
let stdout = build_and_run("ws_main.ail");
|
||||||
assert_eq!(stdout.trim(), "5", "ws_lib.add(2,3) should print 5");
|
assert_eq!(stdout.trim(), "5", "ws_lib.add(2,3) should print 5");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -904,7 +904,7 @@ fn workspace_build_runs_imported_fn() {
|
|||||||
/// `imports: ["prelude"]` entry in the source module.
|
/// `imports: ["prelude"]` entry in the source module.
|
||||||
#[test]
|
#[test]
|
||||||
fn ordering_match_via_prelude_prints_1() {
|
fn ordering_match_via_prelude_prints_1() {
|
||||||
let stdout = build_and_run("ordering_match.ail.json");
|
let stdout = build_and_run("ordering_match.ail");
|
||||||
assert_eq!(stdout, "1\n");
|
assert_eq!(stdout, "1\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -923,7 +923,7 @@ fn ordering_match_via_prelude_prints_1() {
|
|||||||
/// is type-driven, and the run completes cleanly.
|
/// is type-driven, and the run completes cleanly.
|
||||||
#[test]
|
#[test]
|
||||||
fn compare_primitives_smoke_prints_1_2_3_thrice() {
|
fn compare_primitives_smoke_prints_1_2_3_thrice() {
|
||||||
let stdout = build_and_run("compare_primitives_smoke.ail.json");
|
let stdout = build_and_run("compare_primitives_smoke.ail");
|
||||||
assert_eq!(stdout, "1\n2\n3\n1\n2\n3\n1\n2\n3\n");
|
assert_eq!(stdout, "1\n2\n3\n1\n2\n3\n1\n2\n3\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,7 +935,7 @@ fn compare_primitives_smoke_prints_1_2_3_thrice() {
|
|||||||
/// call. Equal-call lines are `1`, unequal-call lines are `0`.
|
/// call. Equal-call lines are `1`, unequal-call lines are `0`.
|
||||||
#[test]
|
#[test]
|
||||||
fn eq_primitives_smoke_compiles_and_runs() {
|
fn eq_primitives_smoke_compiles_and_runs() {
|
||||||
let stdout = build_and_run("eq_primitives_smoke.ail.json");
|
let stdout = build_and_run("eq_primitives_smoke.ail");
|
||||||
assert_eq!(stdout, "1\n0\n1\n0\n1\n0\n");
|
assert_eq!(stdout, "1\n0\n1\n0\n1\n0\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,7 +945,7 @@ fn eq_primitives_smoke_compiles_and_runs() {
|
|||||||
fn manifest_workspace_lists_all_defs() {
|
fn manifest_workspace_lists_all_defs() {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let entry = workspace.join("examples").join("ws_main.ail.json");
|
let entry = workspace.join("examples").join("ws_main.ail");
|
||||||
|
|
||||||
let output = Command::new(ail_bin())
|
let output = Command::new(ail_bin())
|
||||||
.args([
|
.args([
|
||||||
@@ -988,7 +988,7 @@ fn manifest_workspace_lists_all_defs() {
|
|||||||
fn describe_workspace_resolves_qualified_name() {
|
fn describe_workspace_resolves_qualified_name() {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let entry = workspace.join("examples").join("ws_main.ail.json");
|
let entry = workspace.join("examples").join("ws_main.ail");
|
||||||
|
|
||||||
let output = Command::new(ail_bin())
|
let output = Command::new(ail_bin())
|
||||||
.args([
|
.args([
|
||||||
@@ -1031,7 +1031,7 @@ fn describe_workspace_resolves_qualified_name() {
|
|||||||
fn deps_filters_builtins_params_locals() {
|
fn deps_filters_builtins_params_locals() {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let src = workspace.join("examples").join("sum.ail.json");
|
let src = workspace.join("examples").join("sum.ail");
|
||||||
|
|
||||||
let output = Command::new(ail_bin())
|
let output = Command::new(ail_bin())
|
||||||
.args(["deps", src.to_str().unwrap(), "--of", "sum", "--json"])
|
.args(["deps", src.to_str().unwrap(), "--of", "sum", "--json"])
|
||||||
@@ -1070,7 +1070,7 @@ fn deps_filters_builtins_params_locals() {
|
|||||||
fn deps_workspace_filters_builtins_and_params() {
|
fn deps_workspace_filters_builtins_and_params() {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let entry = workspace.join("examples").join("ws_main.ail.json");
|
let entry = workspace.join("examples").join("ws_main.ail");
|
||||||
|
|
||||||
let output = Command::new(ail_bin())
|
let output = Command::new(ail_bin())
|
||||||
.args(["deps", entry.to_str().unwrap(), "--workspace", "--json"])
|
.args(["deps", entry.to_str().unwrap(), "--workspace", "--json"])
|
||||||
@@ -1096,7 +1096,7 @@ fn deps_workspace_filters_builtins_and_params() {
|
|||||||
fn deps_workspace_includes_cross_module() {
|
fn deps_workspace_includes_cross_module() {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let entry = workspace.join("examples").join("ws_main.ail.json");
|
let entry = workspace.join("examples").join("ws_main.ail");
|
||||||
|
|
||||||
let output = Command::new(ail_bin())
|
let output = Command::new(ail_bin())
|
||||||
.args(["deps", entry.to_str().unwrap(), "--workspace", "--json"])
|
.args(["deps", entry.to_str().unwrap(), "--workspace", "--json"])
|
||||||
@@ -1266,7 +1266,7 @@ fn check_json_unbound_var() {
|
|||||||
/// chain default).
|
/// chain default).
|
||||||
#[test]
|
#[test]
|
||||||
fn lit_pat_demo() {
|
fn lit_pat_demo() {
|
||||||
let stdout = build_and_run("lit_pat.ail.json");
|
let stdout = build_and_run("lit_pat.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["100", "200", "999", "-1", "0", "7"]);
|
assert_eq!(lines, vec!["100", "200", "999", "-1", "0", "7"]);
|
||||||
}
|
}
|
||||||
@@ -1281,7 +1281,7 @@ fn lit_pat_demo() {
|
|||||||
/// print the expected outputs.
|
/// print the expected outputs.
|
||||||
#[test]
|
#[test]
|
||||||
fn unreachable_demo() {
|
fn unreachable_demo() {
|
||||||
let stdout = build_and_run("unreachable_demo.ail.json");
|
let stdout = build_and_run("unreachable_demo.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["4", "5"]);
|
assert_eq!(lines, vec!["4", "5"]);
|
||||||
}
|
}
|
||||||
@@ -1321,7 +1321,7 @@ fn borrow_own_demo_modes_are_metadata_only() {
|
|||||||
"expected `param_modes:[\"own\"]` in canonical JSON; the schema for `(own T)` regressed"
|
"expected `param_modes:[\"own\"]` in canonical JSON; the schema for `(own T)` regressed"
|
||||||
);
|
);
|
||||||
|
|
||||||
let stdout = build_and_run("borrow_own_demo.ail.json");
|
let stdout = build_and_run("borrow_own_demo.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(lines, vec!["3", "6"]);
|
assert_eq!(lines, vec!["3", "6"]);
|
||||||
}
|
}
|
||||||
@@ -1338,7 +1338,7 @@ fn borrow_own_demo_modes_are_metadata_only() {
|
|||||||
/// non-Int args at typecheck.
|
/// non-Int args at typecheck.
|
||||||
#[test]
|
#[test]
|
||||||
fn eq_demo() {
|
fn eq_demo() {
|
||||||
let stdout = build_and_run("eq_demo.ail.json");
|
let stdout = build_and_run("eq_demo.ail");
|
||||||
let lines: Vec<&str> = stdout.lines().collect();
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lines,
|
lines,
|
||||||
@@ -1374,7 +1374,7 @@ fn alloc_rc_produces_same_stdout_as_gc() {
|
|||||||
/// passthrough with `call void @ailang_rc_inc` under `--alloc=rc`.
|
/// passthrough with `call void @ailang_rc_inc` under `--alloc=rc`.
|
||||||
#[test]
|
#[test]
|
||||||
fn clone_demo_is_identity_in_18c1() {
|
fn clone_demo_is_identity_in_18c1() {
|
||||||
let stdout = build_and_run("clone_demo.ail.json");
|
let stdout = build_and_run("clone_demo.ail");
|
||||||
assert_eq!(stdout.trim(), "42");
|
assert_eq!(stdout.trim(), "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1413,11 +1413,11 @@ fn reuse_as_demo_under_rc_uses_inplace_rewrite() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// (2) gc baseline.
|
// (2) gc baseline.
|
||||||
let stdout_gc = build_and_run_with_alloc("reuse_as_demo.ail.json", "gc");
|
let stdout_gc = build_and_run_with_alloc("reuse_as_demo.ail", "gc");
|
||||||
assert_eq!(stdout_gc.trim(), "9");
|
assert_eq!(stdout_gc.trim(), "9");
|
||||||
// (3) rc matches gc, (4) clean exit (build_and_run_with_alloc
|
// (3) rc matches gc, (4) clean exit (build_and_run_with_alloc
|
||||||
// panics on non-zero status).
|
// panics on non-zero status).
|
||||||
let stdout_rc = build_and_run_with_alloc("reuse_as_demo.ail.json", "rc");
|
let stdout_rc = build_and_run_with_alloc("reuse_as_demo.ail", "rc");
|
||||||
assert_eq!(stdout_rc.trim(), "9");
|
assert_eq!(stdout_rc.trim(), "9");
|
||||||
|
|
||||||
// (5) Inspect the rc IR text. We re-lower via the codegen API
|
// (5) Inspect the rc IR text. We re-lower via the codegen API
|
||||||
@@ -2176,7 +2176,7 @@ fn build_and_run_with_rc_stats(example: &str) -> (String, u64, u64, i64) {
|
|||||||
#[test]
|
#[test]
|
||||||
fn alloc_rc_explicit_mode_tail_sum_does_not_leak_outer_cells() {
|
fn alloc_rc_explicit_mode_tail_sum_does_not_leak_outer_cells() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("rc_tail_sum_explicit_leak.ail.json");
|
build_and_run_with_rc_stats("rc_tail_sum_explicit_leak.ail");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stdout.trim(),
|
stdout.trim(),
|
||||||
"4950",
|
"4950",
|
||||||
@@ -2211,7 +2211,7 @@ fn alloc_rc_explicit_mode_tail_sum_does_not_leak_outer_cells() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn alloc_rc_let_binder_for_owned_returning_app_drops_at_scope_close() {
|
fn alloc_rc_let_binder_for_owned_returning_app_drops_at_scope_close() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("rc_let_owned_app_leak.ail.json");
|
build_and_run_with_rc_stats("rc_let_owned_app_leak.ail");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stdout.trim(),
|
stdout.trim(),
|
||||||
"1",
|
"1",
|
||||||
@@ -2255,7 +2255,7 @@ fn alloc_rc_let_binder_for_owned_returning_app_drops_at_scope_close() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn alloc_rc_let_binder_for_implicit_returning_app_does_not_drop() {
|
fn alloc_rc_let_binder_for_implicit_returning_app_does_not_drop() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("rc_let_implicit_returning_app.ail.json");
|
build_and_run_with_rc_stats("rc_let_implicit_returning_app.ail");
|
||||||
assert_eq!(stdout.trim(), "7", "alloc(7) -> unbox should print 7");
|
assert_eq!(stdout.trim(), "7", "alloc(7) -> unbox should print 7");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
live, 1,
|
live, 1,
|
||||||
@@ -2339,7 +2339,7 @@ fn alloc_rc_let_alias_of_implicit_param_does_not_dec_borrowed_children() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn alloc_rc_partial_drop_at_fn_return_does_not_leak_unmoved_fields() {
|
fn alloc_rc_partial_drop_at_fn_return_does_not_leak_unmoved_fields() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("rc_own_param_partial_drop_leak.ail.json");
|
build_and_run_with_rc_stats("rc_own_param_partial_drop_leak.ail");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stdout.trim(),
|
stdout.trim(),
|
||||||
"1",
|
"1",
|
||||||
@@ -2378,7 +2378,7 @@ fn alloc_rc_partial_drop_at_fn_return_does_not_leak_unmoved_fields() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn alloc_rc_partial_drop_at_arm_close_does_not_leak_unmoved_fields() {
|
fn alloc_rc_partial_drop_at_arm_close_does_not_leak_unmoved_fields() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("rc_match_arm_partial_drop_leak.ail.json");
|
build_and_run_with_rc_stats("rc_match_arm_partial_drop_leak.ail");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stdout.trim(),
|
stdout.trim(),
|
||||||
"1",
|
"1",
|
||||||
@@ -2411,7 +2411,7 @@ fn alloc_rc_partial_drop_at_arm_close_does_not_leak_unmoved_fields() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn alloc_rc_partial_drop_at_app_let_close_does_not_leak_unmoved_fields() {
|
fn alloc_rc_partial_drop_at_app_let_close_does_not_leak_unmoved_fields() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("rc_app_let_partial_drop_leak.ail.json");
|
build_and_run_with_rc_stats("rc_app_let_partial_drop_leak.ail");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stdout.trim(),
|
stdout.trim(),
|
||||||
"1",
|
"1",
|
||||||
@@ -2571,7 +2571,7 @@ fn ail_run_accepts_ail_source_with_same_stdout_as_ail_json() {
|
|||||||
/// every alloc strategy after the hs.4 hoist).
|
/// every alloc strategy after the hs.4 hoist).
|
||||||
#[test]
|
#[test]
|
||||||
fn int_to_str_smoke() {
|
fn int_to_str_smoke() {
|
||||||
let out = build_and_run("int_to_str_smoke.ail.json");
|
let out = build_and_run("int_to_str_smoke.ail");
|
||||||
assert_eq!(out, "42\n");
|
assert_eq!(out, "42\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2584,7 +2584,7 @@ fn int_to_str_smoke() {
|
|||||||
/// trip-wire that surfaces it.
|
/// trip-wire that surfaces it.
|
||||||
#[test]
|
#[test]
|
||||||
fn float_to_str_smoke() {
|
fn float_to_str_smoke() {
|
||||||
let out = build_and_run("float_to_str_smoke.ail.json");
|
let out = build_and_run("float_to_str_smoke.ail");
|
||||||
assert_eq!(out, "3.5\n");
|
assert_eq!(out, "3.5\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2600,7 +2600,7 @@ fn float_to_str_smoke() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn int_to_str_drop_balances_rc_stats() {
|
fn int_to_str_drop_balances_rc_stats() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("int_to_str_drop_rc.ail.json");
|
build_and_run_with_rc_stats("int_to_str_drop_rc.ail");
|
||||||
assert_eq!(stdout, "42\n");
|
assert_eq!(stdout, "42\n");
|
||||||
assert!(allocs >= 1, "expected at least one heap-Str allocation; got allocs={allocs}");
|
assert!(allocs >= 1, "expected at least one heap-Str allocation; got allocs={allocs}");
|
||||||
assert_eq!(allocs, frees, "every heap-Str alloc must be freed; allocs={allocs}, frees={frees}");
|
assert_eq!(allocs, frees, "every heap-Str alloc must be freed; allocs={allocs}, frees={frees}");
|
||||||
@@ -2616,7 +2616,7 @@ fn int_to_str_drop_balances_rc_stats() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn str_field_in_adt_drops_heap_str_correctly() {
|
fn str_field_in_adt_drops_heap_str_correctly() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("str_field_in_adt_heap.ail.json");
|
build_and_run_with_rc_stats("str_field_in_adt_heap.ail");
|
||||||
assert_eq!(stdout, "42\n");
|
assert_eq!(stdout, "42\n");
|
||||||
assert!(allocs >= 2, "expected ADT cell + heap-Str slab; got allocs={allocs}");
|
assert!(allocs >= 2, "expected ADT cell + heap-Str slab; got allocs={allocs}");
|
||||||
assert_eq!(allocs, frees, "every RC slab must be freed; allocs={allocs}, frees={frees}");
|
assert_eq!(allocs, frees, "every RC slab must be freed; allocs={allocs}, frees={frees}");
|
||||||
@@ -2631,7 +2631,7 @@ fn str_field_in_adt_drops_heap_str_correctly() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn int_arg_to_effect_op_does_not_rc_track() {
|
fn int_arg_to_effect_op_does_not_rc_track() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("int_to_print_int_borrow.ail.json");
|
build_and_run_with_rc_stats("int_to_print_int_borrow.ail");
|
||||||
assert_eq!(stdout, "7\n");
|
assert_eq!(stdout, "7\n");
|
||||||
assert_eq!(allocs, 0, "Int arg should not trigger any RC alloc; got allocs={allocs}");
|
assert_eq!(allocs, 0, "Int arg should not trigger any RC alloc; got allocs={allocs}");
|
||||||
assert_eq!(frees, 0, "Int arg should not trigger any RC free; got frees={frees}");
|
assert_eq!(frees, 0, "Int arg should not trigger any RC free; got frees={frees}");
|
||||||
@@ -2649,7 +2649,7 @@ fn int_arg_to_effect_op_does_not_rc_track() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn heap_str_repeated_print_balances_rc_stats() {
|
fn heap_str_repeated_print_balances_rc_stats() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("heap_str_repeated_print_borrow.ail.json");
|
build_and_run_with_rc_stats("heap_str_repeated_print_borrow.ail");
|
||||||
assert_eq!(stdout, "42\n42\n");
|
assert_eq!(stdout, "42\n42\n");
|
||||||
assert_eq!(allocs, 1, "expected exactly one heap-Str slab; got allocs={allocs}");
|
assert_eq!(allocs, 1, "expected exactly one heap-Str slab; got allocs={allocs}");
|
||||||
assert_eq!(frees, 1, "expected exactly one free; got frees={frees}");
|
assert_eq!(frees, 1, "expected exactly one free; got frees={frees}");
|
||||||
@@ -2665,7 +2665,7 @@ fn heap_str_repeated_print_balances_rc_stats() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bool_to_str_drop_balances_rc_stats() {
|
fn bool_to_str_drop_balances_rc_stats() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("bool_to_str_drop_rc.ail.json");
|
build_and_run_with_rc_stats("bool_to_str_drop_rc.ail");
|
||||||
assert_eq!(stdout, "true\n");
|
assert_eq!(stdout, "true\n");
|
||||||
assert_eq!(allocs, 1, "expected exactly one heap-Str slab; got allocs={allocs}");
|
assert_eq!(allocs, 1, "expected exactly one heap-Str slab; got allocs={allocs}");
|
||||||
assert_eq!(frees, 1, "expected exactly one free; got frees={frees}");
|
assert_eq!(frees, 1, "expected exactly one free; got frees={frees}");
|
||||||
@@ -2678,7 +2678,7 @@ fn bool_to_str_drop_balances_rc_stats() {
|
|||||||
/// "true" slab.
|
/// "true" slab.
|
||||||
#[test]
|
#[test]
|
||||||
fn bool_to_str_emits_true_branch() {
|
fn bool_to_str_emits_true_branch() {
|
||||||
let out = build_and_run("bool_to_str_drop_rc.ail.json");
|
let out = build_and_run("bool_to_str_drop_rc.ail");
|
||||||
assert_eq!(out, "true\n");
|
assert_eq!(out, "true\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2686,7 +2686,7 @@ fn bool_to_str_emits_true_branch() {
|
|||||||
/// content of `ailang_bool_to_str`'s "false" slab.
|
/// content of `ailang_bool_to_str`'s "false" slab.
|
||||||
#[test]
|
#[test]
|
||||||
fn bool_to_str_emits_false_branch() {
|
fn bool_to_str_emits_false_branch() {
|
||||||
let out = build_and_run("bool_to_str_smoke_false.ail.json");
|
let out = build_and_run("bool_to_str_smoke_false.ail");
|
||||||
assert_eq!(out, "false\n");
|
assert_eq!(out, "false\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2698,7 +2698,7 @@ fn bool_to_str_emits_false_branch() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn str_clone_drop_balances_rc_stats() {
|
fn str_clone_drop_balances_rc_stats() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("str_clone_drop_rc.ail.json");
|
build_and_run_with_rc_stats("str_clone_drop_rc.ail");
|
||||||
assert_eq!(stdout, "hello\n");
|
assert_eq!(stdout, "hello\n");
|
||||||
assert_eq!(allocs, 1, "expected exactly one heap-Str clone slab; got allocs={allocs}");
|
assert_eq!(allocs, 1, "expected exactly one heap-Str clone slab; got allocs={allocs}");
|
||||||
assert_eq!(frees, 1, "expected exactly one free; got frees={frees}");
|
assert_eq!(frees, 1, "expected exactly one free; got frees={frees}");
|
||||||
@@ -2716,7 +2716,7 @@ fn str_clone_drop_balances_rc_stats() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn str_clone_cross_realisation_uniform_abi() {
|
fn str_clone_cross_realisation_uniform_abi() {
|
||||||
let (stdout, allocs, frees, live) =
|
let (stdout, allocs, frees, live) =
|
||||||
build_and_run_with_rc_stats("str_clone_cross_realisation.ail.json");
|
build_and_run_with_rc_stats("str_clone_cross_realisation.ail");
|
||||||
assert_eq!(stdout, "42\nabc\n");
|
assert_eq!(stdout, "42\nabc\n");
|
||||||
assert_eq!(allocs, 3, "expected one int_to_str slab + two str_clone slabs; got allocs={allocs}");
|
assert_eq!(allocs, 3, "expected one int_to_str slab + two str_clone slabs; got allocs={allocs}");
|
||||||
assert_eq!(frees, 3, "expected three matching frees; got frees={frees}");
|
assert_eq!(frees, 3, "expected three matching frees; got frees={frees}");
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
//! than seeing a bare "no instance" message.
|
//! than seeing a bare "no instance" message.
|
||||||
|
|
||||||
use ailang_check::check_workspace;
|
use ailang_check::check_workspace;
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn fixture(name: &str) -> PathBuf {
|
fn fixture(name: &str) -> PathBuf {
|
||||||
@@ -19,7 +19,7 @@ fn fixture(name: &str) -> PathBuf {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn eq_at_float_fires_float_aware_noinstance() {
|
fn eq_at_float_fires_float_aware_noinstance() {
|
||||||
let ws = load_workspace(&fixture("eq_float_noinstance.ail.json")).expect("load");
|
let ws = load_workspace(&fixture("eq_float_noinstance.ail")).expect("load");
|
||||||
let diags = check_workspace(&ws);
|
let diags = check_workspace(&ws);
|
||||||
|
|
||||||
assert!(!diags.is_empty(), "expected NoInstance diagnostic, got none");
|
assert!(!diags.is_empty(), "expected NoInstance diagnostic, got none");
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
use ailang_check::{check_workspace, monomorphise_workspace};
|
use ailang_check::{check_workspace, monomorphise_workspace};
|
||||||
use ailang_core::ast::Def;
|
use ailang_core::ast::Def;
|
||||||
use ailang_core::hash::def_hash;
|
use ailang_core::hash::def_hash;
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ fn build_and_run(fixture: &str) -> String {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn eq_ord_polymorphic_runs_end_to_end() {
|
fn eq_ord_polymorphic_runs_end_to_end() {
|
||||||
let stdout = build_and_run("eq_ord_polymorphic.ail.json");
|
let stdout = build_and_run("eq_ord_polymorphic.ail");
|
||||||
// Helper `at_most(3, 5)` at Int = true → 1
|
// Helper `at_most(3, 5)` at Int = true → 1
|
||||||
// `at_most(true, false)` at Bool = false → 0
|
// `at_most(true, false)` at Bool = false → 0
|
||||||
// `at_most("abc", "abd")` at Str = true → 1
|
// `at_most("abc", "abd")` at Str = true → 1
|
||||||
@@ -70,7 +70,7 @@ fn eq_ord_polymorphic_runs_end_to_end() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn eq_ord_user_adt_runs_end_to_end() {
|
fn eq_ord_user_adt_runs_end_to_end() {
|
||||||
let stdout = build_and_run("eq_ord_user_adt.ail.json");
|
let stdout = build_and_run("eq_ord_user_adt.ail");
|
||||||
// eq(MkIntBox 3, MkIntBox 3) = true → 1
|
// eq(MkIntBox 3, MkIntBox 3) = true → 1
|
||||||
// eq(MkIntBox 3, MkIntBox 5) = false → 0
|
// eq(MkIntBox 3, MkIntBox 5) = false → 0
|
||||||
// `io/print_int` emits one int per line; trimmed stdout is "1\n0".
|
// `io/print_int` emits one int per line; trimmed stdout is "1\n0".
|
||||||
@@ -79,7 +79,7 @@ fn eq_ord_user_adt_runs_end_to_end() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn eq_ord_user_adt_eq_intbox_hash_stable() {
|
fn eq_ord_user_adt_eq_intbox_hash_stable() {
|
||||||
let ws = load_workspace(&fixture_path("eq_ord_user_adt.ail.json")).expect("load");
|
let ws = load_workspace(&fixture_path("eq_ord_user_adt.ail")).expect("load");
|
||||||
let diags = check_workspace(&ws);
|
let diags = check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "typecheck failed: {diags:#?}");
|
assert!(diags.is_empty(), "typecheck failed: {diags:#?}");
|
||||||
let mono = monomorphise_workspace(&ws).expect("mono");
|
let mono = monomorphise_workspace(&ws).expect("mono");
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ fn workspace_root() -> PathBuf {
|
|||||||
#[test]
|
#[test]
|
||||||
fn floats_example_prints_expected_stdout() {
|
fn floats_example_prints_expected_stdout() {
|
||||||
let root = workspace_root();
|
let root = workspace_root();
|
||||||
let example = root.join("examples").join("floats.ail.json");
|
let example = root.join("examples").join("floats.ail");
|
||||||
assert!(example.exists(), "expected fixture at {:?}", example);
|
assert!(example.exists(), "expected fixture at {:?}", example);
|
||||||
|
|
||||||
// Build via `cargo run -p ail -- build` to use the in-tree CLI.
|
// Build via `cargo run -p ail -- build` to use the in-tree CLI.
|
||||||
|
|||||||
@@ -134,22 +134,22 @@ fn check_ir_snapshot(example: &str, snapshot_name: &str) {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ir_snapshot_sum() {
|
fn ir_snapshot_sum() {
|
||||||
check_ir_snapshot("sum.ail.json", "sum.ll");
|
check_ir_snapshot("sum.ail", "sum.ll");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ir_snapshot_max3() {
|
fn ir_snapshot_max3() {
|
||||||
check_ir_snapshot("max3.ail.json", "max3.ll");
|
check_ir_snapshot("max3.ail", "max3.ll");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ir_snapshot_hello() {
|
fn ir_snapshot_hello() {
|
||||||
check_ir_snapshot("hello.ail.json", "hello.ll");
|
check_ir_snapshot("hello.ail", "hello.ll");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ir_snapshot_list() {
|
fn ir_snapshot_list() {
|
||||||
check_ir_snapshot("list.ail.json", "list.ll");
|
check_ir_snapshot("list.ail", "list.ll");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Guards workspace lowering (Iter 5c): the entry module `ws_main`
|
/// Guards workspace lowering (Iter 5c): the entry module `ws_main`
|
||||||
@@ -158,5 +158,5 @@ fn ir_snapshot_list() {
|
|||||||
/// `@main` calls `@ail_ws_main_main`.
|
/// `@main` calls `@ail_ws_main_main`.
|
||||||
#[test]
|
#[test]
|
||||||
fn ir_snapshot_ws_main() {
|
fn ir_snapshot_ws_main() {
|
||||||
check_ir_snapshot("ws_main.ail.json", "ws_main.ll");
|
check_ir_snapshot("ws_main.ail", "ws_main.ll");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,21 +6,21 @@
|
|||||||
|
|
||||||
use ailang_core::ast::Def;
|
use ailang_core::ast::Def;
|
||||||
use ailang_core::hash::def_hash;
|
use ailang_core::hash::def_hash;
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn fixture_path() -> PathBuf {
|
fn fixture_path() -> PathBuf {
|
||||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
d.pop();
|
d.pop();
|
||||||
d.pop();
|
d.pop();
|
||||||
d.join("examples").join("mono_hash_pin_smoke.ail.json")
|
d.join("examples").join("mono_hash_pin_smoke.ail")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_fixture_path() -> PathBuf {
|
fn show_fixture_path() -> PathBuf {
|
||||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
d.pop();
|
d.pop();
|
||||||
d.pop();
|
d.pop();
|
||||||
d.join("examples").join("show_mono_pin_smoke.ail.json")
|
d.join("examples").join("show_mono_pin_smoke.ail")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ fn mono_pass_handles_recursive_fn_in_class_workspace() {
|
|||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let src = workspace
|
let src = workspace
|
||||||
.join("examples")
|
.join("examples")
|
||||||
.join("test_mono_recursive_fn_bug.ail.json");
|
.join("test_mono_recursive_fn_bug.ail");
|
||||||
let tmp = std::env::temp_dir().join(format!(
|
let tmp = std::env::temp_dir().join(format!(
|
||||||
"ailang_mono_recursive_fn_{}",
|
"ailang_mono_recursive_fn_{}",
|
||||||
std::process::id()
|
std::process::id()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
use ailang_check::mono::{mono_target_key, MonoTarget};
|
use ailang_check::mono::{mono_target_key, MonoTarget};
|
||||||
use ailang_core::ast::{Def, Type};
|
use ailang_core::ast::{Def, Type};
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn fixture(name: &str) -> PathBuf {
|
fn fixture(name: &str) -> PathBuf {
|
||||||
@@ -19,7 +19,7 @@ fn fixture(name: &str) -> PathBuf {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cmp_max_smoke_produces_both_mono_symbols_in_one_fixpoint() {
|
fn cmp_max_smoke_produces_both_mono_symbols_in_one_fixpoint() {
|
||||||
let ws = load_workspace(&fixture("cmp_max_smoke.ail.json"))
|
let ws = load_workspace(&fixture("cmp_max_smoke.ail"))
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
||||||
@@ -62,7 +62,7 @@ fn cmp_max_smoke_produces_both_mono_symbols_in_one_fixpoint() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn collect_mono_targets_emits_free_fn_target_for_cmp_max_at_int() {
|
fn collect_mono_targets_emits_free_fn_target_for_cmp_max_at_int() {
|
||||||
use ailang_check::mono::collect_mono_targets;
|
use ailang_check::mono::collect_mono_targets;
|
||||||
let ws = load_workspace(&fixture("cmp_max_smoke.ail.json")).expect("workspace loads");
|
let ws = load_workspace(&fixture("cmp_max_smoke.ail")).expect("workspace loads");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
||||||
let env = ailang_check::build_check_env(&ws);
|
let env = ailang_check::build_check_env(&ws);
|
||||||
@@ -93,7 +93,7 @@ fn cmp_max_smoke_runs_end_to_end() {
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
let workspace = manifest_dir.parent().unwrap().parent().unwrap();
|
let workspace = manifest_dir.parent().unwrap().parent().unwrap();
|
||||||
let src = workspace.join("examples").join("cmp_max_smoke.ail.json");
|
let src = workspace.join("examples").join("cmp_max_smoke.ail");
|
||||||
let tmp = std::env::temp_dir().join(format!(
|
let tmp = std::env::temp_dir().join(format!(
|
||||||
"ailang_cmp_max_smoke_{}",
|
"ailang_cmp_max_smoke_{}",
|
||||||
std::process::id()
|
std::process::id()
|
||||||
@@ -123,7 +123,7 @@ fn workspace_with_only_poly_free_fn_and_no_classes_still_monomorphises() {
|
|||||||
// the predicate to also include `Def::Fn` with `Type::Forall`
|
// the predicate to also include `Def::Fn` with `Type::Forall`
|
||||||
// as a specialisable target — which makes the predicate true
|
// as a specialisable target — which makes the predicate true
|
||||||
// for any workspace with poly free fns (regardless of classes).
|
// for any workspace with poly free fns (regardless of classes).
|
||||||
let ws = load_workspace(&fixture("poly_id.ail.json")).expect("workspace loads");
|
let ws = load_workspace(&fixture("poly_id.ail")).expect("workspace loads");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
||||||
let post_mono = ailang_check::monomorphise_workspace(&ws).expect("mono green");
|
let post_mono = ailang_check::monomorphise_workspace(&ws).expect("mono green");
|
||||||
@@ -144,7 +144,7 @@ fn workspace_with_only_poly_free_fn_and_no_classes_still_monomorphises() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rewrite_mono_calls_rewrites_bare_poly_free_fn_to_mono_symbol() {
|
fn rewrite_mono_calls_rewrites_bare_poly_free_fn_to_mono_symbol() {
|
||||||
let ws = load_workspace(&fixture("cmp_max_smoke.ail.json")).expect("workspace loads");
|
let ws = load_workspace(&fixture("cmp_max_smoke.ail")).expect("workspace loads");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
||||||
let post_mono = ailang_check::monomorphise_workspace(&ws).expect("mono green");
|
let post_mono = ailang_check::monomorphise_workspace(&ws).expect("mono green");
|
||||||
@@ -170,7 +170,7 @@ fn rewrite_mono_calls_rewrites_bare_poly_free_fn_to_mono_symbol() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn synthesise_mono_fn_free_fn_arm_substitutes_rigid_vars() {
|
fn synthesise_mono_fn_free_fn_arm_substitutes_rigid_vars() {
|
||||||
use ailang_check::mono::synthesise_mono_fn_for_free_fn;
|
use ailang_check::mono::synthesise_mono_fn_for_free_fn;
|
||||||
let ws = load_workspace(&fixture("cmp_max_smoke.ail.json")).expect("workspace loads");
|
let ws = load_workspace(&fixture("cmp_max_smoke.ail")).expect("workspace loads");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
assert!(diags.is_empty(), "typecheck diagnostics: {diags:?}");
|
||||||
|
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ fn examples_dir() -> PathBuf {
|
|||||||
/// `env.module_types` and validates the ctor by name there.
|
/// `env.module_types` and validates the ctor by name there.
|
||||||
#[test]
|
#[test]
|
||||||
fn mono_pass_handles_xmod_ctor_pattern() {
|
fn mono_pass_handles_xmod_ctor_pattern() {
|
||||||
let entry = examples_dir().join("test_mono_ctor_main.ail.json");
|
let entry = examples_dir().join("test_mono_ctor_main.ail");
|
||||||
let ws = ailang_core::load_workspace(&entry).expect("load");
|
let ws = ailang_surface::load_workspace(&entry).expect("load");
|
||||||
|
|
||||||
// Pre-condition: the workspace typechecks cleanly. The bug is
|
// Pre-condition: the workspace typechecks cleanly. The bug is
|
||||||
// localised to the mono pass; the typecheck path does not have it.
|
// localised to the mono pass; the typecheck path does not have it.
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ fn mono_pass_handles_qualified_cross_module_reference() {
|
|||||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||||
let src = workspace
|
let src = workspace
|
||||||
.join("examples")
|
.join("examples")
|
||||||
.join("test_mono_imports_main.ail.json");
|
.join("test_mono_imports_main.ail");
|
||||||
let tmp = std::env::temp_dir().join(format!(
|
let tmp = std::env::temp_dir().join(format!(
|
||||||
"ailang_mono_xmod_qualified_{}",
|
"ailang_mono_xmod_qualified_{}",
|
||||||
std::process::id()
|
std::process::id()
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ fn run_ail_check_json(entry: &str) -> std::process::Output {
|
|||||||
/// `ambiguous-method-resolution` and names both candidate classes.
|
/// `ambiguous-method-resolution` and names both candidate classes.
|
||||||
#[test]
|
#[test]
|
||||||
fn mq3_two_show_ambiguous_fires_ambiguous_method_resolution() {
|
fn mq3_two_show_ambiguous_fires_ambiguous_method_resolution() {
|
||||||
let out = run_ail_check_json("mq3_two_show_ambiguous.ail.json");
|
let out = run_ail_check_json("mq3_two_show_ambiguous.ail");
|
||||||
assert!(
|
assert!(
|
||||||
!out.status.success(),
|
!out.status.success(),
|
||||||
"expected check to reject ambiguous dispatch; stdout={} stderr={}",
|
"expected check to reject ambiguous dispatch; stdout={} stderr={}",
|
||||||
@@ -76,7 +76,7 @@ fn mq3_two_show_ambiguous_fires_ambiguous_method_resolution() {
|
|||||||
/// workspace as the ambiguous fixture. Typecheck succeeds.
|
/// workspace as the ambiguous fixture. Typecheck succeeds.
|
||||||
#[test]
|
#[test]
|
||||||
fn mq3_two_show_qualified_resolves_clean() {
|
fn mq3_two_show_qualified_resolves_clean() {
|
||||||
let out = run_ail_check_json("mq3_two_show_qualified.ail.json");
|
let out = run_ail_check_json("mq3_two_show_qualified.ail");
|
||||||
assert!(
|
assert!(
|
||||||
out.status.success(),
|
out.status.success(),
|
||||||
"expected check to succeed with explicit qualifier; stdout={} stderr={}",
|
"expected check to succeed with explicit qualifier; stdout={} stderr={}",
|
||||||
@@ -92,7 +92,7 @@ fn mq3_two_show_qualified_resolves_clean() {
|
|||||||
/// LLM-author sees the shadow.
|
/// LLM-author sees the shadow.
|
||||||
#[test]
|
#[test]
|
||||||
fn mq3_class_eq_vs_fn_eq_fn_wins_with_warning() {
|
fn mq3_class_eq_vs_fn_eq_fn_wins_with_warning() {
|
||||||
let out = run_ail_check_json("mq3_class_eq_vs_fn_eq.ail.json");
|
let out = run_ail_check_json("mq3_class_eq_vs_fn_eq.ail");
|
||||||
assert!(
|
assert!(
|
||||||
out.status.success(),
|
out.status.success(),
|
||||||
"expected check to succeed (fn wins); stdout={} stderr={}",
|
"expected check to succeed (fn wins); stdout={} stderr={}",
|
||||||
|
|||||||
@@ -16,13 +16,13 @@
|
|||||||
//! the negative case — confusing diagnostic, hard to bisect.
|
//! the negative case — confusing diagnostic, hard to bisect.
|
||||||
|
|
||||||
use ailang_check::check_workspace;
|
use ailang_check::check_workspace;
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn fixture_path() -> PathBuf {
|
fn fixture_path() -> PathBuf {
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
.join("../../examples")
|
.join("../../examples")
|
||||||
.join("show_no_instance.ail.json")
|
.join("show_no_instance.ail")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
//! the mono symbols are produced.
|
//! the mono symbols are produced.
|
||||||
|
|
||||||
use ailang_check::{check_workspace, monomorphise_workspace};
|
use ailang_check::{check_workspace, monomorphise_workspace};
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn fixture(name: &str) -> PathBuf {
|
fn fixture(name: &str) -> PathBuf {
|
||||||
@@ -38,7 +38,7 @@ fn mono_symbol_names(workspace_name: &str) -> Vec<String> {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ne_at_int_produces_mono_symbol() {
|
fn ne_at_int_produces_mono_symbol() {
|
||||||
let names = mono_symbol_names("ne_at_int_smoke.ail.json");
|
let names = mono_symbol_names("ne_at_int_smoke.ail");
|
||||||
assert!(
|
assert!(
|
||||||
names.iter().any(|n| n == "ne__Int"),
|
names.iter().any(|n| n == "ne__Int"),
|
||||||
"expected ne__Int in workspace, found: {names:#?}"
|
"expected ne__Int in workspace, found: {names:#?}"
|
||||||
@@ -47,7 +47,7 @@ fn ne_at_int_produces_mono_symbol() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lt_at_int_produces_mono_symbol() {
|
fn lt_at_int_produces_mono_symbol() {
|
||||||
let names = mono_symbol_names("lt_at_int_smoke.ail.json");
|
let names = mono_symbol_names("lt_at_int_smoke.ail");
|
||||||
assert!(
|
assert!(
|
||||||
names.iter().any(|n| n == "lt__Int"),
|
names.iter().any(|n| n == "lt__Int"),
|
||||||
"expected lt__Int in workspace, found: {names:#?}"
|
"expected lt__Int in workspace, found: {names:#?}"
|
||||||
@@ -56,7 +56,7 @@ fn lt_at_int_produces_mono_symbol() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn le_at_str_produces_mono_symbol() {
|
fn le_at_str_produces_mono_symbol() {
|
||||||
let names = mono_symbol_names("le_at_str_smoke.ail.json");
|
let names = mono_symbol_names("le_at_str_smoke.ail");
|
||||||
assert!(
|
assert!(
|
||||||
names.iter().any(|n| n == "le__Str"),
|
names.iter().any(|n| n == "le__Str"),
|
||||||
"expected le__Str in workspace, found: {names:#?}"
|
"expected le__Str in workspace, found: {names:#?}"
|
||||||
@@ -65,7 +65,7 @@ fn le_at_str_produces_mono_symbol() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gt_at_bool_produces_mono_symbol() {
|
fn gt_at_bool_produces_mono_symbol() {
|
||||||
let names = mono_symbol_names("gt_at_bool_smoke.ail.json");
|
let names = mono_symbol_names("gt_at_bool_smoke.ail");
|
||||||
assert!(
|
assert!(
|
||||||
names.iter().any(|n| n == "gt__Bool"),
|
names.iter().any(|n| n == "gt__Bool"),
|
||||||
"expected gt__Bool in workspace, found: {names:#?}"
|
"expected gt__Bool in workspace, found: {names:#?}"
|
||||||
@@ -74,7 +74,7 @@ fn gt_at_bool_produces_mono_symbol() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ge_at_int_produces_mono_symbol() {
|
fn ge_at_int_produces_mono_symbol() {
|
||||||
let names = mono_symbol_names("ge_at_int_smoke.ail.json");
|
let names = mono_symbol_names("ge_at_int_smoke.ail");
|
||||||
assert!(
|
assert!(
|
||||||
names.iter().any(|n| n == "ge__Int"),
|
names.iter().any(|n| n == "ge__Int"),
|
||||||
"expected ge__Int in workspace, found: {names:#?}"
|
"expected ge__Int in workspace, found: {names:#?}"
|
||||||
|
|||||||
@@ -17,14 +17,14 @@
|
|||||||
//! function values inside `InstanceMethod.body`).
|
//! function values inside `InstanceMethod.body`).
|
||||||
|
|
||||||
use ailang_core::ast::{Def, Term};
|
use ailang_core::ast::{Def, Term};
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn fixture_path() -> PathBuf {
|
fn fixture_path() -> PathBuf {
|
||||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
d.pop();
|
d.pop();
|
||||||
d.pop();
|
d.pop();
|
||||||
d.join("examples").join("show_print_smoke.ail.json")
|
d.join("examples").join("show_print_smoke.ail")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -45,6 +45,23 @@ fn list_json_fixtures() -> Vec<PathBuf> {
|
|||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_ail_fixtures() -> Vec<PathBuf> {
|
||||||
|
let dir = examples_dir();
|
||||||
|
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
|
||||||
|
.unwrap_or_else(|e| panic!("read_dir({}): {e}", dir.display()))
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.map(|e| e.path())
|
||||||
|
.filter(|p| {
|
||||||
|
p.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.map(|n| n.ends_with(".ail"))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
paths.sort();
|
||||||
|
paths
|
||||||
|
}
|
||||||
|
|
||||||
fn strip_trailing_newlines(mut v: Vec<u8>) -> Vec<u8> {
|
fn strip_trailing_newlines(mut v: Vec<u8>) -> Vec<u8> {
|
||||||
while v.last() == Some(&b'\n') {
|
while v.last() == Some(&b'\n') {
|
||||||
v.pop();
|
v.pop();
|
||||||
@@ -144,3 +161,110 @@ fn cli_render_then_parse_preserves_canonical_bytes_on_every_fixture() {
|
|||||||
}
|
}
|
||||||
eprintln!("CLI roundtrip ok for {passed} fixtures");
|
eprintln!("CLI roundtrip ok for {passed} fixtures");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CLI-pipeline idempotency (post-form-a-default-authoring §C3): for
|
||||||
|
/// every `.ail` fixture, the user-facing pipeline
|
||||||
|
/// `ail parse <ail> | ail render | ail parse` is byte-identical
|
||||||
|
/// to direct `ail parse <ail>`. Pins drift internal tests cannot see.
|
||||||
|
#[test]
|
||||||
|
fn cli_parse_then_render_then_parse_is_idempotent() {
|
||||||
|
let fixtures = list_ail_fixtures();
|
||||||
|
assert!(
|
||||||
|
!fixtures.is_empty(),
|
||||||
|
"no .ail fixtures found under {}",
|
||||||
|
examples_dir().display()
|
||||||
|
);
|
||||||
|
|
||||||
|
let tmpdir = tempfile::TempDir::new().expect("create tempdir");
|
||||||
|
let mut checked = 0usize;
|
||||||
|
let mut failures = Vec::<String>::new();
|
||||||
|
|
||||||
|
for ail_path in &fixtures {
|
||||||
|
// Direct parse → JSON tempfile.
|
||||||
|
let json1 = tmpdir.path().join(format!("v1_{checked}.ail.json"));
|
||||||
|
let parse1 = Command::new(ail_bin())
|
||||||
|
.args([
|
||||||
|
"parse",
|
||||||
|
ail_path.to_str().unwrap(),
|
||||||
|
"-o",
|
||||||
|
json1.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.expect("spawn ail parse #1");
|
||||||
|
if !parse1.status.success() {
|
||||||
|
failures.push(format!(
|
||||||
|
"{}: first ail parse exit={:?}\nstderr: {}",
|
||||||
|
ail_path.display(),
|
||||||
|
parse1.status.code(),
|
||||||
|
String::from_utf8_lossy(&parse1.stderr),
|
||||||
|
));
|
||||||
|
checked += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render that JSON → .ail tempfile.
|
||||||
|
let ail_round = tmpdir.path().join(format!("round_{checked}.ail"));
|
||||||
|
let rendered = Command::new(ail_bin())
|
||||||
|
.args(["render", json1.to_str().unwrap()])
|
||||||
|
.output()
|
||||||
|
.expect("spawn ail render");
|
||||||
|
if !rendered.status.success() {
|
||||||
|
failures.push(format!(
|
||||||
|
"{}: ail render exit={:?}\nstderr: {}",
|
||||||
|
ail_path.display(),
|
||||||
|
rendered.status.code(),
|
||||||
|
String::from_utf8_lossy(&rendered.stderr),
|
||||||
|
));
|
||||||
|
checked += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::fs::write(&ail_round, &rendered.stdout)
|
||||||
|
.expect("write ail_round tempfile");
|
||||||
|
|
||||||
|
// Re-parse → second JSON tempfile.
|
||||||
|
let json2 = tmpdir.path().join(format!("v2_{checked}.ail.json"));
|
||||||
|
let parse2 = Command::new(ail_bin())
|
||||||
|
.args([
|
||||||
|
"parse",
|
||||||
|
ail_round.to_str().unwrap(),
|
||||||
|
"-o",
|
||||||
|
json2.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.expect("spawn ail parse #2");
|
||||||
|
if !parse2.status.success() {
|
||||||
|
failures.push(format!(
|
||||||
|
"{}: second ail parse exit={:?}\nstderr: {}",
|
||||||
|
ail_path.display(),
|
||||||
|
parse2.status.code(),
|
||||||
|
String::from_utf8_lossy(&parse2.stderr),
|
||||||
|
));
|
||||||
|
checked += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert the two canonical JSONs are byte-equal.
|
||||||
|
let b1 = std::fs::read(&json1).expect("read json1");
|
||||||
|
let b2 = std::fs::read(&json2).expect("read json2");
|
||||||
|
if b1 != b2 {
|
||||||
|
let s1 = String::from_utf8_lossy(&b1).into_owned();
|
||||||
|
let s2 = String::from_utf8_lossy(&b2).into_owned();
|
||||||
|
failures.push(format!(
|
||||||
|
"{}: CLI parse|render|parse non-idempotent.\n v1: {s1}\n v2: {s2}",
|
||||||
|
ail_path.display(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
checked += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !failures.is_empty() {
|
||||||
|
panic!(
|
||||||
|
"CLI parse|render|parse non-idempotent on {} of {} fixtures:\n{}",
|
||||||
|
failures.len(),
|
||||||
|
checked,
|
||||||
|
failures.join("\n\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert!(checked > 0, "no .ail fixtures found");
|
||||||
|
eprintln!("CLI parse|render|parse idempotent for {checked} fixtures");
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! post-mono module.
|
//! post-mono module.
|
||||||
|
|
||||||
use ailang_core::ast::Def;
|
use ailang_core::ast::Def;
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use ailang_check::{check_workspace, monomorphise_workspace};
|
use ailang_check::{check_workspace, monomorphise_workspace};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ fn fixture_path() -> PathBuf {
|
|||||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
d.pop();
|
d.pop();
|
||||||
d.pop();
|
d.pop();
|
||||||
d.join("examples").join("show_mono_pin_smoke.ail.json")
|
d.join("examples").join("show_mono_pin_smoke.ail")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//! and how to declare their own instance for a user type.
|
//! and how to declare their own instance for a user type.
|
||||||
|
|
||||||
use ailang_check::check_workspace;
|
use ailang_check::check_workspace;
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn fixture(name: &str) -> PathBuf {
|
fn fixture(name: &str) -> PathBuf {
|
||||||
@@ -18,7 +18,7 @@ fn fixture(name: &str) -> PathBuf {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn print_on_fn_type_fires_show_aware_no_instance() {
|
fn print_on_fn_type_fires_show_aware_no_instance() {
|
||||||
let ws = load_workspace(&fixture("show_no_instance.ail.json")).expect("load");
|
let ws = load_workspace(&fixture("show_no_instance.ail")).expect("load");
|
||||||
let diags = check_workspace(&ws);
|
let diags = check_workspace(&ws);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ fn build_and_run(fixture: &str) -> String {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn print_primitives_smoke_runs_end_to_end() {
|
fn print_primitives_smoke_runs_end_to_end() {
|
||||||
let stdout = build_and_run("show_print_smoke.ail.json");
|
let stdout = build_and_run("show_print_smoke.ail");
|
||||||
// print 42 → "42\n" (int_to_str + puts)
|
// print 42 → "42\n" (int_to_str + puts)
|
||||||
// print true → "true\n" (bool_to_str + puts)
|
// print true → "true\n" (bool_to_str + puts)
|
||||||
// print "hello" → "hello\n" (str_clone + puts)
|
// print "hello" → "hello\n" (str_clone + puts)
|
||||||
@@ -67,7 +67,7 @@ fn print_primitives_smoke_runs_end_to_end() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn print_user_adt_runs_end_to_end() {
|
fn print_user_adt_runs_end_to_end() {
|
||||||
let stdout = build_and_run("show_user_adt.ail.json");
|
let stdout = build_and_run("show_user_adt.ail");
|
||||||
// print (MkIntBox 7) → "7\n" (via user instance prelude.Show
|
// print (MkIntBox 7) → "7\n" (via user instance prelude.Show
|
||||||
// IntBox that unwraps via match + int_to_str).
|
// IntBox that unwraps via match + int_to_str).
|
||||||
assert_eq!(stdout, "7", "got: {stdout:?}");
|
assert_eq!(stdout, "7", "got: {stdout:?}");
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ fn examples_dir() -> PathBuf {
|
|||||||
/// the call site.
|
/// the call site.
|
||||||
#[test]
|
#[test]
|
||||||
fn class_method_is_in_module_globals() {
|
fn class_method_is_in_module_globals() {
|
||||||
let entry = examples_dir().join("test_22b2_class_method_lookup.ail.json");
|
let entry = examples_dir().join("test_22b2_class_method_lookup.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace must load");
|
.expect("workspace must load");
|
||||||
let globals = ailang_check::build_module_globals(&ws)
|
let globals = ailang_check::build_module_globals(&ws)
|
||||||
.expect("globals build");
|
.expect("globals build");
|
||||||
@@ -59,8 +59,8 @@ fn class_method_is_in_module_globals() {
|
|||||||
/// "Str"` with empty effects.
|
/// "Str"` with empty effects.
|
||||||
#[test]
|
#[test]
|
||||||
fn class_method_entry_carries_full_metadata() {
|
fn class_method_entry_carries_full_metadata() {
|
||||||
let entry = examples_dir().join("test_22b2_class_method_lookup.ail.json");
|
let entry = examples_dir().join("test_22b2_class_method_lookup.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace must load");
|
.expect("workspace must load");
|
||||||
let globals = ailang_check::build_module_globals(&ws)
|
let globals = ailang_check::build_module_globals(&ws)
|
||||||
.expect("globals build");
|
.expect("globals build");
|
||||||
@@ -144,8 +144,8 @@ fn class_method_entry_carries_full_metadata() {
|
|||||||
/// which dictionary to thread.
|
/// which dictionary to thread.
|
||||||
#[test]
|
#[test]
|
||||||
fn fn_calling_class_method_without_declared_constraint_fires_missing_constraint() {
|
fn fn_calling_class_method_without_declared_constraint_fires_missing_constraint() {
|
||||||
let entry = examples_dir().join("test_22b2_missing_constraint.ail.json");
|
let entry = examples_dir().join("test_22b2_missing_constraint.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diagnostics = ailang_check::check_workspace(&ws);
|
let diagnostics = ailang_check::check_workspace(&ws);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -164,8 +164,8 @@ fn fn_calling_class_method_without_declared_constraint_fires_missing_constraint(
|
|||||||
/// well-formed inside the body.
|
/// well-formed inside the body.
|
||||||
#[test]
|
#[test]
|
||||||
fn fn_with_correct_constraint_typechecks_green() {
|
fn fn_with_correct_constraint_typechecks_green() {
|
||||||
let entry = examples_dir().join("test_22b2_constraint_declared.ail.json");
|
let entry = examples_dir().join("test_22b2_constraint_declared.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diagnostics = ailang_check::check_workspace(&ws);
|
let diagnostics = ailang_check::check_workspace(&ws);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -186,8 +186,8 @@ fn fn_with_correct_constraint_typechecks_green() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn fn_calling_superclass_method_via_subclass_constraint_typechecks_green() {
|
fn fn_calling_superclass_method_via_subclass_constraint_typechecks_green() {
|
||||||
let entry = examples_dir()
|
let entry = examples_dir()
|
||||||
.join("test_22b2_constraint_declared_via_superclass.ail.json");
|
.join("test_22b2_constraint_declared_via_superclass.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diagnostics = ailang_check::check_workspace(&ws);
|
let diagnostics = ailang_check::check_workspace(&ws);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -207,8 +207,8 @@ fn fn_calling_superclass_method_via_subclass_constraint_typechecks_green() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn fn_with_superclass_constraint_calling_subclass_method_fires_missing_constraint() {
|
fn fn_with_superclass_constraint_calling_subclass_method_fires_missing_constraint() {
|
||||||
let entry = examples_dir()
|
let entry = examples_dir()
|
||||||
.join("test_22b2_missing_constraint_subclass.ail.json");
|
.join("test_22b2_missing_constraint_subclass.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diagnostics = ailang_check::check_workspace(&ws);
|
let diagnostics = ailang_check::check_workspace(&ws);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -230,8 +230,8 @@ fn fn_with_superclass_constraint_calling_subclass_method_fires_missing_constrain
|
|||||||
/// the workspace.
|
/// the workspace.
|
||||||
#[test]
|
#[test]
|
||||||
fn fn_calling_class_method_at_type_without_instance_fires_no_instance() {
|
fn fn_calling_class_method_at_type_without_instance_fires_no_instance() {
|
||||||
let entry = examples_dir().join("test_22b2_no_instance.ail.json");
|
let entry = examples_dir().join("test_22b2_no_instance.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diagnostics = ailang_check::check_workspace(&ws);
|
let diagnostics = ailang_check::check_workspace(&ws);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -253,8 +253,8 @@ fn fn_calling_class_method_at_type_without_instance_fires_no_instance() {
|
|||||||
/// `tshow 42` with `instance Show Int` declared in the same workspace.
|
/// `tshow 42` with `instance Show Int` declared in the same workspace.
|
||||||
#[test]
|
#[test]
|
||||||
fn fn_calling_class_method_at_type_with_instance_typechecks_green() {
|
fn fn_calling_class_method_at_type_with_instance_typechecks_green() {
|
||||||
let entry = examples_dir().join("test_22b2_instance_present.ail.json");
|
let entry = examples_dir().join("test_22b2_instance_present.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diagnostics = ailang_check::check_workspace(&ws);
|
let diagnostics = ailang_check::check_workspace(&ws);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -277,8 +277,8 @@ fn fn_calling_class_method_at_type_with_instance_typechecks_green() {
|
|||||||
/// `class TShow a`; B = `test_22b2_xmod_no_instance` calls `tshow 42`.
|
/// `class TShow a`; B = `test_22b2_xmod_no_instance` calls `tshow 42`.
|
||||||
#[test]
|
#[test]
|
||||||
fn cross_module_class_method_resolves_and_fires_no_instance() {
|
fn cross_module_class_method_resolves_and_fires_no_instance() {
|
||||||
let entry = examples_dir().join("test_22b2_xmod_no_instance.ail.json");
|
let entry = examples_dir().join("test_22b2_xmod_no_instance.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diagnostics = ailang_check::check_workspace(&ws);
|
let diagnostics = ailang_check::check_workspace(&ws);
|
||||||
let codes: Vec<&str> = diagnostics.iter().map(|d| d.code.as_str()).collect();
|
let codes: Vec<&str> = diagnostics.iter().map(|d| d.code.as_str()).collect();
|
||||||
@@ -307,8 +307,8 @@ fn cross_module_class_method_resolves_and_fires_no_instance() {
|
|||||||
/// declares `forall a. (a) -> Str` and calls `tshow x`.
|
/// declares `forall a. (a) -> Str` and calls `tshow x`.
|
||||||
#[test]
|
#[test]
|
||||||
fn cross_module_polymorphic_fn_without_constraint_fires_missing_constraint() {
|
fn cross_module_polymorphic_fn_without_constraint_fires_missing_constraint() {
|
||||||
let entry = examples_dir().join("test_22b2_xmod_missing_constraint.ail.json");
|
let entry = examples_dir().join("test_22b2_xmod_missing_constraint.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diagnostics = ailang_check::check_workspace(&ws);
|
let diagnostics = ailang_check::check_workspace(&ws);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -329,8 +329,8 @@ fn cross_module_polymorphic_fn_without_constraint_fires_missing_constraint() {
|
|||||||
/// `test_22b2_xmod_classmod`; B = `test_22b2_xmod_instance_present`.
|
/// `test_22b2_xmod_classmod`; B = `test_22b2_xmod_instance_present`.
|
||||||
#[test]
|
#[test]
|
||||||
fn cross_module_instance_satisfies_concrete_call() {
|
fn cross_module_instance_satisfies_concrete_call() {
|
||||||
let entry = examples_dir().join("test_22b2_xmod_instance_present.ail.json");
|
let entry = examples_dir().join("test_22b2_xmod_instance_present.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diagnostics = ailang_check::check_workspace(&ws);
|
let diagnostics = ailang_check::check_workspace(&ws);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -352,8 +352,8 @@ fn cross_module_instance_satisfies_concrete_call() {
|
|||||||
/// both ill-formed in the same way; both must surface.
|
/// both ill-formed in the same way; both must surface.
|
||||||
#[test]
|
#[test]
|
||||||
fn two_fns_each_missing_constraint_produce_two_diagnostics() {
|
fn two_fns_each_missing_constraint_produce_two_diagnostics() {
|
||||||
let entry = examples_dir().join("test_22b2_two_fns_missing_constraint.ail.json");
|
let entry = examples_dir().join("test_22b2_two_fns_missing_constraint.ail");
|
||||||
let ws = ailang_core::workspace::load_workspace(&entry)
|
let ws = ailang_surface::load_workspace(&entry)
|
||||||
.expect("workspace loads");
|
.expect("workspace loads");
|
||||||
let diagnostics = ailang_check::check_workspace(&ws);
|
let diagnostics = ailang_check::check_workspace(&ws);
|
||||||
let mc: Vec<&ailang_check::Diagnostic> = diagnostics
|
let mc: Vec<&ailang_check::Diagnostic> = diagnostics
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ fn examples_dir() -> PathBuf {
|
|||||||
fn monomorphise_workspace_is_identity_on_class_free_workspace() {
|
fn monomorphise_workspace_is_identity_on_class_free_workspace() {
|
||||||
// Pick any class-free fixture; `examples/hello.ail.json` is
|
// Pick any class-free fixture; `examples/hello.ail.json` is
|
||||||
// the canonical class-free smoke test.
|
// the canonical class-free smoke test.
|
||||||
let entry = examples_dir().join("hello.ail.json");
|
let entry = examples_dir().join("hello.ail");
|
||||||
let ws = ailang_core::load_workspace(&entry).expect("load");
|
let ws = ailang_surface::load_workspace(&entry).expect("load");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ fn monomorphise_workspace_is_identity_on_class_free_workspace() {
|
|||||||
.expect("mono on class-free workspace");
|
.expect("mono on class-free workspace");
|
||||||
|
|
||||||
// Module set unchanged.
|
// Module set unchanged.
|
||||||
let before = ailang_core::load_workspace(&examples_dir().join("hello.ail.json"))
|
let before = ailang_surface::load_workspace(&examples_dir().join("hello.ail"))
|
||||||
.expect("re-load");
|
.expect("re-load");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ws.modules.keys().collect::<Vec<_>>(),
|
ws.modules.keys().collect::<Vec<_>>(),
|
||||||
@@ -63,8 +63,8 @@ fn monomorphise_workspace_is_identity_on_class_free_workspace() {
|
|||||||
/// (correctly) materialises into `tshow__Int`.
|
/// (correctly) materialises into `tshow__Int`.
|
||||||
#[test]
|
#[test]
|
||||||
fn monomorphise_workspace_is_identity_when_no_concrete_call_sites() {
|
fn monomorphise_workspace_is_identity_when_no_concrete_call_sites() {
|
||||||
let entry = examples_dir().join("test_22b3_no_call_sites.ail.json");
|
let entry = examples_dir().join("test_22b3_no_call_sites.ail");
|
||||||
let ws = ailang_core::load_workspace(&entry).expect("load");
|
let ws = ailang_surface::load_workspace(&entry).expect("load");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ fn monomorphise_workspace_is_identity_when_no_concrete_call_sites() {
|
|||||||
.expect("mono on class-present workspace");
|
.expect("mono on class-present workspace");
|
||||||
|
|
||||||
// Module set unchanged.
|
// Module set unchanged.
|
||||||
let before = ailang_core::load_workspace(&entry).expect("re-load");
|
let before = ailang_surface::load_workspace(&entry).expect("re-load");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ws_after.modules.keys().collect::<Vec<_>>(),
|
ws_after.modules.keys().collect::<Vec<_>>(),
|
||||||
before.modules.keys().collect::<Vec<_>>(),
|
before.modules.keys().collect::<Vec<_>>(),
|
||||||
@@ -120,8 +120,8 @@ fn mono_symbol_primitive_types_use_surface_form() {
|
|||||||
fn collect_mono_targets_single_concrete_call_site() {
|
fn collect_mono_targets_single_concrete_call_site() {
|
||||||
use ailang_check::mono::collect_mono_targets;
|
use ailang_check::mono::collect_mono_targets;
|
||||||
|
|
||||||
let entry = examples_dir().join("test_22b2_instance_present.ail.json");
|
let entry = examples_dir().join("test_22b2_instance_present.ail");
|
||||||
let ws = ailang_core::load_workspace(&entry).expect("load");
|
let ws = ailang_surface::load_workspace(&entry).expect("load");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
||||||
|
|
||||||
@@ -376,8 +376,8 @@ fn synthesise_mono_fn_zero_arg_method_uses_body_verbatim() {
|
|||||||
/// dedup contract — the pass must not emit one FnDef per call site.
|
/// dedup contract — the pass must not emit one FnDef per call site.
|
||||||
#[test]
|
#[test]
|
||||||
fn fixpoint_dedupes_repeated_calls_at_same_type() {
|
fn fixpoint_dedupes_repeated_calls_at_same_type() {
|
||||||
let entry = examples_dir().join("test_22b3_dup_call_sites.ail.json");
|
let entry = examples_dir().join("test_22b3_dup_call_sites.ail");
|
||||||
let ws = ailang_core::load_workspace(&entry).expect("load");
|
let ws = ailang_surface::load_workspace(&entry).expect("load");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
||||||
|
|
||||||
@@ -416,8 +416,8 @@ fn fixpoint_dedupes_repeated_calls_at_same_type() {
|
|||||||
/// body).
|
/// body).
|
||||||
#[test]
|
#[test]
|
||||||
fn fixpoint_handles_chained_class_method_calls() {
|
fn fixpoint_handles_chained_class_method_calls() {
|
||||||
let entry = examples_dir().join("test_22b3_chained_calls.ail.json");
|
let entry = examples_dir().join("test_22b3_chained_calls.ail");
|
||||||
let ws = ailang_core::load_workspace(&entry).expect("load");
|
let ws = ailang_surface::load_workspace(&entry).expect("load");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
||||||
|
|
||||||
@@ -480,8 +480,8 @@ fn fixpoint_handles_chained_class_method_calls() {
|
|||||||
fn rewrite_walker_skips_locally_shadowed_class_method() {
|
fn rewrite_walker_skips_locally_shadowed_class_method() {
|
||||||
use ailang_core::ast::{Def, Term};
|
use ailang_core::ast::{Def, Term};
|
||||||
|
|
||||||
let entry = examples_dir().join("test_22b3_shadow_class_method.ail.json");
|
let entry = examples_dir().join("test_22b3_shadow_class_method.ail");
|
||||||
let ws = ailang_core::load_workspace(&entry).expect("load");
|
let ws = ailang_surface::load_workspace(&entry).expect("load");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
// mq.3: this fixture intentionally shadows the class method `tshow`
|
// mq.3: this fixture intentionally shadows the class method `tshow`
|
||||||
// with a local `let show = "shadow"`, which fires the new
|
// with a local `let show = "shadow"`, which fires the new
|
||||||
@@ -570,8 +570,8 @@ fn rewrite_walker_skips_locally_shadowed_class_method() {
|
|||||||
/// placement contract: the synthesised FnDef lives in B, not A.
|
/// placement contract: the synthesised FnDef lives in B, not A.
|
||||||
#[test]
|
#[test]
|
||||||
fn rewrite_uses_qualified_name_for_cross_module_call_site() {
|
fn rewrite_uses_qualified_name_for_cross_module_call_site() {
|
||||||
let entry = examples_dir().join("test_22b2_xmod_instance_present.ail.json");
|
let entry = examples_dir().join("test_22b2_xmod_instance_present.ail");
|
||||||
let ws = ailang_core::load_workspace(&entry).expect("load");
|
let ws = ailang_surface::load_workspace(&entry).expect("load");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
||||||
|
|
||||||
@@ -624,8 +624,8 @@ fn rewrite_uses_qualified_name_for_cross_module_call_site() {
|
|||||||
/// be ambiguous between mono-pass bug and 22b.2 typecheck bug.
|
/// be ambiguous between mono-pass bug and 22b.2 typecheck bug.
|
||||||
#[test]
|
#[test]
|
||||||
fn synthetic_fixture_typechecks_clean() {
|
fn synthetic_fixture_typechecks_clean() {
|
||||||
let entry = examples_dir().join("test_22b3_mono_synthetic.ail.json");
|
let entry = examples_dir().join("test_22b3_mono_synthetic.ail");
|
||||||
let ws = ailang_core::load_workspace(&entry).expect("load");
|
let ws = ailang_surface::load_workspace(&entry).expect("load");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
||||||
}
|
}
|
||||||
@@ -641,7 +641,7 @@ fn synthetic_fixture_runs_and_prints_five() {
|
|||||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
let ail_bin = std::env::var("CARGO_BIN_EXE_ail")
|
let ail_bin = std::env::var("CARGO_BIN_EXE_ail")
|
||||||
.expect("CARGO_BIN_EXE_ail set by cargo test");
|
.expect("CARGO_BIN_EXE_ail set by cargo test");
|
||||||
let fixture = examples_dir().join("test_22b3_mono_synthetic.ail.json");
|
let fixture = examples_dir().join("test_22b3_mono_synthetic.ail");
|
||||||
|
|
||||||
let tmp = manifest_dir.join("target").join(format!(
|
let tmp = manifest_dir.join("target").join(format!(
|
||||||
"test_22b3_synthetic_{}", std::process::id()
|
"test_22b3_synthetic_{}", std::process::id()
|
||||||
@@ -674,8 +674,8 @@ fn synthetic_fixture_runs_and_prints_five() {
|
|||||||
/// of `tshow`.
|
/// of `tshow`.
|
||||||
#[test]
|
#[test]
|
||||||
fn rewrite_replaces_class_method_var_with_mono_symbol_same_module() {
|
fn rewrite_replaces_class_method_var_with_mono_symbol_same_module() {
|
||||||
let entry = examples_dir().join("test_22b2_instance_present.ail.json");
|
let entry = examples_dir().join("test_22b2_instance_present.ail");
|
||||||
let ws = ailang_core::load_workspace(&entry).expect("load");
|
let ws = ailang_surface::load_workspace(&entry).expect("load");
|
||||||
let diags = ailang_check::check_workspace(&ws);
|
let diags = ailang_check::check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
assert!(diags.is_empty(), "fixture must typecheck: {:?}", diags);
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,6 @@ fn build_and_run(example: &str) -> String {
|
|||||||
/// `lib.rs:2316`) fall through.
|
/// `lib.rs:2316`) fall through.
|
||||||
#[test]
|
#[test]
|
||||||
fn user_class_instance_over_user_adt_builds_and_runs() {
|
fn user_class_instance_over_user_adt_builds_and_runs() {
|
||||||
let stdout = build_and_run("test_22c_user_class_e2e.ail.json");
|
let stdout = build_and_run("test_22c_user_class_e2e.ail");
|
||||||
assert_eq!(stdout.trim(), "42");
|
assert_eq!(stdout.trim(), "42");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
use ailang_check::{build_check_env, build_module_globals, Env};
|
use ailang_check::{build_check_env, build_module_globals, Env};
|
||||||
use ailang_core::ast::Def;
|
use ailang_core::ast::Def;
|
||||||
use ailang_core::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ fn build_env_inline_pre_refactor(ws: &ailang_core::workspace::Workspace) -> Env
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_check_env_matches_inline_seeding() {
|
fn build_check_env_matches_inline_seeding() {
|
||||||
let entry = examples_dir().join("test_mono_imports_main.ail.json");
|
let entry = examples_dir().join("test_mono_imports_main.ail");
|
||||||
let ws = load_workspace(&entry).expect("load test_mono_imports_main");
|
let ws = load_workspace(&entry).expect("load test_mono_imports_main");
|
||||||
|
|
||||||
let env_helper = build_check_env(&ws);
|
let env_helper = build_check_env(&ws);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
//! (`build_check_env`) and not visible from `ailang-core`.
|
//! (`build_check_env`) and not visible from `ailang-core`.
|
||||||
|
|
||||||
use ailang_check::build_check_env;
|
use ailang_check::build_check_env;
|
||||||
use ailang_core::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
fn examples_dir() -> std::path::PathBuf {
|
fn examples_dir() -> std::path::PathBuf {
|
||||||
@@ -34,7 +34,7 @@ fn examples_dir() -> std::path::PathBuf {
|
|||||||
#[test]
|
#[test]
|
||||||
fn mq3_class_class_collision_loads_clean_and_populates_candidates() {
|
fn mq3_class_class_collision_loads_clean_and_populates_candidates() {
|
||||||
let entry = examples_dir()
|
let entry = examples_dir()
|
||||||
.join("test_22b2_method_name_collision_class_class.ail.json");
|
.join("test_22b2_method_name_collision_class_class.ail");
|
||||||
let ws = load_workspace(&entry)
|
let ws = load_workspace(&entry)
|
||||||
.expect("post-mq.3: workspace loads without collision diagnostic");
|
.expect("post-mq.3: workspace loads without collision diagnostic");
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ fn mq3_class_class_collision_loads_clean_and_populates_candidates() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn mq3_class_fn_collision_loads_clean() {
|
fn mq3_class_fn_collision_loads_clean() {
|
||||||
let entry = examples_dir()
|
let entry = examples_dir()
|
||||||
.join("test_22b2_method_name_collision_class_fn.ail.json");
|
.join("test_22b2_method_name_collision_class_fn.ail");
|
||||||
load_workspace(&entry)
|
load_workspace(&entry)
|
||||||
.expect("post-mq.3: workspace loads — class-vs-fn name overlap is no longer a load error");
|
.expect("post-mq.3: workspace loads — class-vs-fn name overlap is no longer a load error");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
//! Step-2 singleton (each method has exactly one candidate class globally).
|
//! Step-2 singleton (each method has exactly one candidate class globally).
|
||||||
|
|
||||||
use ailang_check::{build_check_env, check_workspace};
|
use ailang_check::{build_check_env, check_workspace};
|
||||||
use ailang_core::workspace::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn fixture_dir() -> PathBuf {
|
fn fixture_dir() -> PathBuf {
|
||||||
@@ -22,7 +22,7 @@ fn bare_show_routes_to_prelude_show_via_singleton() {
|
|||||||
// Asserts: env.method_to_candidate_classes["show"] == {"prelude.Show"}
|
// Asserts: env.method_to_candidate_classes["show"] == {"prelude.Show"}
|
||||||
// (a singleton), so Step-2 of resolve_method_dispatch resolves bare
|
// (a singleton), so Step-2 of resolve_method_dispatch resolves bare
|
||||||
// "show" without needing Steps 3-4.
|
// "show" without needing Steps 3-4.
|
||||||
let ws_path = fixture_dir().join("show_mono_pin_smoke.ail.json");
|
let ws_path = fixture_dir().join("show_mono_pin_smoke.ail");
|
||||||
let ws = load_workspace(&ws_path).expect("workspace loads");
|
let ws = load_workspace(&ws_path).expect("workspace loads");
|
||||||
let diags = check_workspace(&ws);
|
let diags = check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "typecheck diagnostics: {diags:#?}");
|
assert!(diags.is_empty(), "typecheck diagnostics: {diags:#?}");
|
||||||
@@ -49,7 +49,7 @@ fn bare_tshow_routes_to_user_tshow_via_singleton() {
|
|||||||
// (which now declares class TShow with method tshow).
|
// (which now declares class TShow with method tshow).
|
||||||
// Asserts: env.method_to_candidate_classes["tshow"] has exactly one
|
// Asserts: env.method_to_candidate_classes["tshow"] has exactly one
|
||||||
// candidate (the user TShow), workspace-globally.
|
// candidate (the user TShow), workspace-globally.
|
||||||
let ws_path = fixture_dir().join("test_22b1_dup_classmod.ail.json");
|
let ws_path = fixture_dir().join("test_22b1_dup_classmod.ail");
|
||||||
let ws = load_workspace(&ws_path).expect("workspace loads");
|
let ws = load_workspace(&ws_path).expect("workspace loads");
|
||||||
let diags = check_workspace(&ws);
|
let diags = check_workspace(&ws);
|
||||||
assert!(diags.is_empty(), "typecheck diagnostics: {diags:#?}");
|
assert!(diags.is_empty(), "typecheck diagnostics: {diags:#?}");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
//! runs in JSON mode against a workspace.
|
//! runs in JSON mode against a workspace.
|
||||||
|
|
||||||
use ailang_check::{check_workspace, Severity};
|
use ailang_check::{check_workspace, Severity};
|
||||||
use ailang_core::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
/// Iter 18c.2: assert that the linearity check's `suggested_rewrites`
|
/// Iter 18c.2: assert that the linearity check's `suggested_rewrites`
|
||||||
@@ -39,7 +39,7 @@ fn examples_dir() -> std::path::PathBuf {
|
|||||||
fn happy_path_resolves_qualified_import() {
|
fn happy_path_resolves_qualified_import() {
|
||||||
// ws_main imports ws_lib and calls `ws_lib.add` — fully typed.
|
// ws_main imports ws_lib and calls `ws_lib.add` — fully typed.
|
||||||
// Expected: no diagnostics.
|
// Expected: no diagnostics.
|
||||||
let entry = examples_dir().join("ws_main.ail.json");
|
let entry = examples_dir().join("ws_main.ail");
|
||||||
let ws = load_workspace(&entry).expect("load ws_main");
|
let ws = load_workspace(&entry).expect("load ws_main");
|
||||||
let diags = check_workspace(&ws);
|
let diags = check_workspace(&ws);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -52,7 +52,7 @@ fn happy_path_resolves_qualified_import() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn unknown_import_is_reported() {
|
fn unknown_import_is_reported() {
|
||||||
// ws_broken references `ws_lib.bogus` — module is there, def isn't.
|
// ws_broken references `ws_lib.bogus` — module is there, def isn't.
|
||||||
let entry = examples_dir().join("ws_broken.ail.json");
|
let entry = examples_dir().join("ws_broken.ail");
|
||||||
let ws = load_workspace(&entry).expect("load ws_broken");
|
let ws = load_workspace(&entry).expect("load ws_broken");
|
||||||
let diags = check_workspace(&ws);
|
let diags = check_workspace(&ws);
|
||||||
assert_eq!(diags.len(), 1, "got: {:?}", diags);
|
assert_eq!(diags.len(), 1, "got: {:?}", diags);
|
||||||
@@ -72,7 +72,7 @@ fn unknown_import_is_reported() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn unknown_module_prefix_is_reported() {
|
fn unknown_module_prefix_is_reported() {
|
||||||
// ws_unknown_module has no imports but references `nope.x`.
|
// ws_unknown_module has no imports but references `nope.x`.
|
||||||
let entry = examples_dir().join("ws_unknown_module.ail.json");
|
let entry = examples_dir().join("ws_unknown_module.ail");
|
||||||
let ws = load_workspace(&entry).expect("load ws_unknown_module");
|
let ws = load_workspace(&entry).expect("load ws_unknown_module");
|
||||||
let diags = check_workspace(&ws);
|
let diags = check_workspace(&ws);
|
||||||
assert_eq!(diags.len(), 1, "got: {:?}", diags);
|
assert_eq!(diags.len(), 1, "got: {:?}", diags);
|
||||||
@@ -674,7 +674,7 @@ fn reuse_as_shape_mismatch_is_reported_on_cons_to_nil() {
|
|||||||
/// are exactly the canonical accept shape.
|
/// are exactly the canonical accept shape.
|
||||||
#[test]
|
#[test]
|
||||||
fn borrow_own_demo_is_linearity_clean() {
|
fn borrow_own_demo_is_linearity_clean() {
|
||||||
let entry = examples_dir().join("borrow_own_demo.ail.json");
|
let entry = examples_dir().join("borrow_own_demo.ail");
|
||||||
let ws = load_workspace(&entry).expect("load borrow_own_demo");
|
let ws = load_workspace(&entry).expect("load borrow_own_demo");
|
||||||
let diags = check_workspace(&ws);
|
let diags = check_workspace(&ws);
|
||||||
let lin: Vec<&ailang_check::Diagnostic> = diags
|
let lin: Vec<&ailang_check::Diagnostic> = diags
|
||||||
|
|||||||
@@ -9,3 +9,6 @@ ailang-core.workspace = true
|
|||||||
ailang-check.workspace = true
|
ailang-check.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ailang-surface = { path = "../ailang-surface" }
|
||||||
|
|||||||
@@ -3708,95 +3708,13 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iter 23.2: integration with the auto-loaded prelude. Compiling
|
// `eq_int_call_produces_icmp_i64_mono_fn` +
|
||||||
/// a workspace that calls `eq` on an Int pair must produce a
|
// `eq_bool_call_produces_icmp_i1_mono_fn` +
|
||||||
/// mono-synthesised `@ail_prelude_eq__Int` whose body lowers to
|
// `eq_str_call_produces_ail_str_eq_call_mono_fn` relocated to
|
||||||
/// `icmp eq i64`. The test runs the full pipeline end-of-typecheck →
|
// `crates/ailang-codegen/tests/eq_primitives_pin.rs` in iter
|
||||||
/// monomorphise → codegen, then asserts substring shape on the IR.
|
// form-a.1 Task 5 (use `ailang_surface::load_workspace` on the
|
||||||
#[test]
|
// `eq_primitives_smoke.ail` Form-A fixture; the original `.ail.json`
|
||||||
fn eq_int_call_produces_icmp_i64_mono_fn() {
|
// fixture is deleted in T8).
|
||||||
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 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__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 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__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}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 23.3: `runtime/str.c::ail_str_compare` backs the prelude's
|
/// Iter 23.3: `runtime/str.c::ail_str_compare` backs the prelude's
|
||||||
/// `compare__Str` mono symbol (see `try_emit_primitive_instance_body`).
|
/// `compare__Str` mono symbol (see `try_emit_primitive_instance_body`).
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
//! Eq-primitive codegen pin tests, relocated from
|
||||||
|
//! `ailang-codegen/src/lib.rs` `#[cfg(test)] mod tests` to a `tests/*`
|
||||||
|
//! integration crate in iter form-a.1 Task 5. The relocation switches
|
||||||
|
//! workspace loading from `ailang_core::load_workspace` (the JSON-only
|
||||||
|
//! loader) to `ailang_surface::load_workspace` (the
|
||||||
|
//! extension-dispatching superset), so the post-iter `.ail` corpus is
|
||||||
|
//! loaded from Form A rather than the deleted `.ail.json` siblings.
|
||||||
|
|
||||||
|
use ailang_check::{check_workspace, monomorphise_workspace, Severity};
|
||||||
|
use ailang_codegen::lower_workspace;
|
||||||
|
use ailang_surface::load_workspace;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn eq_primitives_smoke_path() -> std::path::PathBuf {
|
||||||
|
let manifest = env!("CARGO_MANIFEST_DIR");
|
||||||
|
Path::new(manifest)
|
||||||
|
.parent().unwrap().parent().unwrap()
|
||||||
|
.join("examples").join("eq_primitives_smoke.ail")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lower_eq_primitives_smoke() -> String {
|
||||||
|
let entry = eq_primitives_smoke_path();
|
||||||
|
let ws = load_workspace(&entry).expect("load workspace");
|
||||||
|
let diags = check_workspace(&ws);
|
||||||
|
assert!(
|
||||||
|
diags.iter().all(|d| !matches!(d.severity, Severity::Error)),
|
||||||
|
"unexpected check_workspace errors: {diags:?}"
|
||||||
|
);
|
||||||
|
let ws = monomorphise_workspace(&ws).expect("monomorphise");
|
||||||
|
lower_workspace(&ws).expect("lower")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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`. Full pipeline: end-of-typecheck → monomorphise →
|
||||||
|
/// codegen, then substring-shape assertions on the IR.
|
||||||
|
#[test]
|
||||||
|
fn eq_int_call_produces_icmp_i64_mono_fn() {
|
||||||
|
let ir = lower_eq_primitives_smoke();
|
||||||
|
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() {
|
||||||
|
let ir = lower_eq_primitives_smoke();
|
||||||
|
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() {
|
||||||
|
let ir = lower_eq_primitives_smoke();
|
||||||
|
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}"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -13,3 +13,4 @@ indexmap.workspace = true
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
|
ailang-surface = { path = "../ailang-surface" }
|
||||||
|
|||||||
@@ -47,306 +47,11 @@ pub fn def_hash(def: &Def) -> String {
|
|||||||
hex.as_str()[..16].to_string()
|
hex.as_str()[..16].to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `#[cfg(test)] mod tests` block relocated to
|
||||||
|
// `crates/ailang-core/tests/hash_pin.rs` in iter form-a.1 Task 5. The
|
||||||
|
// integration-test crate has `ailang-surface` as a dev-dependency so
|
||||||
|
// the schema-stability pins can load `.ail` fixtures via
|
||||||
|
// `ailang_surface::load_module`, eliminating the production-source
|
||||||
|
// dependency on `.ail.json` fixture reads.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {}
|
||||||
use super::*;
|
|
||||||
use crate::ast::*;
|
|
||||||
|
|
||||||
fn sample_fn() -> Def {
|
|
||||||
Def::Fn(FnDef {
|
|
||||||
name: "add".into(),
|
|
||||||
ty: Type::Fn {
|
|
||||||
params: vec![Type::int(), Type::int()],
|
|
||||||
ret: Box::new(Type::int()),
|
|
||||||
effects: vec![],
|
|
||||||
param_modes: vec![],
|
|
||||||
ret_mode: ParamMode::Implicit,
|
|
||||||
},
|
|
||||||
params: vec!["a".into(), "b".into()],
|
|
||||||
body: Term::App {
|
|
||||||
callee: Box::new(Term::Var { name: "+".into() }),
|
|
||||||
args: vec![
|
|
||||||
Term::Var { name: "a".into() },
|
|
||||||
Term::Var { name: "b".into() },
|
|
||||||
],
|
|
||||||
tail: false,
|
|
||||||
},
|
|
||||||
suppress: vec![],
|
|
||||||
doc: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_is_stable() {
|
|
||||||
let h1 = def_hash(&sample_fn());
|
|
||||||
let h2 = def_hash(&sample_fn());
|
|
||||||
assert_eq!(h1, h2);
|
|
||||||
assert_eq!(h1.len(), 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_changes_with_content() {
|
|
||||||
let mut def = sample_fn();
|
|
||||||
let h1 = def_hash(&def);
|
|
||||||
if let Def::Fn(ref mut f) = def {
|
|
||||||
f.name = "mul".into();
|
|
||||||
}
|
|
||||||
let h2 = def_hash(&def);
|
|
||||||
assert_ne!(h1, h2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 13a regression: adding `vars` to TypeDef and `args` to
|
|
||||||
/// `Type::Con` must NOT change canonical-JSON hashes of any pre-13a
|
|
||||||
/// definition. Recorded hashes were captured from on-disk modules
|
|
||||||
/// before the schema extension; if this fires, a
|
|
||||||
/// `skip_serializing_if` is missing or wrong. We deserialise the
|
|
||||||
/// real example modules from disk to avoid drift between the test
|
|
||||||
/// and the source-of-truth JSON.
|
|
||||||
#[test]
|
|
||||||
fn iter13a_schema_extension_preserves_pre_13a_hashes() {
|
|
||||||
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
let examples = manifest_dir.join("../../examples");
|
|
||||||
|
|
||||||
let sum_src = std::fs::read(examples.join("sum.ail.json"))
|
|
||||||
.expect("examples/sum.ail.json present");
|
|
||||||
let sum_mod: crate::ast::Module = serde_json::from_slice(&sum_src).unwrap();
|
|
||||||
let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap();
|
|
||||||
assert_eq!(def_hash(sum_def), "db33f57cb329935e");
|
|
||||||
|
|
||||||
let list_src = std::fs::read(examples.join("list.ail.json"))
|
|
||||||
.expect("examples/list.ail.json present");
|
|
||||||
let list_mod: crate::ast::Module = serde_json::from_slice(&list_src).unwrap();
|
|
||||||
let int_list_def = list_mod.defs.iter().find(|d| d.name() == "IntList").unwrap();
|
|
||||||
assert_eq!(def_hash(int_list_def), "b082192bd0c99202");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 19b regression: adding `suppress` to [`crate::ast::FnDef`]
|
|
||||||
/// must NOT change canonical-JSON hashes of any pre-19b fn whose
|
|
||||||
/// `suppress` is empty. The `skip_serializing_if = "Vec::is_empty"`
|
|
||||||
/// predicate on the field is what enforces this; if it is wrong,
|
|
||||||
/// every existing fixture's hash drifts and `ail diff` /
|
|
||||||
/// `ail manifest` output breaks.
|
|
||||||
///
|
|
||||||
/// We construct two FnDefs that differ only in `suppress` (one
|
|
||||||
/// empty, one missing the field). They must hash bit-identically:
|
|
||||||
/// the canonical-JSON of both is the same byte sequence because
|
|
||||||
/// the empty Vec is elided.
|
|
||||||
#[test]
|
|
||||||
fn iter19b_empty_suppress_preserves_pre_19b_hashes() {
|
|
||||||
let with_empty_suppress = sample_fn();
|
|
||||||
// Mutate the bare sample to set suppress explicitly to a
|
|
||||||
// non-empty Vec, then mutate it back to empty: two distinct
|
|
||||||
// construction paths that should still hash identically.
|
|
||||||
let mut with_explicit_empty = sample_fn();
|
|
||||||
if let Def::Fn(ref mut f) = with_explicit_empty {
|
|
||||||
f.suppress = vec![];
|
|
||||||
}
|
|
||||||
assert_eq!(
|
|
||||||
def_hash(&with_empty_suppress),
|
|
||||||
def_hash(&with_explicit_empty),
|
|
||||||
"two FnDefs with empty suppress must hash identically"
|
|
||||||
);
|
|
||||||
|
|
||||||
// And: a FnDef with a *non-empty* suppress hashes
|
|
||||||
// *differently* (sanity check that the field is in fact in
|
|
||||||
// the canonical bytes when present).
|
|
||||||
let mut with_suppress = sample_fn();
|
|
||||||
if let Def::Fn(ref mut f) = with_suppress {
|
|
||||||
f.suppress = vec![crate::ast::Suppress {
|
|
||||||
code: "over-strict-mode".into(),
|
|
||||||
because: "test reason".into(),
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
assert_ne!(
|
|
||||||
def_hash(&with_empty_suppress),
|
|
||||||
def_hash(&with_suppress),
|
|
||||||
"non-empty suppress must produce a distinct hash"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 19b: an on-disk pre-19b fixture must still load cleanly
|
|
||||||
/// (no `suppress` field present in the JSON) and produce its
|
|
||||||
/// canonical-byte hash unchanged. The hash for `sum.sum` was
|
|
||||||
/// recorded by the 13a regression and must stay 16 hex chars
|
|
||||||
/// equal to `db33f57cb329935e` — this test re-asserts it after
|
|
||||||
/// the 19b schema bump.
|
|
||||||
#[test]
|
|
||||||
fn iter19b_schema_extension_preserves_pre_19b_hashes() {
|
|
||||||
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
let examples = manifest_dir.join("../../examples");
|
|
||||||
|
|
||||||
let sum_src = std::fs::read(examples.join("sum.ail.json"))
|
|
||||||
.expect("examples/sum.ail.json present");
|
|
||||||
let sum_mod: crate::ast::Module = serde_json::from_slice(&sum_src).unwrap();
|
|
||||||
let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap();
|
|
||||||
assert_eq!(def_hash(sum_def), "db33f57cb329935e");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 22b.1 regression: adding `Def::Class` and `Def::Instance`
|
|
||||||
/// must NOT change canonical-JSON hashes of any pre-22b def. The
|
|
||||||
/// new variants are alternatives, not field additions — pre-22b
|
|
||||||
/// fixtures simply do not produce a `Class` / `Instance` `Def`,
|
|
||||||
/// and the existing `Fn` / `Const` / `Type` arms are unchanged.
|
|
||||||
/// This re-asserts the same on-disk hashes the 13a / 19b
|
|
||||||
/// regressions pinned, after the 22b.1 schema bump.
|
|
||||||
#[test]
|
|
||||||
fn iter22b1_schema_extension_preserves_pre_22b_hashes() {
|
|
||||||
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
let examples = manifest_dir.join("../../examples");
|
|
||||||
|
|
||||||
let sum_src = std::fs::read(examples.join("sum.ail.json"))
|
|
||||||
.expect("examples/sum.ail.json present");
|
|
||||||
let sum_mod: crate::ast::Module = serde_json::from_slice(&sum_src).unwrap();
|
|
||||||
let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap();
|
|
||||||
assert_eq!(def_hash(sum_def), "db33f57cb329935e");
|
|
||||||
|
|
||||||
let list_src = std::fs::read(examples.join("list.ail.json"))
|
|
||||||
.expect("examples/list.ail.json present");
|
|
||||||
let list_mod: crate::ast::Module = serde_json::from_slice(&list_src).unwrap();
|
|
||||||
let int_list_def = list_mod
|
|
||||||
.defs
|
|
||||||
.iter()
|
|
||||||
.find(|d| d.name() == "IntList")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(def_hash(int_list_def), "b082192bd0c99202");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 22b.1: a [`crate::ast::ClassDef`] with no doc, no
|
|
||||||
/// superclass, and an empty methods list serialises to a stable
|
|
||||||
/// canonical form. Two construction paths (default-elided
|
|
||||||
/// optionals vs. JSON without those keys at all) hash
|
|
||||||
/// bit-identically. If FAIL: a `skip_serializing_if` is missing
|
|
||||||
/// on `superclass` or `doc`, or the parser produces different
|
|
||||||
/// canonical bytes from the same logical value.
|
|
||||||
#[test]
|
|
||||||
fn iter22b1_classdef_empty_optionals_hash_stable() {
|
|
||||||
let bare = Def::Class(crate::ast::ClassDef {
|
|
||||||
name: "Empty".into(),
|
|
||||||
param: "a".into(),
|
|
||||||
superclass: None,
|
|
||||||
methods: vec![],
|
|
||||||
doc: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let json = r#"{"kind":"class","name":"Empty","param":"a","methods":[]}"#;
|
|
||||||
let parsed: Def = serde_json::from_str(json).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
def_hash(&bare),
|
|
||||||
def_hash(&parsed),
|
|
||||||
"ClassDef with elided optionals must hash identically to construction with explicit None"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 22b.2: adding `constraints` to [`crate::ast::Type::Forall`]
|
|
||||||
/// must NOT alter canonical-JSON bytes of any pre-22b.2 polymorphic
|
|
||||||
/// type. The `#[serde(default, skip_serializing_if = "Vec::is_empty")]`
|
|
||||||
/// attribute on the field is what enforces this; if it is wrong,
|
|
||||||
/// every existing fixture's hash drifts and workspace dedup keys
|
|
||||||
/// based on the canonical bytes break.
|
|
||||||
///
|
|
||||||
/// Property: a `Type::Forall` constructed without specifying
|
|
||||||
/// `constraints` (i.e. empty vec) must serialise with no
|
|
||||||
/// `constraints` key in the canonical JSON.
|
|
||||||
#[test]
|
|
||||||
fn forall_without_constraints_hashes_bit_identical_to_pre_22b2() {
|
|
||||||
use crate::ast::Type;
|
|
||||||
let t = Type::Forall {
|
|
||||||
vars: vec!["a".into()],
|
|
||||||
constraints: vec![],
|
|
||||||
body: Box::new(Type::fn_implicit(
|
|
||||||
vec![Type::Var { name: "a".into() }],
|
|
||||||
Type::Var { name: "a".into() },
|
|
||||||
vec![],
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
let bytes = crate::canonical::to_bytes(&t);
|
|
||||||
let s = std::str::from_utf8(&bytes).unwrap();
|
|
||||||
assert!(
|
|
||||||
!s.contains("constraints"),
|
|
||||||
"Type::Forall serialised must omit `constraints` when empty; got: {s}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter ct.4 (canonical-type-names milestone close): pin the
|
|
||||||
/// canonical-form hashes of the two cross-module fixtures
|
|
||||||
/// migrated by `ail migrate-canonical-types` in ct.1. These
|
|
||||||
/// hashes are the post-migration state; pre-migration values
|
|
||||||
/// (when both fixtures carried bare cross-module Type::Con)
|
|
||||||
/// are no longer reproducible because the ct.1 validator
|
|
||||||
/// rejects that shape upstream.
|
|
||||||
#[test]
|
|
||||||
fn ct4_migrated_fixtures_have_canonical_form_hashes() {
|
|
||||||
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
let examples = manifest_dir.join("../../examples");
|
|
||||||
|
|
||||||
let ord_src = std::fs::read(examples.join("ordering_match.ail.json"))
|
|
||||||
.expect("examples/ordering_match.ail.json present");
|
|
||||||
let ord_mod: crate::ast::Module = serde_json::from_slice(&ord_src).unwrap();
|
|
||||||
let main_def = ord_mod.defs.iter().find(|d| d.name() == "main").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
def_hash(main_def),
|
|
||||||
"8d17235aa3d2e127",
|
|
||||||
"ordering_match::main canonical hash must match captured post-migration value"
|
|
||||||
);
|
|
||||||
|
|
||||||
let dup_a_src = std::fs::read(examples.join("test_22b1_dup_a.ail.json"))
|
|
||||||
.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.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-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]),
|
|
||||||
"392c247f07de6517",
|
|
||||||
"test_22b1_dup_a instance hash drifted; expected post-24.2 captured value"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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 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 TShow)");
|
|
||||||
assert_eq!(
|
|
||||||
def_hash(&dup_classmod_mod.defs[0]),
|
|
||||||
"b8bca96c2d09ed93",
|
|
||||||
"test_22b1_dup_classmod class TShow canonical hash; post-24.2 captured value"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter ct.4: re-assert that the canonical-form tightening
|
|
||||||
/// did NOT change hashes of intra-module-only fixtures. The
|
|
||||||
/// existing iter-13a / iter-19b / iter-22b.1 pin tests already
|
|
||||||
/// assert these; this one names the canonical-type-names
|
|
||||||
/// milestone explicitly so future archaeology finds the
|
|
||||||
/// connection.
|
|
||||||
#[test]
|
|
||||||
fn ct4_unmigrated_fixtures_remain_bit_identical() {
|
|
||||||
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
let examples = manifest_dir.join("../../examples");
|
|
||||||
|
|
||||||
let sum_src = std::fs::read(examples.join("sum.ail.json")).unwrap();
|
|
||||||
let sum_mod: crate::ast::Module = serde_json::from_slice(&sum_src).unwrap();
|
|
||||||
let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap();
|
|
||||||
assert_eq!(def_hash(sum_def), "db33f57cb329935e",
|
|
||||||
"sum.sum hash drifted across canonical-form tightening — unexpected");
|
|
||||||
|
|
||||||
let list_src = std::fs::read(examples.join("list.ail.json")).unwrap();
|
|
||||||
let list_mod: crate::ast::Module = serde_json::from_slice(&list_src).unwrap();
|
|
||||||
let int_list_def = list_mod.defs.iter().find(|d| d.name() == "IntList").unwrap();
|
|
||||||
assert_eq!(def_hash(int_list_def), "b082192bd0c99202",
|
|
||||||
"list.IntList hash drifted across canonical-form tightening — unexpected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1609,128 +1609,12 @@ mod tests {
|
|||||||
d
|
d
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// `loads_example_workspace_happy_path` + `loads_workspace_auto_injects_prelude`
|
||||||
fn loads_example_workspace_happy_path() {
|
// relocated to `crates/ailang-core/tests/workspace_pin.rs` in iter
|
||||||
// Uses the canonical example files under `examples/`. This
|
// form-a.1 Task 5 (use `ailang_surface::load_workspace` on `.ail`
|
||||||
// test documents that the loader relies on the committed
|
// fixtures; the original `.ail.json` fixtures are deleted in T8).
|
||||||
// workspace example.
|
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
||||||
let workspace_root =
|
|
||||||
Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
|
||||||
let entry = workspace_root.join("examples").join("ws_main.ail.json");
|
|
||||||
|
|
||||||
let ws = load_workspace(&entry).expect("load workspace");
|
// (See workspace_pin.rs relocation marker above.)
|
||||||
assert_eq!(ws.entry, "ws_main");
|
|
||||||
assert!(ws.modules.contains_key("ws_main"));
|
|
||||||
assert!(ws.modules.contains_key("ws_lib"));
|
|
||||||
// Iter 23.1: the loader auto-injects the `prelude` module,
|
|
||||||
// so the count is the user's two modules plus prelude.
|
|
||||||
assert_eq!(ws.modules.len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn loads_workspace_auto_injects_prelude() {
|
|
||||||
// Iter 23.1: the prelude module is implicitly part of every
|
|
||||||
// workspace, regardless of whether the user's modules import
|
|
||||||
// it. Loading any well-formed workspace must result in
|
|
||||||
// `ws.modules["prelude"]` being present with the Ordering
|
|
||||||
// type def.
|
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
||||||
let workspace_root =
|
|
||||||
Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
|
||||||
let entry = workspace_root.join("examples").join("ws_main.ail.json");
|
|
||||||
|
|
||||||
let ws = load_workspace(&entry).expect("load workspace");
|
|
||||||
assert!(
|
|
||||||
ws.modules.contains_key("prelude"),
|
|
||||||
"prelude module must be auto-injected; modules present: {:?}",
|
|
||||||
ws.modules.keys().collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
let prelude = &ws.modules["prelude"];
|
|
||||||
assert_eq!(prelude.name, "prelude");
|
|
||||||
assert!(
|
|
||||||
prelude.defs.iter().any(|d| matches!(
|
|
||||||
d,
|
|
||||||
crate::ast::Def::Type(t) if t.name == "Ordering"
|
|
||||||
)),
|
|
||||||
"prelude must contain Ordering type def"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Iter 23.2: prelude also ships the `Eq` class plus three
|
|
||||||
// primitive instances (Eq Int, Eq Bool, Eq Str).
|
|
||||||
assert!(
|
|
||||||
prelude.defs.iter().any(|d| matches!(
|
|
||||||
d,
|
|
||||||
crate::ast::Def::Class(c) if c.name == "Eq"
|
|
||||||
)),
|
|
||||||
"prelude must contain Eq class def"
|
|
||||||
);
|
|
||||||
let eq_instance_types: Vec<&str> = prelude
|
|
||||||
.defs
|
|
||||||
.iter()
|
|
||||||
.filter_map(|d| match d {
|
|
||||||
crate::ast::Def::Instance(i) if i.class == "Eq" => {
|
|
||||||
if let crate::ast::Type::Con { name, .. } = &i.type_ {
|
|
||||||
Some(name.as_str())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
assert!(
|
|
||||||
eq_instance_types.contains(&"Int"),
|
|
||||||
"prelude must contain `instance Eq Int`; saw Eq instances on: {eq_instance_types:?}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
eq_instance_types.contains(&"Bool"),
|
|
||||||
"prelude must contain `instance Eq Bool`; saw Eq instances on: {eq_instance_types:?}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
eq_instance_types.contains(&"Str"),
|
|
||||||
"prelude must contain `instance Eq Str`; saw Eq instances on: {eq_instance_types:?}"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Iter 23.3: prelude also ships the `Ord` class plus three
|
|
||||||
// primitive instances (Ord Int, Ord Bool, Ord Str). Ord
|
|
||||||
// extends Eq (Decision 11 single-superclass closure), so its
|
|
||||||
// shape is the same as Eq's instance set; the loader uses the
|
|
||||||
// superclass walk to verify the Eq instances exist.
|
|
||||||
assert!(
|
|
||||||
prelude.defs.iter().any(|d| matches!(
|
|
||||||
d,
|
|
||||||
crate::ast::Def::Class(c) if c.name == "Ord"
|
|
||||||
)),
|
|
||||||
"prelude must contain Ord class def"
|
|
||||||
);
|
|
||||||
let ord_instance_types: Vec<&str> = prelude
|
|
||||||
.defs
|
|
||||||
.iter()
|
|
||||||
.filter_map(|d| match d {
|
|
||||||
crate::ast::Def::Instance(i) if i.class == "Ord" => {
|
|
||||||
if let crate::ast::Type::Con { name, .. } = &i.type_ {
|
|
||||||
Some(name.as_str())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
assert!(
|
|
||||||
ord_instance_types.contains(&"Int"),
|
|
||||||
"prelude must contain `instance Ord Int`; saw: {ord_instance_types:?}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
ord_instance_types.contains(&"Bool"),
|
|
||||||
"prelude must contain `instance Ord Bool`; saw: {ord_instance_types:?}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
ord_instance_types.contains(&"Str"),
|
|
||||||
"prelude must contain `instance Ord Str`; saw: {ord_instance_types:?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn user_module_named_prelude_is_rejected() {
|
fn user_module_named_prelude_is_rejected() {
|
||||||
@@ -1793,25 +1677,8 @@ mod tests {
|
|||||||
/// auto-loaded prelude (iter 23.2) the registry is no longer
|
/// auto-loaded prelude (iter 23.2) the registry is no longer
|
||||||
/// strictly empty, so the invariant is now "no entries whose
|
/// strictly empty, so the invariant is now "no entries whose
|
||||||
/// `defining_module` is anything other than `prelude`".
|
/// `defining_module` is anything other than `prelude`".
|
||||||
#[test]
|
// `iter22b1_workspace_with_no_classes_has_empty_registry` relocated
|
||||||
fn iter22b1_workspace_with_no_classes_has_empty_registry() {
|
// to `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
||||||
let workspace_root =
|
|
||||||
Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
|
||||||
let entry = workspace_root.join("examples").join("sum.ail.json");
|
|
||||||
|
|
||||||
let ws = load_workspace(&entry).expect("sum.ail.json loads");
|
|
||||||
assert!(
|
|
||||||
ws.registry.entries.values().all(|e| e.defining_module == "prelude"),
|
|
||||||
"pre-22b fixture has no class/instance defs of its own; \
|
|
||||||
all registry entries must come from the auto-injected prelude. \
|
|
||||||
got non-prelude entries: {:?}",
|
|
||||||
ws.registry.entries.values()
|
|
||||||
.filter(|e| e.defining_module != "prelude")
|
|
||||||
.map(|e| &e.defining_module)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
fn examples_dir() -> PathBuf {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
@@ -1828,23 +1695,8 @@ mod tests {
|
|||||||
/// fixture itself. With the auto-loaded prelude (iter 23.2)
|
/// fixture itself. With the auto-loaded prelude (iter 23.2)
|
||||||
/// the registry also contains the prelude's own entries; the
|
/// the registry also contains the prelude's own entries; the
|
||||||
/// invariant is filtered to fixture-only entries.
|
/// invariant is filtered to fixture-only entries.
|
||||||
#[test]
|
// `iter22b1_instance_in_class_module_loads_clean` relocated to
|
||||||
fn iter22b1_instance_in_class_module_loads_clean() {
|
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||||
let entry = examples_dir().join("test_22b1_orphan_class.ail.json");
|
|
||||||
let ws = load_workspace(&entry).expect("coherent instance loads");
|
|
||||||
let fixture_entries: Vec<_> = ws.registry.entries.iter()
|
|
||||||
.filter(|(_, e)| e.defining_module != "prelude")
|
|
||||||
.collect();
|
|
||||||
assert_eq!(fixture_entries.len(), 1);
|
|
||||||
let (key, entry) = fixture_entries[0];
|
|
||||||
// mq.1: registry key is keyed by the qualified class name.
|
|
||||||
// 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, "TShow");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 22b.1: an instance declared in a module that is neither
|
/// Iter 22b.1: an instance declared in a module that is neither
|
||||||
/// the class's module nor the type's module fires `OrphanInstance`.
|
/// the class's module nor the type's module fires `OrphanInstance`.
|
||||||
@@ -1853,26 +1705,8 @@ mod tests {
|
|||||||
/// TShow Int` itself — but the entry is not the class's module
|
/// TShow Int` itself — but the entry is not the class's module
|
||||||
/// and `Int` is primitive, so neither leg of coherence is
|
/// and `Int` is primitive, so neither leg of coherence is
|
||||||
/// satisfied. (24.2: class renamed `Show` → `TShow`.)
|
/// satisfied. (24.2: class renamed `Show` → `TShow`.)
|
||||||
#[test]
|
// `iter22b1_orphan_instance_fires_diagnostic` relocated to
|
||||||
fn iter22b1_orphan_instance_fires_diagnostic() {
|
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||||
let entry = examples_dir().join("test_22b1_orphan_third.ail.json");
|
|
||||||
let err = load_workspace(&entry).expect_err("must fire orphan");
|
|
||||||
match err {
|
|
||||||
WorkspaceLoadError::OrphanInstance {
|
|
||||||
class,
|
|
||||||
type_repr,
|
|
||||||
defining_module,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
// mq.1: `class` carries the canonical form (qualified
|
|
||||||
// for cross-module class refs).
|
|
||||||
assert_eq!(class, "test_22b1_orphan_third_classmod.TShow");
|
|
||||||
assert_eq!(type_repr, "Int");
|
|
||||||
assert_eq!(defining_module, "test_22b1_orphan_third");
|
|
||||||
}
|
|
||||||
other => panic!("expected OrphanInstance, got {other:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 22b.1 / mq.1: two instances of the same `(class, type)`
|
/// Iter 22b.1 / mq.1: two instances of the same `(class, type)`
|
||||||
/// pair collide on the registry's uniqueness check.
|
/// pair collide on the registry's uniqueness check.
|
||||||
@@ -1890,29 +1724,8 @@ mod tests {
|
|||||||
/// instances in `test_22b1_dup_same_module` — both class-leg
|
/// instances in `test_22b1_dup_same_module` — both class-leg
|
||||||
/// coherent, both collide on `(test_22b1_dup_same_module.TShow,
|
/// coherent, both collide on `(test_22b1_dup_same_module.TShow,
|
||||||
/// type_hash(Int))`. (24.2: class renamed `Show` → `TShow`.)
|
/// type_hash(Int))`. (24.2: class renamed `Show` → `TShow`.)
|
||||||
#[test]
|
// `iter22b1_duplicate_instance_fires_diagnostic` relocated to
|
||||||
fn iter22b1_duplicate_instance_fires_diagnostic() {
|
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||||
let entry = examples_dir().join("test_22b1_dup_same_module.ail.json");
|
|
||||||
let err = load_workspace(&entry).expect_err("must fire duplicate");
|
|
||||||
match err {
|
|
||||||
WorkspaceLoadError::DuplicateInstance {
|
|
||||||
class,
|
|
||||||
type_repr,
|
|
||||||
first_module,
|
|
||||||
second_module,
|
|
||||||
} => {
|
|
||||||
// `class` is the on-disk `inst.class` value; the
|
|
||||||
// fixture is intra-module so it stays bare.
|
|
||||||
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.
|
|
||||||
assert_eq!(first_module, "test_22b1_dup_same_module");
|
|
||||||
assert_eq!(second_module, "test_22b1_dup_same_module");
|
|
||||||
}
|
|
||||||
other => panic!("expected DuplicateInstance, got {other:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 22b.1: an instance that omits a required (non-default)
|
/// Iter 22b.1: an instance that omits a required (non-default)
|
||||||
/// method of its class fires `MissingMethod`. The fixture's
|
/// method of its class fires `MissingMethod`. The fixture's
|
||||||
@@ -1921,23 +1734,8 @@ mod tests {
|
|||||||
/// (Class is `TEq` (and method `tne`) rather than `Eq` / `ne` to
|
/// (Class is `TEq` (and method `tne`) rather than `Eq` / `ne` to
|
||||||
/// avoid colliding with the auto-loaded prelude's `class Eq` and
|
/// avoid colliding with the auto-loaded prelude's `class Eq` and
|
||||||
/// — since iter 23.5 — the prelude's polymorphic free fn `ne`.)
|
/// — since iter 23.5 — the prelude's polymorphic free fn `ne`.)
|
||||||
#[test]
|
// `iter22b1_missing_method_fires_diagnostic` relocated to
|
||||||
fn iter22b1_missing_method_fires_diagnostic() {
|
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||||
let entry = examples_dir().join("test_22b1_missing_method.ail.json");
|
|
||||||
let err = load_workspace(&entry).expect_err("must fire missing-method");
|
|
||||||
match err {
|
|
||||||
WorkspaceLoadError::MissingMethod {
|
|
||||||
class,
|
|
||||||
type_repr,
|
|
||||||
method,
|
|
||||||
} => {
|
|
||||||
assert_eq!(class, "TEq");
|
|
||||||
assert_eq!(type_repr, "Int");
|
|
||||||
assert_eq!(method, "teq");
|
|
||||||
}
|
|
||||||
other => panic!("expected MissingMethod, got {other:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn class_param_in_applied_position_fires_canonical_form_rejection() {
|
fn class_param_in_applied_position_fires_canonical_form_rejection() {
|
||||||
@@ -1992,24 +1790,8 @@ mod tests {
|
|||||||
/// at the instance site.
|
/// at the instance site.
|
||||||
/// (Class is `TEq` rather than `Eq` to avoid colliding with the
|
/// (Class is `TEq` rather than `Eq` to avoid colliding with the
|
||||||
/// auto-loaded prelude's `class Eq`.)
|
/// auto-loaded prelude's `class Eq`.)
|
||||||
#[test]
|
// `instance_overriding_nonexistent_method_fires` relocated to
|
||||||
fn instance_overriding_nonexistent_method_fires() {
|
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||||
let entry = std::path::PathBuf::from(
|
|
||||||
"../../examples/test_22b2_overriding_nonexistent.ail.json",
|
|
||||||
);
|
|
||||||
let err = load_workspace(&entry)
|
|
||||||
.expect_err("must fire overriding-non-existent-method");
|
|
||||||
match err {
|
|
||||||
WorkspaceLoadError::OverridingNonExistentMethod {
|
|
||||||
class, type_repr, method,
|
|
||||||
} => {
|
|
||||||
assert_eq!(class, "TEq");
|
|
||||||
assert_eq!(type_repr, "Int");
|
|
||||||
assert_eq!(method, "ne");
|
|
||||||
}
|
|
||||||
other => panic!("expected OverridingNonExistentMethod, got {other:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iter 22b.2: a class method's `Type::Forall` whose `constraints`
|
/// Iter 22b.2: a class method's `Type::Forall` whose `constraints`
|
||||||
/// reference a type variable that is neither bound by `Forall.vars`
|
/// reference a type variable that is neither bound by `Forall.vars`
|
||||||
@@ -2054,27 +1836,8 @@ mod tests {
|
|||||||
/// model requires `instance S T` whenever `instance C T` exists.
|
/// model requires `instance S T` whenever `instance C T` exists.
|
||||||
/// (Classes are `TEq` / `TOrd` rather than `Eq` / `Ord` to avoid
|
/// (Classes are `TEq` / `TOrd` rather than `Eq` / `Ord` to avoid
|
||||||
/// colliding with the auto-loaded prelude's `class Eq`.)
|
/// colliding with the auto-loaded prelude's `class Eq`.)
|
||||||
#[test]
|
// `instance_without_superclass_instance_fires` relocated to
|
||||||
fn instance_without_superclass_instance_fires() {
|
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||||
let entry = examples_dir().join("test_22b2_missing_superclass_instance.ail.json");
|
|
||||||
let err = load_workspace(&entry)
|
|
||||||
.expect_err("must fire missing-superclass-instance");
|
|
||||||
match err {
|
|
||||||
WorkspaceLoadError::MissingSuperclassInstance {
|
|
||||||
class, superclass, type_repr,
|
|
||||||
} => {
|
|
||||||
// mq.1: `class` is the qualified registry-key class
|
|
||||||
// name (the chain walker starts from the registry's
|
|
||||||
// qualified key); `superclass` is the canonical-form
|
|
||||||
// value from `SuperclassRef.class` (intra-module here
|
|
||||||
// so it stays bare).
|
|
||||||
assert_eq!(class, "test_22b2_missing_superclass_instance.TOrd");
|
|
||||||
assert_eq!(superclass, "TEq");
|
|
||||||
assert_eq!(type_repr, "Int");
|
|
||||||
}
|
|
||||||
other => panic!("expected MissingSuperclassInstance, got {other:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn module_with_type_def(name: &str, type_name: &str) -> Module {
|
fn module_with_type_def(name: &str, type_name: &str) -> Module {
|
||||||
serde_json::from_value(serde_json::json!({
|
serde_json::from_value(serde_json::json!({
|
||||||
@@ -2737,13 +2500,8 @@ mod tests {
|
|||||||
/// cross-module type-ref fixture (`ct1_validator_accepts_qualified_xmod_ref`
|
/// cross-module type-ref fixture (`ct1_validator_accepts_qualified_xmod_ref`
|
||||||
/// in-test sibling). Guards the full load → validator → registry
|
/// in-test sibling). Guards the full load → validator → registry
|
||||||
/// path on a real on-disk pair.
|
/// path on a real on-disk pair.
|
||||||
#[test]
|
// `mq1_xmod_constraint_class_fixture_loads` relocated to
|
||||||
fn mq1_xmod_constraint_class_fixture_loads() {
|
// `crates/ailang-core/tests/workspace_pin.rs` in iter form-a.1 Task 5.
|
||||||
let entry = examples_dir().join("mq1_xmod_constraint_class.ail.json");
|
|
||||||
let ws = load_workspace(&entry).expect("workspace must load with qualified Constraint.class");
|
|
||||||
assert!(ws.modules.contains_key("mq1_xmod_constraint_class"));
|
|
||||||
assert!(ws.modules.contains_key("mq1_xmod_constraint_class_dep"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ext-cli.1 Task 1: the new `load_workspace_with` injection point
|
/// ext-cli.1 Task 1: the new `load_workspace_with` injection point
|
||||||
/// invokes the caller-supplied loader exactly once for the entry
|
/// invokes the caller-supplied loader exactly once for the entry
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
//! Carve-out inventory check — §T4 of the form-a-default-authoring
|
||||||
|
//! spec. Asserts that exactly the eight named carve-out files exist
|
||||||
|
//! under `examples/*.ail.json` after milestone close. The list is
|
||||||
|
//! hardcoded; any change is a deliberate, brainstorm-level decision.
|
||||||
|
//!
|
||||||
|
//! Eight carve-outs at iter form-a.1 close:
|
||||||
|
//! - §C4 (a) subject-matter: 7 fixtures (canonical-form rejection / unbound / class-def rejection).
|
||||||
|
//! - §C4 (b) compile-time-embed: 1 fixture (prelude.ail.json).
|
||||||
|
//!
|
||||||
|
//! When the prelude-embed-refactor milestone retires §C4 (b), this
|
||||||
|
//! test's pass-count drops to seven and `EXPECTED` is re-published.
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
const EXPECTED: &[&str] = &[
|
||||||
|
// §C4 (a) — subject-matter
|
||||||
|
"broken_unbound.ail.json",
|
||||||
|
"test_22b2_invalid_superclass_param.ail.json",
|
||||||
|
"test_22b2_kind_mismatch.ail.json",
|
||||||
|
"test_22b2_unbound_constraint_var.ail.json",
|
||||||
|
"test_ct1_bad_qualifier.ail.json",
|
||||||
|
"test_ct1_bare_xmod_rejected.ail.json",
|
||||||
|
"test_ct1_qualified_class_rejected.ail.json",
|
||||||
|
// §C4 (b) — compile-time-embed
|
||||||
|
"prelude.ail.json",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn examples_dir() -> std::path::PathBuf {
|
||||||
|
let crate_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate_dir.parent().unwrap().parent().unwrap().join("examples")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "GREEN-ratified by Task 8 (.ail.json deletion); RED until then"]
|
||||||
|
fn examples_ail_json_inventory_matches_carve_outs() {
|
||||||
|
let expected: BTreeSet<&str> = EXPECTED.iter().copied().collect();
|
||||||
|
let mut actual: BTreeSet<String> = BTreeSet::new();
|
||||||
|
for entry in std::fs::read_dir(examples_dir()).expect("read examples/") {
|
||||||
|
let entry = entry.unwrap();
|
||||||
|
let name = entry.file_name().into_string().unwrap();
|
||||||
|
if name.ends_with(".ail.json") {
|
||||||
|
actual.insert(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let actual_refs: BTreeSet<&str> = actual.iter().map(String::as_str).collect();
|
||||||
|
assert_eq!(
|
||||||
|
actual_refs, expected,
|
||||||
|
"examples/*.ail.json inventory drifted from carve-out list",
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
use ailang_core::canonical;
|
use ailang_core::canonical;
|
||||||
use ailang_core::ast::Type;
|
use ailang_core::ast::Type;
|
||||||
use ailang_core::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
fn examples_dir() -> std::path::PathBuf {
|
fn examples_dir() -> std::path::PathBuf {
|
||||||
@@ -27,7 +27,7 @@ fn examples_dir() -> std::path::PathBuf {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_modules_with_same_bare_foo_both_register() {
|
fn two_modules_with_same_bare_foo_both_register() {
|
||||||
let entry = examples_dir().join("ctt2_collision_main.ail.json");
|
let entry = examples_dir().join("ctt2_collision_main.ail");
|
||||||
let ws = load_workspace(&entry)
|
let ws = load_workspace(&entry)
|
||||||
.expect("expected workspace load to succeed; both `type Foo` declarations live in distinct modules and must register under distinct canonical keys");
|
.expect("expected workspace load to succeed; both `type Foo` declarations live in distinct modules and must register under distinct canonical keys");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
//! Hash-pin integration tests, relocated from `ailang-core/src/hash.rs`
|
||||||
|
//! `#[cfg(test)] mod tests` to a `tests/*` integration crate in iter
|
||||||
|
//! form-a.1 Task 5. The relocation removes the production-source file's
|
||||||
|
//! ad-hoc `examples/<stem>.ail.json` reads in favour of loading the
|
||||||
|
//! `.ail` siblings via `ailang_surface::load_module`, mirroring the
|
||||||
|
//! Form-A authoring discipline of the form-a-default-authoring
|
||||||
|
//! milestone. Hash assertions are unchanged: post-form-a the canonical
|
||||||
|
//! JSON-AST is still the hashable representation, derived in-process
|
||||||
|
//! from `.ail` by the surface loader.
|
||||||
|
//!
|
||||||
|
//! The `examples/<stem>.ail.json` fixtures that backed the original
|
||||||
|
//! tests are deleted in Task 8; the `.ail` siblings rendered in Task 2
|
||||||
|
//! are the authored form going forward. The four schema-stability pins
|
||||||
|
//! (iter13a / iter19b / iter22b1 / ct4) plus the two structural pins
|
||||||
|
//! (hash_is_stable / hash_changes_with_content / classdef-empty-optionals
|
||||||
|
//! / forall-without-constraints) all keep their assertion shape.
|
||||||
|
|
||||||
|
use ailang_core::ast::*;
|
||||||
|
use ailang_core::canonical;
|
||||||
|
use ailang_core::hash::def_hash;
|
||||||
|
|
||||||
|
fn examples_dir() -> std::path::PathBuf {
|
||||||
|
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
manifest_dir.parent().unwrap().parent().unwrap().join("examples")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_fn() -> Def {
|
||||||
|
Def::Fn(FnDef {
|
||||||
|
name: "add".into(),
|
||||||
|
ty: Type::Fn {
|
||||||
|
params: vec![Type::int(), Type::int()],
|
||||||
|
ret: Box::new(Type::int()),
|
||||||
|
effects: vec![],
|
||||||
|
param_modes: vec![],
|
||||||
|
ret_mode: ParamMode::Implicit,
|
||||||
|
},
|
||||||
|
params: vec!["a".into(), "b".into()],
|
||||||
|
body: Term::App {
|
||||||
|
callee: Box::new(Term::Var { name: "+".into() }),
|
||||||
|
args: vec![
|
||||||
|
Term::Var { name: "a".into() },
|
||||||
|
Term::Var { name: "b".into() },
|
||||||
|
],
|
||||||
|
tail: false,
|
||||||
|
},
|
||||||
|
suppress: vec![],
|
||||||
|
doc: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_is_stable() {
|
||||||
|
let h1 = def_hash(&sample_fn());
|
||||||
|
let h2 = def_hash(&sample_fn());
|
||||||
|
assert_eq!(h1, h2);
|
||||||
|
assert_eq!(h1.len(), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_changes_with_content() {
|
||||||
|
let mut def = sample_fn();
|
||||||
|
let h1 = def_hash(&def);
|
||||||
|
if let Def::Fn(ref mut f) = def {
|
||||||
|
f.name = "mul".into();
|
||||||
|
}
|
||||||
|
let h2 = def_hash(&def);
|
||||||
|
assert_ne!(h1, h2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 13a regression: adding `vars` to TypeDef and `args` to
|
||||||
|
/// `Type::Con` must NOT change canonical-JSON hashes of any pre-13a
|
||||||
|
/// definition. Recorded hashes were captured from on-disk modules
|
||||||
|
/// before the schema extension; if this fires, a
|
||||||
|
/// `skip_serializing_if` is missing or wrong.
|
||||||
|
#[test]
|
||||||
|
fn iter13a_schema_extension_preserves_pre_13a_hashes() {
|
||||||
|
let examples = examples_dir();
|
||||||
|
|
||||||
|
let sum_mod = ailang_surface::load_module(&examples.join("sum.ail"))
|
||||||
|
.expect("examples/sum.ail loads");
|
||||||
|
let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap();
|
||||||
|
assert_eq!(def_hash(sum_def), "db33f57cb329935e");
|
||||||
|
|
||||||
|
let list_mod = ailang_surface::load_module(&examples.join("list.ail"))
|
||||||
|
.expect("examples/list.ail loads");
|
||||||
|
let int_list_def = list_mod.defs.iter().find(|d| d.name() == "IntList").unwrap();
|
||||||
|
assert_eq!(def_hash(int_list_def), "b082192bd0c99202");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 19b regression: adding `suppress` to FnDef must NOT change
|
||||||
|
/// canonical-JSON hashes of any pre-19b fn whose `suppress` is empty.
|
||||||
|
/// The `skip_serializing_if = "Vec::is_empty"` predicate on the field
|
||||||
|
/// is what enforces this; if it is wrong, every existing fixture's
|
||||||
|
/// hash drifts and `ail diff` / `ail manifest` output breaks.
|
||||||
|
#[test]
|
||||||
|
fn iter19b_empty_suppress_preserves_pre_19b_hashes() {
|
||||||
|
let with_empty_suppress = sample_fn();
|
||||||
|
// Mutate the bare sample to set suppress explicitly to a
|
||||||
|
// non-empty Vec, then mutate it back to empty: two distinct
|
||||||
|
// construction paths that should still hash identically.
|
||||||
|
let mut with_explicit_empty = sample_fn();
|
||||||
|
if let Def::Fn(ref mut f) = with_explicit_empty {
|
||||||
|
f.suppress = vec![];
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
def_hash(&with_empty_suppress),
|
||||||
|
def_hash(&with_explicit_empty),
|
||||||
|
"two FnDefs with empty suppress must hash identically"
|
||||||
|
);
|
||||||
|
|
||||||
|
// And: a FnDef with a *non-empty* suppress hashes differently.
|
||||||
|
let mut with_suppress = sample_fn();
|
||||||
|
if let Def::Fn(ref mut f) = with_suppress {
|
||||||
|
f.suppress = vec![Suppress {
|
||||||
|
code: "over-strict-mode".into(),
|
||||||
|
because: "test reason".into(),
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
assert_ne!(
|
||||||
|
def_hash(&with_empty_suppress),
|
||||||
|
def_hash(&with_suppress),
|
||||||
|
"non-empty suppress must produce a distinct hash"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 19b: an on-disk pre-19b fixture must still load cleanly
|
||||||
|
/// (no `suppress` field present in the JSON) and produce its
|
||||||
|
/// canonical-byte hash unchanged.
|
||||||
|
#[test]
|
||||||
|
fn iter19b_schema_extension_preserves_pre_19b_hashes() {
|
||||||
|
let examples = examples_dir();
|
||||||
|
|
||||||
|
let sum_mod = ailang_surface::load_module(&examples.join("sum.ail"))
|
||||||
|
.expect("examples/sum.ail loads");
|
||||||
|
let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap();
|
||||||
|
assert_eq!(def_hash(sum_def), "db33f57cb329935e");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 22b.1 regression: adding `Def::Class` and `Def::Instance` must
|
||||||
|
/// NOT change canonical-JSON hashes of any pre-22b def. The new variants
|
||||||
|
/// are alternatives, not field additions — pre-22b fixtures simply do
|
||||||
|
/// not produce a `Class` / `Instance` `Def`, and the existing arms are
|
||||||
|
/// unchanged.
|
||||||
|
#[test]
|
||||||
|
fn iter22b1_schema_extension_preserves_pre_22b_hashes() {
|
||||||
|
let examples = examples_dir();
|
||||||
|
|
||||||
|
let sum_mod = ailang_surface::load_module(&examples.join("sum.ail"))
|
||||||
|
.expect("examples/sum.ail loads");
|
||||||
|
let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap();
|
||||||
|
assert_eq!(def_hash(sum_def), "db33f57cb329935e");
|
||||||
|
|
||||||
|
let list_mod = ailang_surface::load_module(&examples.join("list.ail"))
|
||||||
|
.expect("examples/list.ail loads");
|
||||||
|
let int_list_def = list_mod
|
||||||
|
.defs
|
||||||
|
.iter()
|
||||||
|
.find(|d| d.name() == "IntList")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(def_hash(int_list_def), "b082192bd0c99202");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 22b.1: a ClassDef with no doc, no superclass, and an empty
|
||||||
|
/// methods list serialises to a stable canonical form. Two construction
|
||||||
|
/// paths (default-elided optionals vs. JSON without those keys at all)
|
||||||
|
/// hash bit-identically.
|
||||||
|
#[test]
|
||||||
|
fn iter22b1_classdef_empty_optionals_hash_stable() {
|
||||||
|
let bare = Def::Class(ClassDef {
|
||||||
|
name: "Empty".into(),
|
||||||
|
param: "a".into(),
|
||||||
|
superclass: None,
|
||||||
|
methods: vec![],
|
||||||
|
doc: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = r#"{"kind":"class","name":"Empty","param":"a","methods":[]}"#;
|
||||||
|
let parsed: Def = serde_json::from_str(json).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
def_hash(&bare),
|
||||||
|
def_hash(&parsed),
|
||||||
|
"ClassDef with elided optionals must hash identically to construction with explicit None"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 22b.2: adding `constraints` to `Type::Forall` must NOT alter
|
||||||
|
/// canonical-JSON bytes of any pre-22b.2 polymorphic type.
|
||||||
|
#[test]
|
||||||
|
fn forall_without_constraints_hashes_bit_identical_to_pre_22b2() {
|
||||||
|
let t = Type::Forall {
|
||||||
|
vars: vec!["a".into()],
|
||||||
|
constraints: vec![],
|
||||||
|
body: Box::new(Type::fn_implicit(
|
||||||
|
vec![Type::Var { name: "a".into() }],
|
||||||
|
Type::Var { name: "a".into() },
|
||||||
|
vec![],
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
let bytes = canonical::to_bytes(&t);
|
||||||
|
let s = std::str::from_utf8(&bytes).unwrap();
|
||||||
|
assert!(
|
||||||
|
!s.contains("constraints"),
|
||||||
|
"Type::Forall serialised must omit `constraints` when empty; got: {s}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter ct.4 (canonical-type-names milestone close): pin the
|
||||||
|
/// canonical-form hashes of the two cross-module fixtures migrated by
|
||||||
|
/// `ail migrate-canonical-types` in ct.1. These hashes are the
|
||||||
|
/// post-migration state.
|
||||||
|
#[test]
|
||||||
|
fn ct4_migrated_fixtures_have_canonical_form_hashes() {
|
||||||
|
let examples = examples_dir();
|
||||||
|
|
||||||
|
let ord_mod = ailang_surface::load_module(&examples.join("ordering_match.ail"))
|
||||||
|
.expect("examples/ordering_match.ail loads");
|
||||||
|
let main_def = ord_mod.defs.iter().find(|d| d.name() == "main").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
def_hash(main_def),
|
||||||
|
"8d17235aa3d2e127",
|
||||||
|
"ordering_match::main canonical hash must match captured post-migration value"
|
||||||
|
);
|
||||||
|
|
||||||
|
let dup_a_mod = ailang_surface::load_module(&examples.join("test_22b1_dup_a.ail"))
|
||||||
|
.expect("examples/test_22b1_dup_a.ail loads");
|
||||||
|
assert_eq!(dup_a_mod.defs.len(), 1, "test_22b1_dup_a expected to have exactly 1 def (the instance)");
|
||||||
|
assert_eq!(
|
||||||
|
def_hash(&dup_a_mod.defs[0]),
|
||||||
|
"392c247f07de6517",
|
||||||
|
"test_22b1_dup_a instance hash drifted; expected post-24.2 captured value"
|
||||||
|
);
|
||||||
|
|
||||||
|
let dup_classmod_mod = ailang_surface::load_module(&examples.join("test_22b1_dup_classmod.ail"))
|
||||||
|
.expect("examples/test_22b1_dup_classmod.ail loads");
|
||||||
|
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]),
|
||||||
|
"b8bca96c2d09ed93",
|
||||||
|
"test_22b1_dup_classmod class TShow canonical hash; post-24.2 captured value"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter ct.4: re-assert that the canonical-form tightening did NOT
|
||||||
|
/// change hashes of intra-module-only fixtures.
|
||||||
|
#[test]
|
||||||
|
fn ct4_unmigrated_fixtures_remain_bit_identical() {
|
||||||
|
let examples = examples_dir();
|
||||||
|
|
||||||
|
let sum_mod = ailang_surface::load_module(&examples.join("sum.ail"))
|
||||||
|
.expect("examples/sum.ail loads");
|
||||||
|
let sum_def = sum_mod.defs.iter().find(|d| d.name() == "sum").unwrap();
|
||||||
|
assert_eq!(def_hash(sum_def), "db33f57cb329935e",
|
||||||
|
"sum.sum hash drifted across canonical-form tightening — unexpected");
|
||||||
|
|
||||||
|
let list_mod = ailang_surface::load_module(&examples.join("list.ail"))
|
||||||
|
.expect("examples/list.ail loads");
|
||||||
|
let int_list_def = list_mod.defs.iter().find(|d| d.name() == "IntList").unwrap();
|
||||||
|
assert_eq!(def_hash(int_list_def), "b082192bd0c99202",
|
||||||
|
"list.IntList hash drifted across canonical-form tightening — unexpected");
|
||||||
|
}
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
//! Workspace-load pin tests, relocated from `ailang-core/src/workspace.rs`
|
||||||
|
//! `#[cfg(test)] mod tests` to a `tests/*` integration crate in iter
|
||||||
|
//! form-a.1 Task 5. The relocation switches each test from
|
||||||
|
//! `ailang_core::load_workspace` (the JSON-only loader) to
|
||||||
|
//! `ailang_surface::load_workspace` (the extension-dispatching superset),
|
||||||
|
//! so the post-iter `.ail` corpus is loaded from Form A rather than the
|
||||||
|
//! deleted `.ail.json` siblings.
|
||||||
|
//!
|
||||||
|
//! Carve-out tests (the seven §C4 (a) subject-matter rejections and the
|
||||||
|
//! prelude embed) stay in-place in `workspace.rs` because their
|
||||||
|
//! `.ail.json` fixtures are not migrated; this file covers the
|
||||||
|
//! non-carve-out cohort only.
|
||||||
|
|
||||||
|
use ailang_core::workspace::{Registry, RegistryEntry, WorkspaceLoadError};
|
||||||
|
use ailang_surface::load_workspace;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
fn examples_dir() -> PathBuf {
|
||||||
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
|
Path::new(manifest_dir).parent().unwrap().parent().unwrap().join("examples")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loads_example_workspace_happy_path() {
|
||||||
|
let entry = examples_dir().join("ws_main.ail");
|
||||||
|
let ws = load_workspace(&entry).expect("load workspace");
|
||||||
|
assert_eq!(ws.entry, "ws_main");
|
||||||
|
assert!(ws.modules.contains_key("ws_main"));
|
||||||
|
assert!(ws.modules.contains_key("ws_lib"));
|
||||||
|
// Iter 23.1: the loader auto-injects the `prelude` module,
|
||||||
|
// so the count is the user's two modules plus prelude.
|
||||||
|
assert_eq!(ws.modules.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loads_workspace_auto_injects_prelude() {
|
||||||
|
// Iter 23.1: the prelude module is implicitly part of every
|
||||||
|
// workspace, regardless of whether the user's modules import
|
||||||
|
// it. Loading any well-formed workspace must result in
|
||||||
|
// `ws.modules["prelude"]` being present with the Ordering
|
||||||
|
// type def.
|
||||||
|
let entry = examples_dir().join("ws_main.ail");
|
||||||
|
let ws = load_workspace(&entry).expect("load workspace");
|
||||||
|
assert!(
|
||||||
|
ws.modules.contains_key("prelude"),
|
||||||
|
"prelude module must be auto-injected; modules present: {:?}",
|
||||||
|
ws.modules.keys().collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
let prelude = &ws.modules["prelude"];
|
||||||
|
assert_eq!(prelude.name, "prelude");
|
||||||
|
assert!(
|
||||||
|
prelude.defs.iter().any(|d| matches!(
|
||||||
|
d,
|
||||||
|
ailang_core::ast::Def::Type(t) if t.name == "Ordering"
|
||||||
|
)),
|
||||||
|
"prelude must contain Ordering type def"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Iter 23.2: prelude also ships the `Eq` class plus three
|
||||||
|
// primitive instances (Eq Int, Eq Bool, Eq Str).
|
||||||
|
assert!(
|
||||||
|
prelude.defs.iter().any(|d| matches!(
|
||||||
|
d,
|
||||||
|
ailang_core::ast::Def::Class(c) if c.name == "Eq"
|
||||||
|
)),
|
||||||
|
"prelude must contain Eq class def"
|
||||||
|
);
|
||||||
|
let eq_instance_types: Vec<&str> = prelude
|
||||||
|
.defs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|d| match d {
|
||||||
|
ailang_core::ast::Def::Instance(i) if i.class == "Eq" => {
|
||||||
|
if let ailang_core::ast::Type::Con { name, .. } = &i.type_ {
|
||||||
|
Some(name.as_str())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
assert!(
|
||||||
|
eq_instance_types.contains(&"Int"),
|
||||||
|
"prelude must contain `instance Eq Int`; saw Eq instances on: {eq_instance_types:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
eq_instance_types.contains(&"Bool"),
|
||||||
|
"prelude must contain `instance Eq Bool`; saw Eq instances on: {eq_instance_types:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
eq_instance_types.contains(&"Str"),
|
||||||
|
"prelude must contain `instance Eq Str`; saw Eq instances on: {eq_instance_types:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Iter 23.3: prelude also ships the `Ord` class plus three
|
||||||
|
// primitive instances (Ord Int, Ord Bool, Ord Str).
|
||||||
|
assert!(
|
||||||
|
prelude.defs.iter().any(|d| matches!(
|
||||||
|
d,
|
||||||
|
ailang_core::ast::Def::Class(c) if c.name == "Ord"
|
||||||
|
)),
|
||||||
|
"prelude must contain Ord class def"
|
||||||
|
);
|
||||||
|
let ord_instance_types: Vec<&str> = prelude
|
||||||
|
.defs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|d| match d {
|
||||||
|
ailang_core::ast::Def::Instance(i) if i.class == "Ord" => {
|
||||||
|
if let ailang_core::ast::Type::Con { name, .. } = &i.type_ {
|
||||||
|
Some(name.as_str())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
assert!(
|
||||||
|
ord_instance_types.contains(&"Int"),
|
||||||
|
"prelude must contain `instance Ord Int`; saw: {ord_instance_types:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
ord_instance_types.contains(&"Bool"),
|
||||||
|
"prelude must contain `instance Ord Bool`; saw: {ord_instance_types:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
ord_instance_types.contains(&"Str"),
|
||||||
|
"prelude must contain `instance Ord Str`; saw: {ord_instance_types:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iter22b1_workspace_with_no_classes_has_empty_registry() {
|
||||||
|
let entry = examples_dir().join("sum.ail");
|
||||||
|
let ws = load_workspace(&entry).expect("sum.ail loads");
|
||||||
|
assert!(
|
||||||
|
ws.registry.entries.values().all(|e: &RegistryEntry| e.defining_module == "prelude"),
|
||||||
|
"pre-22b fixture has no class/instance defs of its own; \
|
||||||
|
all registry entries must come from the auto-injected prelude. \
|
||||||
|
got non-prelude entries: {:?}",
|
||||||
|
ws.registry.entries.values()
|
||||||
|
.filter(|e| e.defining_module != "prelude")
|
||||||
|
.map(|e| &e.defining_module)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
// Silence unused-import warning for `Registry`; the type is named
|
||||||
|
// in the test only via the field-access path above.
|
||||||
|
let _: Option<&Registry> = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iter22b1_instance_in_class_module_loads_clean() {
|
||||||
|
let entry = examples_dir().join("test_22b1_orphan_class.ail");
|
||||||
|
let ws = load_workspace(&entry).expect("coherent instance loads");
|
||||||
|
let fixture_entries: Vec<_> = ws.registry.entries.iter()
|
||||||
|
.filter(|(_, e)| e.defining_module != "prelude")
|
||||||
|
.collect();
|
||||||
|
assert_eq!(fixture_entries.len(), 1);
|
||||||
|
let (key, entry) = fixture_entries[0];
|
||||||
|
// mq.1: registry key is keyed by the qualified class name.
|
||||||
|
// 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, "TShow");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 22b.1: an instance declared in a module that is neither
|
||||||
|
/// the class's module nor the type's module fires `OrphanInstance`.
|
||||||
|
#[test]
|
||||||
|
fn iter22b1_orphan_instance_fires_diagnostic() {
|
||||||
|
let entry = examples_dir().join("test_22b1_orphan_third.ail");
|
||||||
|
let err = load_workspace(&entry).expect_err("must fire orphan");
|
||||||
|
match err {
|
||||||
|
WorkspaceLoadError::OrphanInstance {
|
||||||
|
class,
|
||||||
|
type_repr,
|
||||||
|
defining_module,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
assert_eq!(class, "test_22b1_orphan_third_classmod.TShow");
|
||||||
|
assert_eq!(type_repr, "Int");
|
||||||
|
assert_eq!(defining_module, "test_22b1_orphan_third");
|
||||||
|
}
|
||||||
|
other => panic!("expected OrphanInstance, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 22b.1 / mq.1: two instances of the same `(class, type)` pair
|
||||||
|
/// collide on the registry's uniqueness check. Post-mq.1 both
|
||||||
|
/// instances must live in the class's or the type's module (see
|
||||||
|
/// fixture).
|
||||||
|
#[test]
|
||||||
|
fn iter22b1_duplicate_instance_fires_diagnostic() {
|
||||||
|
let entry = examples_dir().join("test_22b1_dup_same_module.ail");
|
||||||
|
let err = load_workspace(&entry).expect_err("must fire duplicate");
|
||||||
|
match err {
|
||||||
|
WorkspaceLoadError::DuplicateInstance {
|
||||||
|
class,
|
||||||
|
type_repr,
|
||||||
|
first_module,
|
||||||
|
second_module,
|
||||||
|
} => {
|
||||||
|
assert_eq!(class, "TShow");
|
||||||
|
assert_eq!(type_repr, "Int");
|
||||||
|
assert_eq!(first_module, "test_22b1_dup_same_module");
|
||||||
|
assert_eq!(second_module, "test_22b1_dup_same_module");
|
||||||
|
}
|
||||||
|
other => panic!("expected DuplicateInstance, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 22b.1: an instance that omits a required (non-default) method
|
||||||
|
/// of its class fires `MissingMethod`.
|
||||||
|
#[test]
|
||||||
|
fn iter22b1_missing_method_fires_diagnostic() {
|
||||||
|
let entry = examples_dir().join("test_22b1_missing_method.ail");
|
||||||
|
let err = load_workspace(&entry).expect_err("must fire missing-method");
|
||||||
|
match err {
|
||||||
|
WorkspaceLoadError::MissingMethod {
|
||||||
|
class,
|
||||||
|
type_repr,
|
||||||
|
method,
|
||||||
|
} => {
|
||||||
|
assert_eq!(class, "TEq");
|
||||||
|
assert_eq!(type_repr, "Int");
|
||||||
|
assert_eq!(method, "teq");
|
||||||
|
}
|
||||||
|
other => panic!("expected MissingMethod, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 22b.2: an instance that specifies a body for a method name the
|
||||||
|
/// class never declared must fire `OverridingNonExistentMethod`.
|
||||||
|
/// (`test_22b2_overriding_nonexistent` is NOT a §C4 carve-out — it
|
||||||
|
/// stays as a `.ail`-loadable fixture.)
|
||||||
|
#[test]
|
||||||
|
fn instance_overriding_nonexistent_method_fires() {
|
||||||
|
let entry = examples_dir().join("test_22b2_overriding_nonexistent.ail");
|
||||||
|
let err = load_workspace(&entry)
|
||||||
|
.expect_err("must fire overriding-non-existent-method");
|
||||||
|
match err {
|
||||||
|
WorkspaceLoadError::OverridingNonExistentMethod {
|
||||||
|
class, type_repr, method,
|
||||||
|
} => {
|
||||||
|
assert_eq!(class, "TEq");
|
||||||
|
assert_eq!(type_repr, "Int");
|
||||||
|
assert_eq!(method, "ne");
|
||||||
|
}
|
||||||
|
other => panic!("expected OverridingNonExistentMethod, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iter 22b.2: an instance `C T` whose class `C` declares a superclass
|
||||||
|
/// `S` requires that `instance S T` also exist in the workspace.
|
||||||
|
#[test]
|
||||||
|
fn instance_without_superclass_instance_fires() {
|
||||||
|
let entry = examples_dir().join("test_22b2_missing_superclass_instance.ail");
|
||||||
|
let err = load_workspace(&entry)
|
||||||
|
.expect_err("must fire missing-superclass-instance");
|
||||||
|
match err {
|
||||||
|
WorkspaceLoadError::MissingSuperclassInstance {
|
||||||
|
class, superclass, type_repr,
|
||||||
|
} => {
|
||||||
|
assert_eq!(class, "test_22b2_missing_superclass_instance.TOrd");
|
||||||
|
assert_eq!(superclass, "TEq");
|
||||||
|
assert_eq!(type_repr, "Int");
|
||||||
|
}
|
||||||
|
other => panic!("expected MissingSuperclassInstance, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// mq.1: positive on-disk pair where `Constraint.class` references a
|
||||||
|
/// class in an imported module via the qualified form loads cleanly.
|
||||||
|
#[test]
|
||||||
|
fn mq1_xmod_constraint_class_fixture_loads() {
|
||||||
|
let entry = examples_dir().join("mq1_xmod_constraint_class.ail");
|
||||||
|
let ws = load_workspace(&entry)
|
||||||
|
.expect("workspace must load with qualified Constraint.class");
|
||||||
|
assert!(ws.modules.contains_key("mq1_xmod_constraint_class"));
|
||||||
|
assert!(ws.modules.contains_key("mq1_xmod_constraint_class_dep"));
|
||||||
|
}
|
||||||
@@ -6,3 +6,6 @@ license.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ailang-core.workspace = true
|
ailang-core.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ailang-surface = { path = "../ailang-surface" }
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
//! Iter 20a snapshot tests.
|
//! Iter 20a snapshot tests.
|
||||||
//!
|
//!
|
||||||
//! For each `examples/<name>.ail.json`, the renderer's output is
|
//! For each `examples/<name>.ail`, the renderer's output is compared
|
||||||
//! compared against the committed `examples/<name>.prose.txt`. The
|
//! against the committed `examples/<name>.prose.txt`. The snapshot
|
||||||
//! snapshot files are part of the repo: changing the renderer requires
|
//! files are part of the repo: changing the renderer requires a
|
||||||
//! a deliberate snapshot update, which is the point of this test.
|
//! deliberate snapshot update, which is the point of this test.
|
||||||
//!
|
//!
|
||||||
//! Update protocol (manual): regenerate the file by running
|
//! Update protocol (manual): regenerate the file by running
|
||||||
//! `cargo run -p ail -- prose <fixture>.ail.json > <fixture>.prose.txt`
|
//! `cargo run -p ail -- prose <fixture>.ail > <fixture>.prose.txt`
|
||||||
//! once the renderer change is agreed; commit alongside the change.
|
//! once the renderer change is agreed; commit alongside the change.
|
||||||
//! There is no `UPDATE_SNAPSHOTS=1` environment short-cut here on
|
//! There is no `UPDATE_SNAPSHOTS=1` environment short-cut here on
|
||||||
//! purpose — Iter 20a is the seeding round, and we want every change
|
//! purpose — Iter 20a is the seeding round, and we want every change
|
||||||
@@ -25,11 +25,11 @@ fn examples_dir() -> PathBuf {
|
|||||||
|
|
||||||
fn check_snapshot(stem: &str) {
|
fn check_snapshot(stem: &str) {
|
||||||
let dir = examples_dir();
|
let dir = examples_dir();
|
||||||
let json_path = dir.join(format!("{stem}.ail.json"));
|
let ail_path = dir.join(format!("{stem}.ail"));
|
||||||
let prose_path = dir.join(format!("{stem}.prose.txt"));
|
let prose_path = dir.join(format!("{stem}.prose.txt"));
|
||||||
|
|
||||||
let module = ailang_core::load_module(&json_path)
|
let module = ailang_surface::load_module(&ail_path)
|
||||||
.unwrap_or_else(|e| panic!("failed to load {json_path:?}: {e}"));
|
.unwrap_or_else(|e| panic!("failed to load {ail_path:?}: {e}"));
|
||||||
let actual = ailang_prose::module_to_prose(&module);
|
let actual = ailang_prose::module_to_prose(&module);
|
||||||
|
|
||||||
let expected = std::fs::read_to_string(&prose_path)
|
let expected = std::fs::read_to_string(&prose_path)
|
||||||
|
|||||||
@@ -275,3 +275,28 @@ fn parse_then_print_then_parse_is_idempotent_on_every_ail_fixture() {
|
|||||||
}
|
}
|
||||||
eprintln!("parse→print→parse idempotency ok for {passed} .ail fixtures");
|
eprintln!("parse→print→parse idempotency ok for {passed} .ail fixtures");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse-determinism (post-form-a-default-authoring §C3): for every
|
||||||
|
/// well-formed `.ail` text `t`, `parse(t)` produces the same canonical
|
||||||
|
/// bytes every invocation. Loader is a pure function of input — no
|
||||||
|
/// randomness, no time dependence, no environment leak.
|
||||||
|
#[test]
|
||||||
|
fn parse_is_deterministic_over_every_ail_fixture() {
|
||||||
|
let mut checked = 0usize;
|
||||||
|
for ail_path in list_ail_fixtures() {
|
||||||
|
let text = std::fs::read_to_string(&ail_path)
|
||||||
|
.unwrap_or_else(|e| panic!("read {ail_path:?}: {e}"));
|
||||||
|
let m1 = ailang_surface::parse(&text)
|
||||||
|
.unwrap_or_else(|e| panic!("parse {ail_path:?}: {e:?}"));
|
||||||
|
let m2 = ailang_surface::parse(&text)
|
||||||
|
.unwrap_or_else(|e| panic!("parse {ail_path:?} (2nd): {e:?}"));
|
||||||
|
let b1 = ailang_core::canonical::to_bytes(&m1);
|
||||||
|
let b2 = ailang_core::canonical::to_bytes(&m2);
|
||||||
|
assert_eq!(
|
||||||
|
b1, b2,
|
||||||
|
"parse non-determinism on {ail_path:?}",
|
||||||
|
);
|
||||||
|
checked += 1;
|
||||||
|
}
|
||||||
|
assert!(checked > 0, "no .ail fixtures found");
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
# iter form-a.1 — Big-bang migration (Tasks 1-5: additive phase + relocation)
|
||||||
|
|
||||||
|
**Date:** 2026-05-13
|
||||||
|
**Started from:** 1a065b37 (post-iter 24.tidy)
|
||||||
|
**Status:** DONE (Tasks 1-5 only; Tasks 6-12 deferred to next dispatch)
|
||||||
|
**Tasks completed:** 5 of 12
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
First half of the form-a-default-authoring milestone-close iter: T1
|
||||||
|
adds three new tests (parse-determinism + CLI-pipeline-idempotency +
|
||||||
|
carve-out-inventory-but-`#[ignore]`'d), T2 bulk-renders the 99
|
||||||
|
missing `examples/<stem>.ail` files via `ail render` (corpus 58 →
|
||||||
|
157), T3 + T4 migrate ~26 test files from `ailang_core::load_workspace`
|
||||||
|
to `ailang_surface::load_workspace` (Group A) and flip subprocess
|
||||||
|
fixture-suffix strings from `.ail.json` to `.ail` (Group B), T5
|
||||||
|
relocates `#[cfg(test)] mod tests` blocks from production source
|
||||||
|
(`ailang-core/src/hash.rs`, `ailang-core/src/workspace.rs`
|
||||||
|
non-carve-out subset, `ailang-codegen/src/lib.rs:3717-3799`) to
|
||||||
|
integration test crates that carry `ailang-surface` as a
|
||||||
|
dev-dependency, eliminating the production-source dependency on
|
||||||
|
`.ail.json` fixture reads. The remaining Tasks 6-12 (bench-driver
|
||||||
|
suffix flips, e2e diff-test rewrite, the actual 156-file
|
||||||
|
`.ail.json` deletion, retiring obsolete roundtrip tests +
|
||||||
|
schema_coverage corpus flip, DESIGN.md §Roundtrip Invariant
|
||||||
|
restatement, CLAUDE.md + DESIGN.md doctrine edits, WhatsNew +
|
||||||
|
roadmap close) run in the Boss's next dispatch on this iter ID.
|
||||||
|
`cargo test --workspace` is green at every per-task boundary (560
|
||||||
|
passed + 4 ignored at iter-half close; 558 + 3 new − 1 transit
|
||||||
|
`#[ignore]` on the inventory test). One Task-2 plan defect surfaced
|
||||||
|
inline and was repaired by pre-emptive forward-pull of Task-3
|
||||||
|
migrations.
|
||||||
|
|
||||||
|
## Per-task notes
|
||||||
|
|
||||||
|
- **iter form-a.1.1** — Added `parse_is_deterministic_over_every_ail_fixture`
|
||||||
|
(round_trip.rs) and `cli_parse_then_render_then_parse_is_idempotent`
|
||||||
|
(roundtrip_cli.rs); created `crates/ailang-core/tests/carve_out_inventory.rs`
|
||||||
|
with the 8-file hardcoded list and `#[ignore]`'d until Task 8.
|
||||||
|
The plan's `cli_parse_then_render_then_parse_is_idempotent` body
|
||||||
|
used an `ail()` helper that doesn't exist in the repo (assert_cmd
|
||||||
|
is not a dev-dep); adapted to use `Command::new(ail_bin())`
|
||||||
|
matching the file's existing pattern. Plan acknowledged this
|
||||||
|
possibility — "verify before writing the body; if `list_ail_fixtures`
|
||||||
|
is missing in roundtrip_cli.rs, add a parallel helper at lines 47+."
|
||||||
|
- **iter form-a.1.2** — Rendered the 99 missing `.ail` files. The
|
||||||
|
bulk render exposed a recon defect: `load_workspace` (since
|
||||||
|
ext-cli.1) prefers `.ail` siblings for *imports* even when the
|
||||||
|
entry is `.ail.json`. The seven Group-A test files whose entries
|
||||||
|
were `.ail.json` but whose imports gained newly-rendered `.ail`
|
||||||
|
siblings broke at Task 2's green-gate. Repair pulled forward five
|
||||||
|
Task-3 migrations (mono_xmod_ctor_pattern, 3 sites in
|
||||||
|
typeclass_22b2, 1 site in typeclass_22b3, env_construction_pin,
|
||||||
|
4 sites in ailang-check/tests/workspace.rs, ctt2_registry_rekey)
|
||||||
|
plus `#[ignore]`'d the 4 fixture-loading tests in
|
||||||
|
`ailang-core/src/workspace.rs` `mod tests` with explicit
|
||||||
|
`form-a.1 transit: Task 5 relocates...` markers. Also added
|
||||||
|
`ailang-surface = { path = "../ailang-surface" }` to
|
||||||
|
`crates/ailang-core/Cargo.toml` `[dev-dependencies]` (required
|
||||||
|
for the ctt2_registry_rekey integration test to use
|
||||||
|
`ailang_surface::load_workspace`).
|
||||||
|
- **iter form-a.1.3** — Finished Group A migration across the
|
||||||
|
remaining 12 Group-A files (10 + the 2 already migrated in T2
|
||||||
|
scope-pull). Two files (`mono_recursive_fn.rs`,
|
||||||
|
`mono_xmod_qualified_ref.rs`) were Group-B-pattern (subprocess
|
||||||
|
CLI, suffix-only flip) but listed in plan's Group A; treated
|
||||||
|
per their actual shape.
|
||||||
|
- **iter form-a.1.4** — Group-B migration: bulk regex flip of
|
||||||
|
`build_and_run("X.ail.json")` → `build_and_run("X.ail")` (3
|
||||||
|
patterns) in e2e.rs across 59 invocations; plus targeted
|
||||||
|
subprocess `examples/<fixture>.ail.json` → `.ail` flips at 11
|
||||||
|
more e2e.rs sites (6× `ws_main`, 2× `sum`, 1× `escape_local_demo`,
|
||||||
|
1× `list_map_poly`, 1× `std_either` left as-is per its raw-bytes
|
||||||
|
roundtrip semantics). Four additional sites in e2e.rs preserved
|
||||||
|
intentionally for raw-JSON inspection (`borrow_own_demo`,
|
||||||
|
`reuse_as_demo`, the `sum.ail.json` diff-test pending Task 7
|
||||||
|
rewrite, and the `ail_run_accepts_ail_source...` dual-form smoke).
|
||||||
|
Four plan-Group-B files (`mono_hash_stability`, `prelude_free_fns`,
|
||||||
|
`print_mono_body_shape`, `show_mono_synthesis`) were
|
||||||
|
Group-A-pattern (consumers of `load_workspace`); migrated both
|
||||||
|
imports AND paths.
|
||||||
|
- **iter form-a.1.5** — Relocated 10 fixture-loading tests from
|
||||||
|
`ailang-core/src/workspace.rs` `mod tests` to new
|
||||||
|
`crates/ailang-core/tests/workspace_pin.rs` (integration test
|
||||||
|
crate); relocated all 8 tests from `ailang-core/src/hash.rs`
|
||||||
|
`mod tests` plus 2 in-memory unit tests to
|
||||||
|
`crates/ailang-core/tests/hash_pin.rs`; relocated 3 tests from
|
||||||
|
`ailang-codegen/src/lib.rs:3717-3799` to
|
||||||
|
`crates/ailang-codegen/tests/eq_primitives_pin.rs`; migrated
|
||||||
|
`ailang-prose/tests/snapshot.rs` (helper + 8 fixtures) to `.ail`
|
||||||
|
+ `ailang_surface::load_module`. Carve-out tests stay in-place
|
||||||
|
(3× `class_param_in_applied_position` / `superclass_with_wrong_param`
|
||||||
|
/ `constraint_with_unbound_var` for 22b2; 3× `ct1_fixture_*`
|
||||||
|
for ct.1). Tempdir-based loader tests
|
||||||
|
(`user_module_named_prelude_is_rejected`, `detects_import_cycle`,
|
||||||
|
`module_not_found_yields_structured_error`) stay in-place too —
|
||||||
|
they don't consume the examples corpus, so they don't need
|
||||||
|
migration for the Form-A goal. Added `ailang-surface` as a
|
||||||
|
`[dev-dependencies]` entry to `ailang-codegen/Cargo.toml` and
|
||||||
|
`ailang-prose/Cargo.toml`. Hash.rs has `mod tests {}` placeholder
|
||||||
|
kept; workspace.rs `mod tests` shrunk from 73 to 57 tests +
|
||||||
|
6 carve-outs unchanged.
|
||||||
|
|
||||||
|
## Concerns
|
||||||
|
|
||||||
|
- **e2e.rs raw-JSON-inspect tests** (4 sites: `borrow_own_demo_modes_are_metadata_only`,
|
||||||
|
`reuse_as_demo_under_rc_uses_inplace_rewrite`, `diff_detects_changed_def`,
|
||||||
|
`render_parse_round_trip_canonical`, plus the dual-form smoke
|
||||||
|
`ail_run_accepts_ail_source_with_same_stdout_as_ail_json`) currently
|
||||||
|
read raw `.ail.json` bytes for substring asserts or byte-roundtrip
|
||||||
|
comparison. They pass today (the `.ail.json` files still exist
|
||||||
|
pre-T8) but will break at T8 when `.ail.json` siblings are deleted.
|
||||||
|
Plan Task 7 only addresses the `diff_detects_changed_def` test (the
|
||||||
|
one that mutates JSON). The other three are out of T1-5 scope and
|
||||||
|
inherit to Boss for T6-12 dispatch resolution (either parse-derived
|
||||||
|
JSON via `ail parse` per T7 pattern, or `#[ignore]` with explicit
|
||||||
|
marker).
|
||||||
|
- **`ailang-core/src/workspace.rs` `mod tests`**: 3 tempdir-based
|
||||||
|
loader-mechanism tests (lines :1743, :1759, :1776 pre-relocation;
|
||||||
|
current line numbers shifted by the deletions) remain in-place
|
||||||
|
per implementer-judgement (they don't consume `examples/`, so
|
||||||
|
the Form-A goal is trivially satisfied). Plan suggested
|
||||||
|
relocating them for doctrinal cleanup; scope reduction is
|
||||||
|
deliberate. They continue to test what they always tested
|
||||||
|
(loader cycle/missing-module/user-prelude-conflict behaviour)
|
||||||
|
and pass.
|
||||||
|
- **Plan recon defect on Group-A vs. Group-B classification**:
|
||||||
|
`mono_hash_stability.rs`, `prelude_free_fns.rs`,
|
||||||
|
`print_mono_body_shape.rs`, `show_mono_synthesis.rs` were listed
|
||||||
|
in plan's Group B but actually use `ailang_core::workspace::load_workspace`
|
||||||
|
directly (Group-A pattern). Migrated correctly by switching both
|
||||||
|
the import and the path. Recorded so a future recon-tightening
|
||||||
|
pass on the planner skill can review whether path-grep-only
|
||||||
|
recon is sufficient for Group A/B classification.
|
||||||
|
- **`mono_recursive_fn.rs` and `mono_xmod_qualified_ref.rs`** were
|
||||||
|
symmetric — listed in plan's Group A but actually CLI subprocess
|
||||||
|
callers (Group-B pattern). Suffix-only flip applied as if Group B.
|
||||||
|
|
||||||
|
## Known debt
|
||||||
|
|
||||||
|
- 4 raw-JSON-inspect tests in e2e.rs need T7-style rewrites or
|
||||||
|
`#[ignore]` markers before Task 8's `.ail.json` deletion lands
|
||||||
|
(see Concerns).
|
||||||
|
- 3 tempdir tests in `ailang-core/src/workspace.rs` `mod tests`
|
||||||
|
could be relocated as doctrinal cleanup but functionally don't
|
||||||
|
need to be (see Concerns).
|
||||||
|
- `mod tests {}` placeholder in `ailang-core/src/hash.rs` could
|
||||||
|
be deleted entirely; left as a structural breadcrumb pointing at
|
||||||
|
the integration test crate.
|
||||||
|
|
||||||
|
## Files touched
|
||||||
|
|
||||||
|
Working tree: 37 modified + 14 new test files + 99 new `.ail`
|
||||||
|
fixtures = 150 paths. Detail:
|
||||||
|
|
||||||
|
### Modified production / test source (37)
|
||||||
|
|
||||||
|
- `crates/ail/tests/`: codegen_import_map_fallback_pin.rs,
|
||||||
|
ct1_check_cli.rs, e2e.rs (extensive build_and_run + 11
|
||||||
|
subprocess sites), eq_float_noinstance.rs, eq_ord_e2e.rs,
|
||||||
|
floats_e2e.rs, ir_snapshot.rs, mono_hash_stability.rs,
|
||||||
|
mono_recursive_fn.rs, mono_unification.rs, mono_xmod_ctor_pattern.rs,
|
||||||
|
mono_xmod_qualified_ref.rs, mq3_multi_class_e2e.rs,
|
||||||
|
polyfn_dot_qualified_branch_pin.rs, prelude_free_fns.rs,
|
||||||
|
print_mono_body_shape.rs, roundtrip_cli.rs, show_mono_synthesis.rs,
|
||||||
|
show_no_instance_e2e.rs, show_print_e2e.rs, typeclass_22b2.rs,
|
||||||
|
typeclass_22b3.rs, typeclass_22c.rs (23 files)
|
||||||
|
- `crates/ailang-check/tests/`: env_construction_pin.rs,
|
||||||
|
method_collision_pin.rs, show_dispatch_pin.rs, workspace.rs (4)
|
||||||
|
- `crates/ailang-codegen/`: Cargo.toml + src/lib.rs (2)
|
||||||
|
- `crates/ailang-core/`: Cargo.toml + src/hash.rs + src/workspace.rs
|
||||||
|
+ tests/ctt2_registry_rekey.rs (4)
|
||||||
|
- `crates/ailang-prose/`: Cargo.toml + tests/snapshot.rs (2)
|
||||||
|
- `crates/ailang-surface/tests/round_trip.rs` (1)
|
||||||
|
- `Cargo.lock` (1)
|
||||||
|
|
||||||
|
### New (14 test files + 99 rendered .ail fixtures)
|
||||||
|
|
||||||
|
- `crates/ailang-codegen/tests/eq_primitives_pin.rs` (relocated T5)
|
||||||
|
- `crates/ailang-core/tests/hash_pin.rs` (relocated T5)
|
||||||
|
- `crates/ailang-core/tests/workspace_pin.rs` (relocated T5)
|
||||||
|
- `crates/ailang-core/tests/carve_out_inventory.rs` (new T1)
|
||||||
|
- 99× `examples/<stem>.ail` rendered via `ail render` (T2)
|
||||||
|
|
||||||
|
## Stats
|
||||||
|
|
||||||
|
bench/orchestrator-stats/2026-05-13-iter-form-a.1.json
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
(module bench_mono_dispatch
|
||||||
|
(class Foo
|
||||||
|
(param a)
|
||||||
|
(method foo
|
||||||
|
(type (fn-type (params a) (ret (con Int))))))
|
||||||
|
(instance
|
||||||
|
(class Foo)
|
||||||
|
(type (con Int))
|
||||||
|
(method foo
|
||||||
|
(body (lam (params (typed x a)) (ret (con Int)) (body (app + (app * x 1103515245) 12345))))))
|
||||||
|
(class Looper
|
||||||
|
(param a)
|
||||||
|
(method loop_call
|
||||||
|
(type (fn-type (params a (con Int)) (ret (con Int))))))
|
||||||
|
(instance
|
||||||
|
(class Looper)
|
||||||
|
(type (con Int))
|
||||||
|
(method loop_call
|
||||||
|
(body (lam (params (typed i a) (typed acc (con Int))) (ret (con Int)) (body (if (app == i 0) acc (tail-app loop_call (app - i 1) (app + acc (app foo (app + acc i))))))))))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (app loop_call 100000000 0)))))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(module bool_to_str_drop_rc
|
||||||
|
(fn main
|
||||||
|
(doc "Iter 24.1: pin that `bool_to_str` participates in RC discipline. Bind the heap-Str result to `s`, consume `s` once in `io/print_str`, let the binder drop at scope close. Under --alloc=rc with AILANG_RC_STATS=1 the atexit summary must report allocs == 1 && frees == 1 && live == 0 — the heap-Str slab allocated by str_alloc inside ailang_bool_to_str rides the same rc_header + ailang_rc_dec path as int_to_str.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let s (app bool_to_str true) (do io/print_str s)))))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(module bool_to_str_smoke_false
|
||||||
|
(fn main
|
||||||
|
(doc "Iter 24.1: stdout-smoke for the false branch of bool_to_str. Stdout must be `false\\n` (puts adds the newline). Companion of bool_to_str_drop_rc.ail.json (which covers the true branch and the RC-stats invariant).")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let s (app bool_to_str false) (do io/print_str s)))))
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
(module closure
|
||||||
|
(fn apply
|
||||||
|
(doc "apply a fn-of-Int to an Int")
|
||||||
|
(type (fn-type (params (fn-type (params (con Int)) (ret (con Int))) (con Int)) (ret (con Int))))
|
||||||
|
(params f x)
|
||||||
|
(body (app f x)))
|
||||||
|
(fn main
|
||||||
|
(doc "captures `n` from the enclosing let, returns 42")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let n 3 (do io/print_int (app apply (lam (params (typed x (con Int))) (ret (con Int)) (body (app + x n))) 39))))))
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
(module cmp_max_smoke
|
||||||
|
(fn cmp_max
|
||||||
|
(type (forall (vars a) (constraints (constraint prelude.Ord a)) (fn-type (params a a) (ret a))))
|
||||||
|
(params x y)
|
||||||
|
(body (match (app compare x y)
|
||||||
|
(case (pat-ctor LT) y)
|
||||||
|
(case _ x))))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (app cmp_max 3 7)))))
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
(module compare_primitives_smoke
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (seq (do io/print_int (match (app compare 1 2)
|
||||||
|
(case (pat-ctor LT) 1)
|
||||||
|
(case (pat-ctor EQ) 2)
|
||||||
|
(case (pat-ctor GT) 3))) (seq (do io/print_int (match (app compare 1 1)
|
||||||
|
(case (pat-ctor LT) 1)
|
||||||
|
(case (pat-ctor EQ) 2)
|
||||||
|
(case (pat-ctor GT) 3))) (seq (do io/print_int (match (app compare 2 1)
|
||||||
|
(case (pat-ctor LT) 1)
|
||||||
|
(case (pat-ctor EQ) 2)
|
||||||
|
(case (pat-ctor GT) 3))) (seq (do io/print_int (match (app compare false true)
|
||||||
|
(case (pat-ctor LT) 1)
|
||||||
|
(case (pat-ctor EQ) 2)
|
||||||
|
(case (pat-ctor GT) 3))) (seq (do io/print_int (match (app compare false false)
|
||||||
|
(case (pat-ctor LT) 1)
|
||||||
|
(case (pat-ctor EQ) 2)
|
||||||
|
(case (pat-ctor GT) 3))) (seq (do io/print_int (match (app compare true false)
|
||||||
|
(case (pat-ctor LT) 1)
|
||||||
|
(case (pat-ctor EQ) 2)
|
||||||
|
(case (pat-ctor GT) 3))) (seq (do io/print_int (match (app compare "a" "b")
|
||||||
|
(case (pat-ctor LT) 1)
|
||||||
|
(case (pat-ctor EQ) 2)
|
||||||
|
(case (pat-ctor GT) 3))) (seq (do io/print_int (match (app compare "a" "a")
|
||||||
|
(case (pat-ctor LT) 1)
|
||||||
|
(case (pat-ctor EQ) 2)
|
||||||
|
(case (pat-ctor GT) 3))) (do io/print_int (match (app compare "b" "a")
|
||||||
|
(case (pat-ctor LT) 1)
|
||||||
|
(case (pat-ctor EQ) 2)
|
||||||
|
(case (pat-ctor GT) 3))))))))))))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module ctt2_collision_cls
|
||||||
|
(class MyC
|
||||||
|
(param a)
|
||||||
|
(method op
|
||||||
|
(type (fn-type (params a) (ret (con Int)))))))
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
(module ctt2_collision_lib
|
||||||
|
(import ctt2_collision_cls)
|
||||||
|
(data Foo
|
||||||
|
(ctor MkLib))
|
||||||
|
(instance
|
||||||
|
(class ctt2_collision_cls.MyC)
|
||||||
|
(type (con Foo))
|
||||||
|
(method op
|
||||||
|
(body (lam (params (typed _x (con Foo))) (ret (con Int)) (body 2))))))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(module ctt2_collision_main
|
||||||
|
(import ctt2_collision_cls)
|
||||||
|
(import ctt2_collision_lib)
|
||||||
|
(data Foo
|
||||||
|
(ctor MkMain))
|
||||||
|
(instance
|
||||||
|
(class ctt2_collision_cls.MyC)
|
||||||
|
(type (con Foo))
|
||||||
|
(method op
|
||||||
|
(body (lam (params (typed _x (con Foo))) (ret (con Int)) (body 1))))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module eq_float_noinstance
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (if (app eq 1.0 2.0) 1 0)))))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(module eq_ord_polymorphic
|
||||||
|
(fn at_most
|
||||||
|
(doc "Composition of prelude `gt` with `not` — semantically equivalent to `le`. Used to exercise polymorphic-helper-over-prelude-fns composition through the unified mono pass.")
|
||||||
|
(type (forall (vars a) (constraints (constraint prelude.Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool)))))
|
||||||
|
(params x y)
|
||||||
|
(body (app not (app gt x y))))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (seq (do io/print_int (if (app at_most 3 5) 1 0)) (seq (do io/print_int (if (app at_most true false) 1 0)) (do io/print_int (if (app at_most "abc" "abd") 1 0)))))))
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
(module eq_ord_user_adt
|
||||||
|
(data IntBox
|
||||||
|
(doc "A one-field box around an Int, used to demonstrate that the unified mono pass synthesises eq__IntBox from a user-written instance, not from the primitive == operator.")
|
||||||
|
(ctor MkIntBox (con Int)))
|
||||||
|
(instance
|
||||||
|
(class prelude.Eq)
|
||||||
|
(type (con IntBox))
|
||||||
|
(doc "Eq IntBox by structural unwrap of the inner Int.")
|
||||||
|
(method eq
|
||||||
|
(body (lam (params (typed a (con IntBox)) (typed b (con IntBox))) (ret (con Bool)) (body (match a
|
||||||
|
(case (pat-ctor MkIntBox ai) (match b
|
||||||
|
(case (pat-ctor MkIntBox bi) (app == ai bi))))))))))
|
||||||
|
(instance
|
||||||
|
(class prelude.Ord)
|
||||||
|
(type (con IntBox))
|
||||||
|
(doc "Ord IntBox by delegating to Int's compare on the inner field.")
|
||||||
|
(method compare
|
||||||
|
(body (lam (params (typed a (con IntBox)) (typed b (con IntBox))) (ret (con prelude.Ordering)) (body (match a
|
||||||
|
(case (pat-ctor MkIntBox ai) (match b
|
||||||
|
(case (pat-ctor MkIntBox bi) (app compare ai bi))))))))))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (seq (do io/print_int (if (app eq (term-ctor IntBox MkIntBox 3) (term-ctor IntBox MkIntBox 3)) 1 0)) (do io/print_int (if (app eq (term-ctor IntBox MkIntBox 3) (term-ctor IntBox MkIntBox 5)) 1 0))))))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(module eq_primitives_smoke
|
||||||
|
(fn to_int
|
||||||
|
(doc "1 if the bool is true, 0 otherwise. Used to render Eq results as printable Ints.")
|
||||||
|
(type (fn-type (params (con Bool)) (ret (con Int))))
|
||||||
|
(params b)
|
||||||
|
(body (if b 1 0)))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (seq (do io/print_int (app to_int (app eq 7 7))) (seq (do io/print_int (app to_int (app eq 7 8))) (seq (do io/print_int (app to_int (app eq true true))) (seq (do io/print_int (app to_int (app eq true false))) (seq (do io/print_int (app to_int (app eq "hello" "hello"))) (do io/print_int (app to_int (app eq "hello" "world")))))))))))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(module float_to_str_smoke
|
||||||
|
(fn main
|
||||||
|
(doc "Iter hs.4: smoke pin for the float_to_str heap-Str builtin. `do io/print_str(float_to_str(3.5))` builds through the whole pipeline (checker resolves float_to_str:(Float)->Str, codegen lowers to call ptr @ailang_float_to_str(double 3.5), runtime/str.c's ailang_float_to_str snprintfs into a heap-Str slab via libc %g, io/print_str's @puts prints from the bytes pointer at offset 8). The exact rendering of 3.5 is libc-target-dependent; on the dev target's clang+glibc with default C locale it is the three bytes `3.5`. Single-value smoke; NaN / +Inf / -Inf edge cases are deferred per the hs.4 plan.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_str (app float_to_str 3.5)))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module floats
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (seq (do io/print_float (app + 1.5 2.5)) (seq (do io/print_float (app int_to_float 42)) (do io/print_float (app neg 1.5)))))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module ge_at_int_smoke
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (if (app ge 3 5) 1 0)))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module gt_at_bool_smoke
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (if (app gt true false) 1 0)))))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(module heap_str_repeated_print_borrow
|
||||||
|
(fn main
|
||||||
|
(doc "Iter eob.1: heap-Str passed to io/print_str twice in sequence. Under the new rule (Term::Do args = Borrow), the second call no longer triggers use-after-consume; the linearity check accepts the program. Under --alloc=rc + AILANG_RC_STATS=1 the slab allocates once and is freed once at scope close (allocs == 1, frees == 1, live == 0).")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let s (app int_to_str 42) (seq (do io/print_str s) (do io/print_str s))))))
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
(module hof
|
||||||
|
(fn inc
|
||||||
|
(doc "increment by one")
|
||||||
|
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||||
|
(params x)
|
||||||
|
(body (app + x 1)))
|
||||||
|
(fn apply
|
||||||
|
(doc "apply a fn-of-Int to an Int")
|
||||||
|
(type (fn-type (params (fn-type (params (con Int)) (ret (con Int))) (con Int)) (ret (con Int))))
|
||||||
|
(params f x)
|
||||||
|
(body (app f x)))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (app apply inc 41)))))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(module int_to_print_int_borrow
|
||||||
|
(fn main
|
||||||
|
(doc "Iter eob.1: primitive Int passed to io/print_int. The rule that Term::Do args are Borrow has no observable RC effect here because Int is unboxed; the test pins that no spurious bookkeeping appears (allocs == 0, frees == 0, live == 0).")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let n 7 (do io/print_int n)))))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(module int_to_str_drop_rc
|
||||||
|
(fn main
|
||||||
|
(doc "Iter hs.4: pin that `int_to_str` participates in RC discipline. Bind the heap-Str result to `s`, consume `s` once in `io/print_str`, let the binder drop at scope close. Under --alloc=rc with AILANG_RC_STATS=1 the atexit summary must report allocs == frees && live == 0 — the heap-Str slab allocated by str_alloc inside ailang_int_to_str rides the same rc_header + ailang_rc_dec path as any other RC value.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let s (app int_to_str 42) (do io/print_str s)))))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(module int_to_str_smoke
|
||||||
|
(fn main
|
||||||
|
(doc "Iter hs.4: smoke pin for the int_to_str heap-Str builtin. `do io/print_str(int_to_str(42))` builds through the whole pipeline (checker resolves int_to_str:(Int)->Str, codegen lowers to call ptr @ailang_int_to_str(i64 42), runtime/str.c's ailang_int_to_str snprintfs into a heap-Str slab, io/print_str's @puts prints from the bytes pointer at offset 8). Single-value smoke; the spec's broader edge-case sweep (0, -1, i64::MAX, i64::MIN) is deferred to a follow-up tidy iter per the hs.4 plan.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_str (app int_to_str 42)))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module le_at_str_smoke
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (if (app le "abc" "abd") 1 0)))))
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
(module list
|
||||||
|
(data IntList
|
||||||
|
(doc "Einfach verkettete Int-Liste, boxed.")
|
||||||
|
(ctor Nil)
|
||||||
|
(ctor Cons (con Int) (con IntList)))
|
||||||
|
(fn sum_list
|
||||||
|
(doc "Summiert die Elemente einer Int-Liste rekursiv.")
|
||||||
|
(type (fn-type (params (con IntList)) (ret (con Int))))
|
||||||
|
(params xs)
|
||||||
|
(body (match xs
|
||||||
|
(case (pat-ctor Nil) 0)
|
||||||
|
(case (pat-ctor Cons h t) (app + h (app sum_list t))))))
|
||||||
|
(fn main
|
||||||
|
(doc "Baut [10, 20, 12] und druckt die Summe (42).")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let xs (term-ctor IntList Cons 10 (term-ctor IntList Cons 20 (term-ctor IntList Cons 12 (term-ctor IntList Nil)))) (do io/print_int (app sum_list xs))))))
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
(module list_map
|
||||||
|
(data IntList
|
||||||
|
(doc "Singly-linked list of Int, boxed.")
|
||||||
|
(ctor Nil)
|
||||||
|
(ctor Cons (con Int) (con IntList)))
|
||||||
|
(fn map_int
|
||||||
|
(doc "Apply f to every element. Recursive on the tail.")
|
||||||
|
(type (fn-type (params (fn-type (params (con Int)) (ret (con Int))) (con IntList)) (ret (con IntList))))
|
||||||
|
(params f xs)
|
||||||
|
(body (match xs
|
||||||
|
(case (pat-ctor Nil) (term-ctor IntList Nil))
|
||||||
|
(case (pat-ctor Cons h t) (term-ctor IntList Cons (app f h) (app map_int f t))))))
|
||||||
|
(fn print_list
|
||||||
|
(doc "Print each Int on its own line.")
|
||||||
|
(type (fn-type (params (con IntList)) (ret (con Unit)) (effects IO)))
|
||||||
|
(params xs)
|
||||||
|
(body (match xs
|
||||||
|
(case (pat-ctor Nil) (lit-unit))
|
||||||
|
(case (pat-ctor Cons h t) (seq (do io/print_int h) (app print_list t))))))
|
||||||
|
(fn main
|
||||||
|
(doc "Build [1,2,3], double each, print result.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let xs (term-ctor IntList Cons 1 (term-ctor IntList Cons 2 (term-ctor IntList Cons 3 (term-ctor IntList Nil)))) (app print_list (app map_int (lam (params (typed x (con Int))) (ret (con Int)) (body (app * x 2))) xs))))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module lt_at_int_smoke
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (if (app lt 3 5) 1 0)))))
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
(module max3
|
||||||
|
(fn max
|
||||||
|
(type (fn-type (params (con Int) (con Int)) (ret (con Int))))
|
||||||
|
(params a b)
|
||||||
|
(body (if (app > a b) a b)))
|
||||||
|
(fn max3
|
||||||
|
(doc "Demonstriert verschachteltes if (statt max-call) zum Test des Block-Trackings.")
|
||||||
|
(type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))
|
||||||
|
(params a b c)
|
||||||
|
(body (if (app > a b) (if (app > a c) a c) (if (app > b c) b c))))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (app max3 3 17 9)))))
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
(module maybe_int
|
||||||
|
(data Maybe (vars a)
|
||||||
|
(doc "Optional value with a polymorphic payload.")
|
||||||
|
(ctor None)
|
||||||
|
(ctor Some a))
|
||||||
|
(fn or_else
|
||||||
|
(doc "Returns the wrapped Int for Some(x) and the default d for None.")
|
||||||
|
(type (fn-type (params (con Maybe (con Int)) (con Int)) (ret (con Int))))
|
||||||
|
(params m d)
|
||||||
|
(body (match m
|
||||||
|
(case (pat-ctor None) d)
|
||||||
|
(case (pat-ctor Some x) x))))
|
||||||
|
(fn main
|
||||||
|
(doc "Print or_else for both arms; expected 7 then 99.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (seq (do io/print_int (app or_else (term-ctor Maybe Some 7) 99)) (do io/print_int (app or_else (term-ctor Maybe None) 99))))))
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
(module mono_hash_pin_smoke
|
||||||
|
(fn ord_to_int
|
||||||
|
(type (fn-type (params (con prelude.Ordering)) (ret (con Int))))
|
||||||
|
(params o)
|
||||||
|
(body (match o
|
||||||
|
(case (pat-ctor LT) -1)
|
||||||
|
(case (pat-ctor EQ) 0)
|
||||||
|
(case (pat-ctor GT) 1))))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (seq (do io/print_bool (app eq 1 1)) (seq (do io/print_bool (app eq true true)) (seq (do io/print_bool (app eq "a" "a")) (seq (do io/print_int (app ord_to_int (app compare 1 2))) (seq (do io/print_int (app ord_to_int (app compare false true))) (do io/print_int (app ord_to_int (app compare "a" "b")))))))))))
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
(module mq1_xmod_constraint_class
|
||||||
|
(import mq1_xmod_constraint_class_dep)
|
||||||
|
(fn useShow
|
||||||
|
(doc "Positive mq.1 fixture: a polymorphic fn that takes a single value and a Show constraint, ignores the value, and returns Unit. Exercises the qualified `Constraint.class` shape end-to-end (loader + validator + check_workspace).")
|
||||||
|
(type (forall (vars a) (constraints (constraint mq1_xmod_constraint_class_dep.Show a)) (fn-type (params (borrow a)) (ret (con Unit)))))
|
||||||
|
(params _x)
|
||||||
|
(body (lit-unit))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module mq1_xmod_constraint_class_dep
|
||||||
|
(class Show
|
||||||
|
(param a)
|
||||||
|
(method show
|
||||||
|
(type (fn-type (params a) (ret (con Str)))))))
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
(module mq3_class_eq_vs_fn_eq
|
||||||
|
(import mq3_class_eq_vs_fn_eq_classmod)
|
||||||
|
(import mq3_class_eq_vs_fn_eq_fnmod)
|
||||||
|
(fn main
|
||||||
|
(doc "mq.3.6 fixture (c): bare `myeq 1 2` in a workspace with `class MyEq` (in classmod) and `fn myeq` (in fnmod) both imported. Fn wins per lookup precedence; the typechecker emits `class-method-shadowed-by-fn` warning so the LLM-author sees the shadow.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_bool (app myeq 1 2)))))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(module mq3_class_eq_vs_fn_eq_classmod
|
||||||
|
(class MyEq
|
||||||
|
(param a)
|
||||||
|
(method myeq
|
||||||
|
(type (fn-type (params (borrow a) (borrow a)) (ret (con Bool))))))
|
||||||
|
(instance
|
||||||
|
(class MyEq)
|
||||||
|
(type (con Int))
|
||||||
|
(method myeq
|
||||||
|
(body (lam (params (typed x (con Int)) (typed y (con Int))) (ret (con Bool)) (body true))))))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(module mq3_class_eq_vs_fn_eq_fnmod
|
||||||
|
(fn myeq
|
||||||
|
(doc "mq.3.6 fixture (c) helper: free fn `myeq` that shadows the class method `myeq` declared in `mq3_class_eq_vs_fn_eq_classmod`. Always returns false so the fn-wins precedence is observable at runtime if the e2e test ever runs the binary.")
|
||||||
|
(type (fn-type (params (borrow (con Int)) (borrow (con Int))) (ret (con Bool))))
|
||||||
|
(params x y)
|
||||||
|
(body false)))
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
(module mq3_two_show_ambiguous
|
||||||
|
(import mq3_two_show_ambiguous_a)
|
||||||
|
(import mq3_two_show_ambiguous_b)
|
||||||
|
(fn main
|
||||||
|
(doc "mq.3.6 fixture (a): bare `show 42` in a workspace where two Show classes (in modA and modB) each ship Show Int. Without an explicit qualifier the dispatch is genuinely ambiguous; the typechecker must fire `ambiguous-method-resolution` naming both candidate classes.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_str (app show 42)))))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(module mq3_two_show_ambiguous_a
|
||||||
|
(class Show
|
||||||
|
(param a)
|
||||||
|
(method show
|
||||||
|
(type (fn-type (params (borrow a)) (ret (con Str))))))
|
||||||
|
(instance
|
||||||
|
(class Show)
|
||||||
|
(type (con Int))
|
||||||
|
(method show
|
||||||
|
(body (lam (params (typed x (con Int))) (ret (con Str)) (body (app int_to_str x)))))))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(module mq3_two_show_ambiguous_b
|
||||||
|
(class Show
|
||||||
|
(param a)
|
||||||
|
(method show
|
||||||
|
(type (fn-type (params (borrow a)) (ret (con Str))))))
|
||||||
|
(instance
|
||||||
|
(class Show)
|
||||||
|
(type (con Int))
|
||||||
|
(method show
|
||||||
|
(body (lam (params (typed x (con Int))) (ret (con Str)) (body (app int_to_str x)))))))
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
(module mq3_two_show_qualified
|
||||||
|
(import mq3_two_show_ambiguous_a)
|
||||||
|
(import mq3_two_show_ambiguous_b)
|
||||||
|
(fn main
|
||||||
|
(doc "mq.3.6 fixture (b): explicit qualifier `mq3_two_show_ambiguous_a.Show.show 42` disambiguates the same workspace as fixture (a). Resolves cleanly to modA's class; typecheck passes.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_str (app mq3_two_show_ambiguous_a.Show.show 42)))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module ne_at_int_smoke
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (if (app ne 3 5) 1 0)))))
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
(module ordering_match
|
||||||
|
(fn main
|
||||||
|
(doc "Iter 23.1 fixture: pattern-match on prelude Ordering ctor without explicit prelude import.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (match (term-ctor prelude.Ordering LT)
|
||||||
|
(case (pat-ctor LT) 1)
|
||||||
|
(case (pat-ctor EQ) 2)
|
||||||
|
(case (pat-ctor GT) 3))))))
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
(module poly_apply
|
||||||
|
(fn apply
|
||||||
|
(type (forall (vars a b) (fn-type (params (fn-type (params a) (ret b)) a) (ret b))))
|
||||||
|
(params f x)
|
||||||
|
(body (app f x)))
|
||||||
|
(fn succ
|
||||||
|
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||||
|
(params n)
|
||||||
|
(body (app + n 1)))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (app apply succ 41)))))
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
(module poly_id
|
||||||
|
(fn id
|
||||||
|
(type (forall (vars a) (fn-type (params a) (ret a))))
|
||||||
|
(params x)
|
||||||
|
(body x))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (seq (do io/print_int (app id 42)) (do io/print_bool (app id true))))))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(module rc_box_drop
|
||||||
|
(data Box
|
||||||
|
(doc "Single-cell ADT around an Int. Iter 18c.3 RC fixture: shallow free is sufficient because Box has no boxed children.")
|
||||||
|
(ctor MkBox (con Int)))
|
||||||
|
(fn main
|
||||||
|
(doc "Bind a heap-allocated MkBox to `b`, read its Int via match, print it. Under --alloc=rc, codegen emits `ailang_rc_dec(b)` at the let scope close: `b` is unique, has zero consume uses (only the match scrutinee, a borrow), and the body's tail value is the printed Unit, not `b` itself. The Box has no boxed children so shallow `free()` is correct.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let b (term-ctor Box MkBox 42) (match b
|
||||||
|
(case (pat-ctor MkBox x) (do io/print_int x)))))))
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
(module rc_list_drop
|
||||||
|
(data IntList
|
||||||
|
(doc "Recursive Int list. Iter 18c.4 RC fixture: codegen emits `drop_rc_list_drop_IntList` whose `Cons` arm recursively calls itself on the tail field.")
|
||||||
|
(ctor Nil)
|
||||||
|
(ctor Cons (con Int) (con IntList)))
|
||||||
|
(fn sum_list
|
||||||
|
(doc "Recursive fold over IntList.")
|
||||||
|
(type (fn-type (params (con IntList)) (ret (con Int))))
|
||||||
|
(params xs)
|
||||||
|
(body (match xs
|
||||||
|
(case (pat-ctor Nil) 0)
|
||||||
|
(case (pat-ctor Cons h t) (app + h (app sum_list t))))))
|
||||||
|
(fn main
|
||||||
|
(doc "Build a 5-element IntList [1,2,3,4,5] and print its sum (15). Under --alloc=rc the recursive drop_rc_list_drop_IntList fn cascades through the tail at process exit when sum_list returns ownership-implicit (the consume_count of `xs` is 1 so no dec at the outer let; the test's correctness invariant is the byte-identical stdout — leak quantification is 18f's bench).")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let xs (term-ctor IntList Cons 1 (term-ctor IntList Cons 2 (term-ctor IntList Cons 3 (term-ctor IntList Cons 4 (term-ctor IntList Cons 5 (term-ctor IntList Nil)))))) (do io/print_int (app sum_list xs))))))
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
(module rc_list_drop_borrow
|
||||||
|
(data IntList
|
||||||
|
(doc "Recursive Int list. Iter 18c.4 RC fixture: codegen emits a recursive drop fn; this fixture keeps the binder consume_count at 0 so the drop call actually fires at scope close, exercising the recursive cascade at runtime over a 5-element list.")
|
||||||
|
(ctor Nil)
|
||||||
|
(ctor Cons (con Int) (con IntList)))
|
||||||
|
(fn main
|
||||||
|
(doc "Build a 5-element IntList and print only its head via match. xs has consume_count == 0 (only the match scrutinee, a borrow), so under --alloc=rc the let scope close emits `drop_rc_list_drop_borrow_IntList(xs)` which recursively dec-cascades over the whole 5-cell chain.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let xs (term-ctor IntList Cons 11 (term-ctor IntList Cons 22 (term-ctor IntList Cons 33 (term-ctor IntList Cons 44 (term-ctor IntList Cons 55 (term-ctor IntList Nil)))))) (match xs
|
||||||
|
(case (pat-ctor Nil) (do io/print_int 0))
|
||||||
|
(case (pat-ctor Cons h t) (do io/print_int h)))))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module show_mono_pin_smoke
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let s1 (app show 42) (let s2 (app show true) (let s3 (app show "x") (let s4 (app show 1.5) (do io/print_str s1))))))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module show_no_instance
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let f (lam (params (typed x (con Int))) (ret (con Int)) (body x)) (app print f)))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module show_print_smoke
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (seq (seq (seq (app print 42) (app print true)) (app print "hello")) (app print 3.14)))))
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
(module show_user_adt
|
||||||
|
(data IntBox
|
||||||
|
(ctor MkIntBox (con Int)))
|
||||||
|
(instance
|
||||||
|
(class prelude.Show)
|
||||||
|
(type (con IntBox))
|
||||||
|
(method show
|
||||||
|
(body (lam (params (typed x (con IntBox))) (ret (con Str)) (body (match x
|
||||||
|
(case (pat-ctor MkIntBox n) (app int_to_str n))))))))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (app print (term-ctor IntBox MkIntBox 7)))))
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
(module sort
|
||||||
|
(data IntList
|
||||||
|
(doc "Singly-linked Int list, boxed.")
|
||||||
|
(ctor Nil)
|
||||||
|
(ctor Cons (con Int) (con IntList)))
|
||||||
|
(fn insert
|
||||||
|
(doc "Insert y into a sorted list xs, preserving order.")
|
||||||
|
(type (fn-type (params (con Int) (con IntList)) (ret (con IntList))))
|
||||||
|
(params y xs)
|
||||||
|
(body (match xs
|
||||||
|
(case (pat-ctor Nil) (term-ctor IntList Cons y (term-ctor IntList Nil)))
|
||||||
|
(case (pat-ctor Cons h t) (if (app <= y h) (term-ctor IntList Cons y (term-ctor IntList Cons h t)) (term-ctor IntList Cons h (app insert y t)))))))
|
||||||
|
(fn sort
|
||||||
|
(doc "Insertion sort: build the result by inserting each head into the sorted tail.")
|
||||||
|
(type (fn-type (params (con IntList)) (ret (con IntList))))
|
||||||
|
(params xs)
|
||||||
|
(body (match xs
|
||||||
|
(case (pat-ctor Nil) (term-ctor IntList Nil))
|
||||||
|
(case (pat-ctor Cons h t) (app insert h (app sort t))))))
|
||||||
|
(fn print_list
|
||||||
|
(type (fn-type (params (con IntList)) (ret (con Unit)) (effects IO)))
|
||||||
|
(params xs)
|
||||||
|
(body (match xs
|
||||||
|
(case (pat-ctor Nil) (lit-unit))
|
||||||
|
(case (pat-ctor Cons h t) (seq (do io/print_int h) (tail-app print_list t))))))
|
||||||
|
(fn main
|
||||||
|
(doc "Sort and print [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let xs (term-ctor IntList Cons 3 (term-ctor IntList Cons 1 (term-ctor IntList Cons 4 (term-ctor IntList Cons 1 (term-ctor IntList Cons 5 (term-ctor IntList Cons 9 (term-ctor IntList Cons 2 (term-ctor IntList Cons 6 (term-ctor IntList Cons 5 (term-ctor IntList Cons 3 (term-ctor IntList Cons 5 (term-ctor IntList Nil)))))))))))) (app print_list (app sort xs))))))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(module str_clone_cross_realisation
|
||||||
|
(fn main
|
||||||
|
(doc "Iter 24.1: cross-realisation invariant for str_clone. Clones a heap-Str (produced by int_to_str 42) AND a static-Str (literal `abc`); both clones produce fresh heap-Str slabs that print their input bytes. Pins the uniform-consumer-ABI claim — str_clone only reads the len-field at offset 0 plus the bytes plus the NUL; it never consults the (absent for static-Str) rc_header. RC stats under --alloc=rc: allocs == 3 (int_to_str output + two str_clone outputs), frees == 3, live == 0.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let src_heap (app int_to_str 42) (let c1 (app str_clone src_heap) (let c2 (app str_clone "abc") (let _a (do io/print_str c1) (do io/print_str c2))))))))
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(module str_clone_drop_rc
|
||||||
|
(fn main
|
||||||
|
(doc "Iter 24.1: pin that `str_clone` participates in RC discipline AND emits correct bytes. Input is a static-Str literal; the clone produces a fresh heap-Str slab whose payload is byte-equal to the input. RC stats under --alloc=rc with AILANG_RC_STATS=1: allocs == 1 (the clone), frees == 1 (let-binder drop at scope close), live == 0.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let s (app str_clone "hello") (do io/print_str s)))))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(module str_field_in_adt_heap
|
||||||
|
(data Boxed
|
||||||
|
(doc "Iter hs.4 fixture: single-cell ADT around a Str field. Used to pin the drop discipline for ADTs that carry a heap-Str payload — both the ADT cell and the heap-Str inside it must round-trip through the RC walk.")
|
||||||
|
(ctor Box (con Str)))
|
||||||
|
(fn main
|
||||||
|
(doc "Iter hs.4: pin that `field_drop_call` for `Str` correctly dispatches to `ailang_rc_dec` when the Str payload is heap-Str. Construct `Box(int_to_str(42))`, bind, pattern-match to read the Str, print it. Under --alloc=rc with AILANG_RC_STATS=1: allocs >= 2 (ADT cell + heap-Str slab), allocs == frees, live == 0. drop.rs's Str-arm comment names the codegen-level elision that protects static-Str from this same path; here the Str is heap-allocated so rc_dec is the right (and only) consumer.")
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (let b (term-ctor Boxed Box (app int_to_str 42)) (match b
|
||||||
|
(case (pat-ctor Box s) (do io/print_str s)))))))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(module sum
|
||||||
|
(fn sum
|
||||||
|
(doc "rekursive Summe 0..=n")
|
||||||
|
(type (fn-type (params (con Int)) (ret (con Int))))
|
||||||
|
(params n)
|
||||||
|
(body (if (app == n 0) 0 (app + n (app sum (app - n 1))))))
|
||||||
|
(fn main
|
||||||
|
(type (fn-type (params) (ret (con Unit)) (effects IO)))
|
||||||
|
(params)
|
||||||
|
(body (do io/print_int (app sum 10)))))
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
(module test_22b1_dup_a
|
||||||
|
(import test_22b1_dup_b)
|
||||||
|
(import test_22b1_dup_classmod)
|
||||||
|
(instance
|
||||||
|
(class test_22b1_dup_classmod.TShow)
|
||||||
|
(type (con test_22b1_dup_b.MyInt))
|
||||||
|
(method tshow
|
||||||
|
(body "from-a"))))
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
(module test_22b1_dup_b
|
||||||
|
(import test_22b1_dup_classmod)
|
||||||
|
(data MyInt
|
||||||
|
(ctor MkMyInt (con Int)))
|
||||||
|
(instance
|
||||||
|
(class test_22b1_dup_classmod.TShow)
|
||||||
|
(type (con MyInt))
|
||||||
|
(method tshow
|
||||||
|
(body "from-b"))))
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
(module test_22b1_dup_classmod
|
||||||
|
(class TShow
|
||||||
|
(param a)
|
||||||
|
(method tshow
|
||||||
|
(type (fn-type (params a) (ret (con Str)))))))
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user