Files
AILang/crates/ailang-check/tests/loop_recur_pin.rs
T
Brummel 96db54d15d iter it.1: loop/recur additive — Term::Loop/Term::Recur/LoopBinder end-to-end
Iteration-discipline milestone, 1 of 3. Adds named loop + recur as
strictly-additive first-class AST nodes: parse/print/prose/serde/
round-trip/schema lockstep, typecheck (binder typing + recur
arity/type unification via loop_stack threaded as mut.2's
mut_scope_stack; recur-tail-position via verify_loop_body), codegen
(loop-header + one phi per binder; recur back-edge br with a NEW
parallel block_terminated setter; lambda-boundary loop_frames
save/restore). Four Recur* CheckError variants. Strictly additive:
zero deletions touch tail-app/tail-do or the seven existing
block_terminated sites — this is what makes the destructive it.3
safe. recur synth = fresh metavar (resolves the plan's flagged
Type::unit() risk). loop_counter->55, loop_in_lambda->49, four
negatives fire, tail-app byte-identical, cargo test --workspace
green. Spec fda9b78, plan 7381a42.
2026-05-15 14:38:55 +02:00

61 lines
2.1 KiB
Rust

//! Iter it.1 (Task 5): pin tests that the four `recur` negative
//! fixtures produce their exact diagnostic codes, and that the
//! positive `loop_smoke` fixture typechecks clean.
//!
//! Spec: `docs/specs/2026-05-15-iteration-discipline.md`. The four
//! negatives live as canonical `.ail.json` (diagnostic code is the
//! load-bearing assertion, not the surface form), mirroring the
//! `mut_typecheck_pin` carve-out precedent.
use ailang_check::check_workspace;
use ailang_surface::load_workspace;
use std::path::PathBuf;
fn examples_dir() -> PathBuf {
let manifest = env!("CARGO_MANIFEST_DIR");
PathBuf::from(manifest)
.parent().expect("CARGO_MANIFEST_DIR has a parent (crates/ailang-check)")
.parent().expect("CARGO_MANIFEST_DIR has a grandparent (crates/)")
.join("examples")
}
/// Load the named fixture under `examples/`, run check_workspace, and
/// return the diagnostic code list (one entry per Diagnostic).
fn check_fixture(fixture_name: &str) -> Vec<String> {
let path = examples_dir().join(fixture_name);
let ws = load_workspace(&path)
.unwrap_or_else(|e| panic!("workspace `{fixture_name}` must load: {e:?}"));
let diags = check_workspace(&ws);
diags.iter().map(|d| d.code.clone()).collect()
}
#[test]
fn loop_smoke_typechecks_clean() {
let codes = check_fixture("loop_smoke.ail");
assert!(codes.is_empty(), "expected zero diagnostics, got {codes:?}");
}
#[test]
fn recur_outside_loop_is_rejected() {
let codes = check_fixture("test_recur_outside_loop.ail.json");
assert_eq!(codes, vec!["recur-outside-loop".to_string()]);
}
#[test]
fn recur_arity_mismatch_is_rejected() {
let codes = check_fixture("test_recur_arity_mismatch.ail.json");
assert_eq!(codes, vec!["recur-arity-mismatch".to_string()]);
}
#[test]
fn recur_type_mismatch_is_rejected() {
let codes = check_fixture("test_recur_type_mismatch.ail.json");
assert_eq!(codes, vec!["recur-type-mismatch".to_string()]);
}
#[test]
fn recur_not_in_tail_position_is_rejected() {
let codes = check_fixture("test_recur_not_in_tail_position.ail.json");
assert_eq!(codes, vec!["recur-not-in-tail-position".to_string()]);
}