source: scrub iter-code / Decision-N residue from inline comments

Follow-up to bcd4181: the remaining ~530 inline `//` and `///`
comments still carrying opaque shorthand are now reformulated to
their content phrases. The only surviving `iter-<code>` reference
in source is the literal filename
`docs/journals/2026-05-13-iter-mq.3.md` (a real journal file).

Sweep covered:
  - `// Iter X.Y: <text>` prefixes (Iter 13a / 14a / 14e / 15g-aux /
    16b.x / 16d / 16e / 18b / 18c.x / 18d.x / 18e / 18g.x / 19a /
    19a.1 / 19b / 20a / 20f / 22-floats.x / 22b.x / 22c / 23.x /
    24.1 / cli-diag-human / hs.x / str-concat / etc.) — fully
    removed; the descriptive text that followed each prefix stays.
  - `// (Decision N)` and `per Decision N` and `Decision N axis 3` —
    replaced with the content phrase plus the relevant contract
    file (`design/contracts/tail-calls.md` for Decision 8,
    `design/contracts/memory-model.md` for Decision 10,
    `design/contracts/typeclasses.md` and `design/models/typeclasses.md`
    for Decision 11, `design/contracts/authoring-surface.md` for
    Decision 6, "the transitional dual-allocator" for Decision 9,
    "Effect prose" for Decision 3).
  - `// mq.X / mq.X (Task N) / mq.X journal / mq.X invariant` ->
    "the canonical-class-form rule / Class-class repurpose /
    method-dispatch-refactor journal / canonical-class-form
    invariant".
  - `// ct.X / ct.X (canonical-type-names) / ct.1.5a + ctt.2 /
    ct.2 Task N` -> "the canonical-form rule for type references /
    the canonical-form normalisation step / canonical-type-lookup
    refactor".
  - `// eob.X` -> "heap-Str-ABI" / "the Str carve-out".
  - `// rpe.X` -> "the per-type-print-op retirement".
  - `// post-mq.X / Pre-ct.X / pre-mq.X` -> "post-canonical-class-form" /
    "Pre-canonical-type-form" etc.
  - `/// Iter X regression: / /// Iter X.Y: / /// Iter A arm-close /
    /// Iter ct.4 (...): / /// Iter rpe.1 ...` -> descriptive
    phrases.

The journal filename in `crates/ailang-core/src/workspace.rs:573`
stays verbatim because it points at an actual file under
`docs/journals/`.

Tests: full `cargo test --workspace` green (80/80 test-result blocks
clean, no FAILED line). design_index_pin 5/5 + docs_honesty_pin 5/5
gating tests pass.
This commit is contained in:
2026-05-20 09:58:04 +02:00
parent bcd41810f4
commit ac4d545570
43 changed files with 1004 additions and 1000 deletions
+88 -87
View File
@@ -37,7 +37,7 @@ fn build_and_run(example: &str) -> String {
String::from_utf8(output.stdout).expect("stdout utf8")
}
/// Iter 18b: build with an explicit `--alloc=<alloc>` and run.
/// build with an explicit `--alloc=<alloc>` and run.
/// Mirrors [`build_and_run`] but threads the allocator selector through
/// to `ail build`. Used by the alloc-equivalence tests that assert the
/// `gc` and `rc` paths produce byte-identical stdout.
@@ -140,7 +140,7 @@ fn list_sum_via_match() {
assert_eq!(stdout.trim(), "42");
}
/// Iter 7: first-class function references — `apply(inc, 41)` must
/// first-class function references — `apply(inc, 41)` must
/// produce 42, exercising fn-typed parameters and indirect call.
#[test]
fn higher_order_apply_inc() {
@@ -148,7 +148,7 @@ fn higher_order_apply_inc() {
assert_eq!(stdout.trim(), "42");
}
/// Iter 8b: lambdas with capture. `let n = 3 in apply(\x. x + n, 39)`
/// lambdas with capture. `let n = 3 in apply(\x. x + n, 39)`
/// must print 42 — the lambda captures `n` from the enclosing `let`,
/// the env struct is malloc'd, the closure pair is passed to `apply`,
/// and apply's indirect call unpacks both halves.
@@ -158,7 +158,7 @@ fn closure_captures_let_n() {
assert_eq!(stdout.trim(), "42");
}
/// Iter 9 dogfood: a non-trivial program that combines ADTs, recursion,
/// a non-trivial program that combines ADTs, recursion,
/// closure-without-capture, fn-typed parameters, IO effects, and `let`-
/// sequencing of effectful sub-expressions in a pattern-match arm.
/// `map (\x. x * 2)` over `[1, 2, 3]` then print each element.
@@ -169,7 +169,7 @@ fn list_map_doubles_then_prints() {
assert_eq!(lines, vec!["2", "4", "6"]);
}
/// Iter 14a: end-to-end exercise of parameterised ADTs through a
/// end-to-end exercise of parameterised ADTs through a
/// polymorphic higher-order fn. `data List a` plus
/// `map : forall a b. ((a) -> b, List<a>) -> List<b>` recursive,
/// instantiated at `(Int, Int)`. Guards: forall with two type vars +
@@ -183,7 +183,7 @@ fn list_map_poly_inc_then_prints() {
assert_eq!(lines, vec!["2", "3", "4"]);
}
/// Iter 14f: stress the Boehm conservative GC integration end-to-end.
/// stress the Boehm conservative GC integration end-to-end.
/// `build 50` performs 50 `Cons` allocations via `GC_malloc`; `sum_list`
/// walks the resulting `List Int` and prints the sum (1275 = 50*51/2).
/// If GC is wired wrongly — missing `-lgc`, missing
@@ -202,7 +202,7 @@ fn gc_handles_recursive_list_construction() {
);
}
/// Iter 17a: per-fn arena via stack `alloca` for non-escaping
/// per-fn arena via stack `alloca` for non-escaping
/// allocations. The fixture's `peek` and `count` fns each build a
/// `Box(_)` whose payload is dropped via a wildcard pattern; the
/// allocation is fully consumed locally and never flows to a fn
@@ -264,7 +264,7 @@ fn iter17a_local_box_alloca() {
}
}
/// Iter 14e: `tail: true` annotation on `print_list`'s recursive
/// `tail: true` annotation on `print_list`'s recursive
/// call must reach LLVM as a `musttail call`. Asserted by emitting IR
/// for list_map_poly and grepping for the exact instruction. This is
/// the only direct evidence that the type-system marker actually
@@ -320,7 +320,7 @@ fn iter14e_print_list_recursion_emits_musttail() {
panic!("musttail call line not found (sanity check)");
}
/// Iter 11 dogfood: insertion sort over an 11-element IntList.
/// insertion sort over an 11-element IntList.
/// Exercises `<=`, `if`, mutual-leaf recursion (`insert` and `sort`),
/// nested ctor construction, and the Iter 10 seq operator inside
/// print_list. Validates that the language handles deeper ADT
@@ -335,7 +335,7 @@ fn insertion_sort_orders_list() {
);
}
/// Iter 12b: polymorphism end-to-end. `id : forall a. (a) -> a`
/// polymorphism end-to-end. `id : forall a. (a) -> a`
/// gets called with `42` (Int) and `true` (Bool); each instantiation
/// triggers a separate specialised LLVM fn. Output: 42, then "true".
#[test]
@@ -345,7 +345,7 @@ fn polymorphic_id_at_int_and_bool() {
assert_eq!(lines, vec!["42", "true"]);
}
/// Iter 12b: polymorphism with a function-typed parameter.
/// polymorphism with a function-typed parameter.
/// `apply : forall a b. ((a) -> b, a) -> b` invoked as
/// `apply(succ, 41) == 42`. Exercises the closure-pair ABI inside a
/// monomorphised body (a fn-typed param survives substitution).
@@ -355,7 +355,7 @@ fn polymorphic_apply_with_fn_param() {
assert_eq!(stdout.trim(), "42");
}
/// Iter 13b: round-trip a primitive through a parameterised ADT and a
/// round-trip a primitive through a parameterised ADT and a
/// polymorphic-fn boundary. `type Box[a] = MkBox(a)` plus
/// `unbox : forall a. (Box<a>) -> a` plus `print_int(unbox(MkBox(42)))`.
/// Guards: ctor lower with substituted LLVM field types,
@@ -367,7 +367,7 @@ fn parameterised_box_round_trip() {
assert_eq!(stdout.trim(), "42");
}
/// Iter 13b: pattern-match on `Maybe<Int>`. `or_else(Some(7), 99) == 7`
/// pattern-match on `Maybe<Int>`. `or_else(Some(7), 99) == 7`
/// then `or_else(None, 99) == 99`. Guards: match-arm field
/// substitution at a parameterised ctor (the `Some(x)` arm must bind
/// `x : Int`, not `x : a`).
@@ -378,13 +378,14 @@ fn parameterised_maybe_match() {
assert_eq!(lines, vec!["7", "99"]);
}
/// Iter 15a: cross-module reference to a parameterised ADT, including
/// cross-module reference to a parameterised ADT, including
/// its ctors and a polymorphic combinator instantiated at `(Int, Int)`.
/// `std_maybe_demo` imports `std_maybe`, qualifies the type-name slot
/// of every `term-ctor` (`std_maybe.Maybe`), and exercises
/// `from_maybe`, `is_some`, `is_none`, and `map_maybe` once each.
/// Property protected: the qualified-only convention (Decision 6's
/// architectural pin extended to types and ctors per the brief)
/// Property protected: the qualified-only convention (the
/// authoring surface's architectural pin extended to types and
/// ctors per the brief)
/// flows end-to-end through check, codegen, and runtime.
#[test]
fn cross_module_maybe_demo() {
@@ -393,7 +394,7 @@ fn cross_module_maybe_demo() {
assert_eq!(lines, vec!["7", "99", "true", "true", "42"]);
}
/// Iter 15b: drives the polymorphic `std_list` combinators end-to-end.
/// drives the polymorphic `std_list` combinators end-to-end.
/// Exercises a recursive cross-module ADT (`std_list.List<a>`) consumed
/// from a separate module that also imports `std_maybe`. Guards the
/// `qualify_local_types` propagation in both `Term::Ctor` synth and
@@ -412,7 +413,7 @@ fn std_list_demo() {
);
}
/// Iter 15c: empirical stress test for `std_list`'s folds at N=1000.
/// empirical stress test for `std_list`'s folds at N=1000.
///
/// Property protected: `fold_left`'s tail-call marker actually
/// survives monomorphisation and lowers to `musttail call` in the
@@ -450,7 +451,7 @@ fn std_list_stress_1000_element_folds() {
// `crates/ail/tests/roundtrip_cli.rs` (added in T1), which is strictly
// stronger (whole-corpus vs. one-fixture).
/// Iter 16a: nested constructor patterns. Property protected:
/// nested constructor patterns. Property protected:
/// `ailang_core::desugar` flattens `Cons a (Cons b _)` to a chain of
/// single-level matches before the checker/codegen sees it. Without
/// the desugar pass the checker would emit
@@ -463,7 +464,7 @@ fn nested_ctor_pattern_first_two_sum() {
assert_eq!(lines, vec!["30"]);
}
/// Iter 15f: `std_pair` end-to-end. Polymorphic product type with
/// `std_pair` end-to-end. Polymorphic product type with
/// two type vars and a single constructor; six combinators including
/// the 3-type-var `map_first` / `map_second` reshapers. Property
/// protected: `Pair<a, b> -> Pair<c, b>` and `Pair<a, b> -> Pair<a, c>`
@@ -477,7 +478,7 @@ fn std_pair_demo() {
assert_eq!(lines, vec!["7", "9", "9", "7", "8", "18"]);
}
/// Iter 15d: `std_either` end-to-end. First stdlib ADT with two type
/// `std_either` end-to-end. First stdlib ADT with two type
/// parameters (`Either<e, a>`); first combinator with three type vars
/// (`either : (e -> c) -> (a -> c) -> Either<e, a> -> c`).
///
@@ -497,7 +498,7 @@ fn std_either_demo() {
);
}
/// Iter 15g: `std_either_list` end-to-end. First stdlib fn set that
/// `std_either_list` end-to-end. First stdlib fn set that
/// imports three other stdlib modules (`std_list`, `std_either`,
/// `std_pair`) and returns a compound polymorphic ADT tree
/// (`partition_eithers : List<Either<e,a>> -> Pair<List<e>, List<a>>`).
@@ -519,7 +520,7 @@ fn std_either_list_demo() {
assert_eq!(lines, vec!["2", "3", "2", "3"]);
}
/// Iter 15h: `std_list.take` and `std_list.drop` end-to-end. Both are
/// `std_list.take` and `std_list.drop` end-to-end. Both are
/// index-driven recursive ADT-pattern fns — the first `std_list`
/// combinators that pair `if (<= n 0)` Int-arithmetic guards with a
/// recursive Cons/Nil match. Property protected: the `if` base-case
@@ -534,7 +535,7 @@ fn std_list_more_demo() {
assert_eq!(lines, vec!["0", "3", "5", "5", "3", "0"]);
}
/// Iter 16b.1: local recursive `let`. Property protected: a
/// local recursive `let`. Property protected: a
/// `(let-rec ...)` term in a fn body whose recursive body has no
/// captures from the enclosing scope is lifted by the 16a desugar
/// pass to a synthetic top-level fn (`<name>$lr_N`), and the
@@ -552,7 +553,7 @@ fn local_rec_factorial_demo() {
assert_eq!(lines, vec!["1", "6", "120"]);
}
/// Iter 16b.2: LetRec capture of a fn-param (path-1 safe subset).
/// LetRec capture of a fn-param (path-1 safe subset).
/// Property protected: the desugar pass lifts a LetRec whose body
/// captures one or more enclosing-scope names (here: `n` from
/// `sum_below`'s params) into a synthetic top-level fn whose
@@ -569,7 +570,7 @@ fn local_rec_capture_demo() {
assert_eq!(lines, vec!["0", "10", "45"]);
}
/// Iter 16b.3: LetRec capture of a `Term::Let`-bound name. Property
/// LetRec capture of a `Term::Let`-bound name. Property
/// protected: the desugar pass leaves a LetRec whose only outside-
/// scope captures are Let-bound (type unknown until typecheck) in
/// place; the post-typecheck `lift_letrecs` pass in `ailang-check`
@@ -587,7 +588,7 @@ fn local_rec_let_capture_demo() {
assert_eq!(lines, vec!["0", "5", "9"]);
}
/// Iter 16b.4: LetRec capture of a Match-arm pattern binding.
/// LetRec capture of a Match-arm pattern binding.
/// Property protected: the desugar pass leaves a LetRec whose
/// captures are `Pattern::Ctor`-Var-bound (here `threshold` and
/// `n`, both fields of `MkPair` in `Pair Int Int`) in place; the
@@ -607,7 +608,7 @@ fn local_rec_match_capture_demo() {
assert_eq!(lines, vec!["0", "5", "9"]);
}
/// Iter 16b.5: LetRec name as a value in the in-clause (no capture).
/// LetRec name as a value in the in-clause (no capture).
/// Property protected: the desugar pass detects a non-callee use of
/// the LetRec name in `in_term` and wraps the rewritten in-term in
/// `(let factorial (lam ...) ...)` whose lam eta-expands the lifted
@@ -623,7 +624,7 @@ fn local_rec_as_value_demo() {
assert_eq!(lines, vec!["120"]);
}
/// Iter 16b.5: LetRec name as a value in the in-clause WITH capture.
/// LetRec name as a value in the in-clause WITH capture.
/// Property protected: the eta-Lam wrap composes correctly with the
/// 16b.2 capture-augmentation. The lifted fn has signature
/// `factorial_plus$lr_N(n: Int, base: Int) -> Int`; the eta-Lam
@@ -639,7 +640,7 @@ fn local_rec_as_value_capture_demo() {
assert_eq!(lines, vec!["1320", "12120"]);
}
/// Iter 16b.6: LetRec inside a polymorphic enclosing fn.
/// LetRec inside a polymorphic enclosing fn.
/// Property protected: when the enclosing fn is `Forall(a). Fn(...)`,
/// the desugar/lift pipeline now (a) enters fn-params as `KnownType`
/// (not `LetBound`) so a LetRec inside CAN capture them, and (b)
@@ -667,7 +668,7 @@ fn poly_rec_capture_demo() {
assert_eq!(lines, vec!["5", "false"]);
}
/// Iter 16b.7: nested LetRec where the inner one captures the OUTER
/// nested LetRec where the inner one captures the OUTER
/// LetRec's PARAMS (not its name).
///
/// Property protected: when an inner LetRec lifts inside an outer
@@ -868,7 +869,7 @@ fn workspace_lists_imported_modules() {
);
let modules = v["modules"].as_array().expect("modules must be array");
// Iter 23.1: the loader auto-injects the `prelude` module, so
// the loader auto-injects the `prelude` module, so
// the count is the user's two modules plus prelude.
assert_eq!(modules.len(), 3, "expected 3 modules: {stdout}");
@@ -930,7 +931,7 @@ fn ordering_match_via_prelude_prints_1() {
assert_eq!(stdout, "1\n");
}
/// Iter 23.3 Task 4 (resumed in ct.4): the end-to-end
/// the end-to-end
/// demonstration that the iter-23.1 cross-module Type::Con
/// mismatch bug is closed. `examples/compare_primitives_smoke.ail.json`
/// imports prelude, calls `compare` on three pairs each of
@@ -949,7 +950,7 @@ fn compare_primitives_smoke_prints_1_2_3_thrice() {
assert_eq!(stdout, "1\n2\n3\n1\n2\n3\n1\n2\n3\n");
}
/// Iter 23.2: end-to-end coverage for the auto-loaded `Eq` class
/// end-to-end coverage for the auto-loaded `Eq` class
/// and its three primitive instances. The fixture calls `eq` on
/// Int / Bool / Str with both an equal and an unequal pair each;
/// the monomorphiser synthesises `eq__Int` / `eq__Bool` /
@@ -1272,7 +1273,7 @@ fn check_json_unbound_var() {
);
}
/// Iter 16c: literal patterns at top level and inside Ctor
/// literal patterns at top level and inside Ctor
/// sub-patterns. Property protected: the 16a desugar pass rewrites
/// every `Pattern::Lit` to a `Term::If` against the scrutinee/field
/// before either typecheck or codegen sees it. Without 16c, the
@@ -1281,7 +1282,7 @@ fn check_json_unbound_var() {
/// the path. The fixture exercises both sites: `classify` (top-
/// level lit arms) and `categorize_first` (nested lit-in-Ctor).
///
/// Iter 16d update: the trailing `(case _ 0)` arm of
/// the trailing `(case _ 0)` arm of
/// `categorize_first` was removed when the chain machinery's
/// terminator switched from a `Unit` literal to the polymorphic
/// `__unreachable__` builtin. Output is unchanged — this test
@@ -1294,7 +1295,7 @@ fn lit_pat_demo() {
assert_eq!(lines, vec!["100", "200", "999", "-1", "0", "7"]);
}
/// Iter 16d: `__unreachable__` as an explicit user-callable
/// `__unreachable__` as an explicit user-callable
/// primitive. Property protected: a polymorphic `forall a. a` value
/// reference (a) typechecks against any expected type at the use
/// site (here `Int`), and (b) codegen lowers it to LLVM
@@ -1309,7 +1310,7 @@ fn unreachable_demo() {
assert_eq!(lines, vec!["4", "5"]);
}
/// Iter 18a: `(borrow T)` and `(own T)` mode annotations on
/// `(borrow T)` and `(own T)` mode annotations on
/// fn-type parameters. Properties protected:
/// (1) the parser accepts `(borrow ...)` and `(own ...)` only as
/// wrappers in fn-type param/ret slots and round-trips them
@@ -1366,7 +1367,7 @@ fn borrow_own_demo_modes_are_metadata_only() {
assert_eq!(lines, vec!["3", "6"]);
}
/// Iter 16e: polymorphic `==`. Properties protected:
/// polymorphic `==`. Properties protected:
/// (1) `==` typechecks at Int / Bool / Str / Unit (the fixture
/// uses every supported case directly via `(app == ...)`);
/// (2) codegen dispatches each arg-type to the right LLVM shape
@@ -1392,7 +1393,7 @@ fn eq_demo() {
);
}
/// Iter 18b: --alloc=rc routes allocation through ailang_rc_alloc
/// --alloc=rc routes allocation through ailang_rc_alloc
/// (8-byte refcount header, libc malloc backing). With no inc/dec
/// emission yet (18c work), programs leak under this mode but must
/// still produce correct stdout — that's the validation 18b ships.
@@ -1406,7 +1407,7 @@ fn alloc_rc_produces_same_stdout_as_gc() {
assert_eq!(stdout_rc.trim(), "42");
}
/// Iter 18c.1: `Term::Clone` is a pure schema addition. In 18c.1 the
/// `Term::Clone` is a pure schema addition. In 18c.1 the
/// wrapper is identity for both typechecker (same type as inner) and
/// codegen (same SSA reg, no extra IR) — programs that contain
/// `(clone X)` produce the same stdout they would have produced
@@ -1418,7 +1419,7 @@ fn clone_demo_is_identity_in_18c1() {
assert_eq!(stdout.trim(), "42");
}
/// Iter 18d.2: under `--alloc=rc`, `Term::ReuseAs { source, body =
/// under `--alloc=rc`, `Term::ReuseAs { source, body =
/// Term::Ctor }` lowers to a runtime refcount-1 dispatch — when the
/// source's box is unique we overwrite it in place (skipping the
/// alloc + cascade-dec round-trip); otherwise we fall back to a
@@ -1497,7 +1498,7 @@ fn reuse_as_demo_under_rc_uses_inplace_rewrite() {
root_dir: ws.root_dir.clone(),
registry: ws.registry.clone(),
};
// Iter rpe.1: post-print-migration, fixtures use `(app print x)`
// post-print-migration, fixtures use `(app print x)`
// which monomorphises to `print__<T>`. Adding the mono pass here
// mirrors `ail build`'s pipeline (desugar -> lift -> mono ->
// lower); without it, lowering errors with `UnknownVar("print")`.
@@ -1537,7 +1538,7 @@ fn reuse_as_demo_under_rc_uses_inplace_rewrite() {
!reuse_arm.contains("@ailang_rc_alloc"),
"reuse arm must not call @ailang_rc_alloc (the slot is reused in place). Reuse arm IR was:\n{reuse_arm}"
);
// Iter 18d.3: in this canonical fixture the pattern `(Cons h t)`
// in this canonical fixture the pattern `(Cons h t)`
// moves the only pointer-typed slot of the source's old ctor
// (slot 1, the tail; slot 0 is Int and never a dec candidate).
// The reuse arm therefore must emit NEITHER a per-field drop
@@ -1556,7 +1557,7 @@ fn reuse_as_demo_under_rc_uses_inplace_rewrite() {
);
}
/// Iter 18b: extends `alloc_rc_produces_same_stdout_as_gc` to a larger
/// extends `alloc_rc_produces_same_stdout_as_gc` to a larger
/// fixture (`std_list_demo`) so more allocation sites — folds, maps,
/// cross-module ctors — are exercised under `--alloc=rc`. Same
/// invariant: stdout must be byte-identical to the `gc` build.
@@ -1571,7 +1572,7 @@ fn alloc_rc_matches_gc_on_std_list_demo() {
);
}
/// Iter 18c.3: codegen actually emits `ailang_rc_dec` at end-of-scope
/// codegen actually emits `ailang_rc_dec` at end-of-scope
/// for trackable RC binders under `--alloc=rc`. This is the first
/// fixture where the `dec` path runs at runtime — `b` is bound to a
/// heap-allocated `MkBox`, the box is read via `match` (a borrow,
@@ -1605,7 +1606,7 @@ fn alloc_rc_emits_dec_for_unique_let_bound_box() {
assert_eq!(stdout_gc, stdout_rc, "alloc=rc must match alloc=gc on rc_box_drop");
}
/// Iter 18c.4: per-type drop fns + recursive `dec` cascade. Builds a
/// per-type drop fns + recursive `dec` cascade. Builds a
/// 5-element `IntList` and reduces it via `sum_list`. Under
/// `--alloc=rc` the codegen emits `define void @drop_<m>_IntList`
/// whose `Cons` arm recursively calls itself on the `tail` field —
@@ -1634,7 +1635,7 @@ fn alloc_rc_recursive_list_sum() {
);
}
/// Iter 18c.4 / 18d.4: exercise the recursive drop cascade at
/// exercise the recursive drop cascade at
/// runtime AND the arm-close pattern-binder dec.
///
/// 18c.4 originally used this fixture to lock in the recursive
@@ -1679,8 +1680,8 @@ fn alloc_rc_borrow_only_recursive_list_drop() {
"alloc=rc must match alloc=gc on rc_list_drop_borrow"
);
// Iter 18d.4 IR-shape assertion: the Cons arm of main's match
// emits a drop on `t` after lowering the body. We re-lower under
// IR-shape assertion: the Cons arm of main's match emits a drop
// on `t` after lowering the body. We re-lower under
// rc and inspect main's IR — the arm sequence must contain
// call void @ai/print_int(...) (the `print h`)
// call void @drop_rc_list_drop_borrow_IntList(ptr %v...) (NEW: 18d.4 t-drop)
@@ -1703,7 +1704,7 @@ fn alloc_rc_borrow_only_recursive_list_drop() {
root_dir: ws.root_dir.clone(),
registry: ws.registry.clone(),
};
// Iter rpe.1: post-print-migration, fixtures use `(app print x)`
// post-print-migration, fixtures use `(app print x)`
// which monomorphises to `print__<T>`. Adding the mono pass here
// mirrors `ail build`'s pipeline (desugar -> lift -> mono ->
// lower); without it, lowering errors with `UnknownVar("print")`.
@@ -1731,7 +1732,7 @@ fn alloc_rc_borrow_only_recursive_list_drop() {
);
}
/// Iter 18d.3: move-aware pattern bindings — let-close inlines a
/// move-aware pattern bindings — let-close inlines a
/// per-field dec sequence that skips moved slots and dec's non-moved
/// (e.g. wildcarded) pointer-typed slots.
///
@@ -1785,7 +1786,7 @@ fn alloc_rc_partial_drop_skips_moved_keeps_wildcarded() {
root_dir: ws.root_dir.clone(),
registry: ws.registry.clone(),
};
// Iter rpe.1: post-print-migration, fixtures use `(app print x)`
// post-print-migration, fixtures use `(app print x)`
// which monomorphises to `print__<T>`. Adding the mono pass here
// mirrors `ail build`'s pipeline (desugar -> lift -> mono ->
// lower); without it, lowering errors with `UnknownVar("print")`.
@@ -1828,7 +1829,7 @@ fn alloc_rc_partial_drop_skips_moved_keeps_wildcarded() {
);
}
/// Iter 18d.4: Own-param dec at fn return. Symmetric to 18c.3/18c.4's
/// Own-param dec at fn return. Symmetric to 18c.3/18c.4's
/// `Term::Let`-scope-close drop emission and 18d.4's arm-close
/// pattern-binder dec, fired at the lexical close of a fn body. The
/// `head_or_zero` fn declares its only parameter `xs` as
@@ -1881,7 +1882,7 @@ fn alloc_rc_own_param_dec_at_fn_return() {
root_dir: ws.root_dir.clone(),
registry: ws.registry.clone(),
};
// Iter rpe.1: post-print-migration, fixtures use `(app print x)`
// post-print-migration, fixtures use `(app print x)`
// which monomorphises to `print__<T>`. Adding the mono pass here
// mirrors `ail build`'s pipeline (desugar -> lift -> mono ->
// lower); without it, lowering errors with `UnknownVar("print")`.
@@ -1947,7 +1948,7 @@ fn alloc_rc_own_param_dec_at_fn_return() {
);
}
/// Iter 18e: `(drop-iterative)` opt-in annotation. The fixture
/// `(drop-iterative)` opt-in annotation. The fixture
/// declares `IntList` with `(drop-iterative)` and runs an N=1,000,000
/// cell list through `head_or_zero`. The fn signature is
/// `(own (con IntList))` — 18d.4 emits a drop on the param at fn
@@ -1987,7 +1988,7 @@ fn alloc_rc_drop_iterative_handles_million_cell_list() {
);
}
/// Iter 18e: IR-shape signature of `(drop-iterative)`. The drop fn
/// IR-shape signature of `(drop-iterative)`. The drop fn
/// for the annotated type contains a worklist loop (`br label
/// %loop_head`, the runtime-helper calls, a backedge from each ctor
/// arm) and does NOT contain a recursive `call void @drop_<m>_<T>(...)`
@@ -2013,7 +2014,7 @@ fn iter18e_drop_iterative_emits_worklist_body_no_self_recursion() {
root_dir: ws.root_dir.clone(),
registry: ws.registry.clone(),
};
// Iter rpe.1: post-print-migration, fixtures use `(app print x)`
// post-print-migration, fixtures use `(app print x)`
// which monomorphises to `print__<T>`. Adding the mono pass here
// mirrors `ail build`'s pipeline (desugar -> lift -> mono ->
// lower); without it, lowering errors with `UnknownVar("print")`.
@@ -2071,7 +2072,7 @@ fn iter18e_drop_iterative_emits_worklist_body_no_self_recursion() {
);
}
/// Iter 18e: control — the same fixture WITHOUT the
/// control — the same fixture WITHOUT the
/// `(drop-iterative)` annotation must still emit the recursive
/// 18c.4 body. We synthesise the unannotated module on the fly
/// (mutating the workspace's parsed AST) so this test is independent
@@ -2103,7 +2104,7 @@ fn iter18e_no_annotation_keeps_recursive_drop_body() {
root_dir: ws.root_dir.clone(),
registry: ws.registry.clone(),
};
// Iter rpe.1: post-print-migration, fixtures use `(app print x)`
// post-print-migration, fixtures use `(app print x)`
// which monomorphises to `print__<T>`. Adding the mono pass here
// mirrors `ail build`'s pipeline (desugar -> lift -> mono ->
// lower); without it, lowering errors with `UnknownVar("print")`.
@@ -2137,7 +2138,7 @@ fn iter18e_no_annotation_keeps_recursive_drop_body() {
);
}
/// Iter 18d.4 regression: pattern-binder dec at arm close (Iter A in
/// pattern-binder dec at arm close (Iter A in
/// the 18d.4 emission seam) was firing on pattern-bound pointer
/// fields whose enclosing fn is Implicit-mode. The dec freed memory
/// the caller still referenced, producing refcount underflow or
@@ -2174,7 +2175,7 @@ fn alloc_rc_pattern_bind_in_implicit_fn_does_not_dec_borrowed_children() {
);
}
/// Iter 18g.0: build the example under `--alloc=rc`, run with
/// build the example under `--alloc=rc`, run with
/// `AILANG_RC_STATS=1`, and return the parsed `(allocs, frees, live)`
/// triple from the runtime's atexit summary on stderr. The summary
/// line shape is fixed by `runtime/rc.c`'s `ailang_rc_stats_atexit`:
@@ -2246,7 +2247,7 @@ fn build_and_run_with_rc_stats(example: &str) -> (String, u64, u64, i64) {
)
}
/// Iter 18g.1 regression: explicit-mode tail-recursive list-sum must
/// explicit-mode tail-recursive list-sum must
/// not leak the LCons outer cells.
///
/// Background: 18f.2's tail-latency bench found that
@@ -2287,7 +2288,7 @@ fn alloc_rc_explicit_mode_tail_sum_does_not_leak_outer_cells() {
);
}
/// Iter 18g.2 regression: a `let`-binder whose value is the result of
/// a `let`-binder whose value is the result of
/// an Own-returning function call must be dropped at let-scope close.
///
/// Background: 18c.3's `is_rc_heap_allocated` returns `false` for any
@@ -2324,7 +2325,7 @@ fn alloc_rc_let_binder_for_owned_returning_app_drops_at_scope_close() {
);
}
/// Iter 18g tidy negative-coverage: a let-binder whose value is the
/// a let-binder whose value is the
/// result of an `Implicit`-ret-mode (default, unannotated) call must
/// NOT be trackable. Implicit is the back-compat lane that 18c.3
/// documented as "params don't get any dec — leak rather than mis-
@@ -2364,7 +2365,7 @@ fn alloc_rc_let_binder_for_implicit_returning_app_does_not_drop() {
);
}
/// Iter 18g tidy follow-up: let-alias-aware mode propagation.
/// let-alias-aware mode propagation.
///
/// 18d.4 Iter A's fix gated arm-close pattern-binder dec on the
/// scrutinee's `current_param_modes` lookup, but only fn-params
@@ -2402,8 +2403,8 @@ fn alloc_rc_let_alias_of_implicit_param_does_not_dec_borrowed_children() {
);
}
/// Iter 18g.tidy.fu2 regression — RED-then-GREEN for the
/// dynamic-tag partial-drop carve-out at fn-return.
/// Regression — RED-then-GREEN for the dynamic-tag partial-drop
/// carve-out at fn-return.
///
/// Three carve-out sites previously fell back to a shallow
/// `ailang_rc_dec` when a binder's `moved_slots` was non-empty
@@ -2451,8 +2452,7 @@ fn alloc_rc_partial_drop_at_fn_return_does_not_leak_unmoved_fields() {
);
}
/// Iter 18g.tidy.fu2 regression — site 2: Iter A arm-close
/// pattern-binder dec (match_lower.rs).
/// Arm-close pattern-binder dec (match_lower.rs).
///
/// An outer match's arm-bound pattern-binder may itself be the
/// scrutinee of an inner match that moves out some of its fields.
@@ -2489,7 +2489,7 @@ fn alloc_rc_partial_drop_at_arm_close_does_not_leak_unmoved_fields() {
);
}
/// Iter 18g.tidy.fu2 regression — site 3: let-close for an
/// let-close for an
/// App-bound binder (drop.rs `emit_inlined_partial_drop`
/// non-Ctor branch).
///
@@ -2523,7 +2523,7 @@ fn alloc_rc_partial_drop_at_app_let_close_does_not_leak_unmoved_fields() {
);
}
/// Iter 20d: `ail merge-prose` reads two files and prints a prompt
/// `ail merge-prose` reads two files and prints a prompt
/// that frames the prose-round-trip task for an external LLM. Smoke
/// test: writes two temp inputs, invokes the binary, asserts exit 0
/// and that the captured stdout carries the role-statement landmark
@@ -2537,7 +2537,7 @@ fn merge_prose_prints_framed_prompt() {
std::fs::create_dir_all(&tmp).unwrap();
let orig_path = tmp.join("orig.ail.json");
let prose_path = tmp.join("edited.prose.txt");
// Iter 20f: merge-prose now loads the module via ailang_core,
// merge-prose now loads the module via ailang_core,
// renders to Form-A, and embeds *that* in the prompt — so the
// input must be a well-formed ailang/v0 JSON-AST that
// load_module accepts. The schema, name, and an empty defs list
@@ -2681,7 +2681,7 @@ fn ail_run_accepts_ail_source_with_same_stdout_as_ail_json() {
);
}
/// Iter hs.4: `int_to_str(42)` prints "42\n" through the standard
/// `int_to_str(42)` prints "42\n" through the standard
/// `do io/print_str(...)` path. Smoke pin for the heap-Str builtin
/// wired in hs.4 — exercises checker (signature install), codegen
/// (lower_app arm + IR-header declare + is_static_callee whitelist),
@@ -2696,7 +2696,7 @@ fn int_to_str_smoke() {
assert_eq!(out, "42\n");
}
/// Iter hs.4: `float_to_str(3.5)` prints a libc-%g-conformant
/// `float_to_str(3.5)` prints a libc-%g-conformant
/// rendering of 3.5. The runtime's `ailang_float_to_str` uses
/// `snprintf(..., "%g", x)` with no locale call, so the C locale
/// default applies and the dev target's glibc produces the three
@@ -2744,11 +2744,11 @@ fn str_field_in_adt_drops_heap_str_correctly() {
assert_eq!(live, 0, "no leaked RC slabs allowed; live={live}");
}
/// Iter eob.1 / rpe.1: primitive-Int passed to the polymorphic
/// `print` helper. Pre-rpe.1 the fixture used `(do io/print_int n)`
/// directly and the iter-eob.1 "Term::Do args = Borrow" rule meant
/// primitive-Int passed to the polymorphic
/// `print` helper. Pre-per-type-print-retirement the fixture used `(do io/print_int n)`
/// directly and the heap-Str-ABI "Term::Do args = Borrow" rule meant
/// zero RC traffic on the unboxed Int (allocs == 0, frees == 0).
/// Post-rpe.1, `print n` desugars through `Show Int.show` →
/// Post-per-type-print-retirement, `print n` desugars through `Show Int.show` →
/// `int_to_str` (heap-Str alloc, `ret_mode: Own`) → `io/print_str`
/// → slab drops at scope close. Pin shifts to the new canonical
/// post-iter shape: exactly one heap-Str slab cycle per `print`
@@ -2761,14 +2761,15 @@ fn int_arg_to_effect_op_does_not_rc_track() {
let (stdout, allocs, frees, live) =
build_and_run_with_rc_stats("int_to_print_int_borrow.ail");
assert_eq!(stdout, "7\n");
// Post-rpe.1: print n for n : Int allocates one heap-Str slab
// via Show Int → int_to_str, frees it at scope close. Spec §E.
// After the per-type-print-op retirement, `print n` for n : Int
// allocates one heap-Str slab via Show Int → int_to_str, frees
// it at scope close. Spec §E.
assert_eq!(allocs, 1, "expected exactly one Show-Int heap-Str slab; got allocs={allocs}");
assert_eq!(frees, 1, "expected exactly one free; got frees={frees}");
assert_eq!(live, 0, "no leaked RC slabs allowed; live={live}");
}
/// Iter eob.1: heap-Str let-binder consumed by two `io/print_str`
/// heap-Str let-binder consumed by two `io/print_str`
/// calls in sequence. Pins the linearity-side consequence of the new
/// rule: under the old Position::Consume walk for Term::Do args, the
/// second print would have triggered `use-after-consume`. Under the
@@ -2786,7 +2787,7 @@ fn heap_str_repeated_print_balances_rc_stats() {
assert_eq!(live, 0, "no leaked heap-Str slabs allowed; live={live}");
}
/// Iter 24.1: `bool_to_str(true)` bound to a let, consumed by
/// `bool_to_str(true)` bound to a let, consumed by
/// `io/print_str`. The heap-Str slab from `ailang_bool_to_str`
/// rides the same rc_header + ailang_rc_dec path as int_to_str's
/// output — `allocs == 1, frees == 1, live == 0` and stdout is
@@ -2802,7 +2803,7 @@ fn bool_to_str_drop_balances_rc_stats() {
assert_eq!(live, 0, "no leaked heap-Str slabs allowed; live={live}");
}
/// Iter 24.1: stdout-smoke for the true branch — same fixture as
/// stdout-smoke for the true branch — same fixture as
/// `bool_to_str_drop_balances_rc_stats` but checked here without
/// the RC-stats overhead. Pins the byte content of `ailang_bool_to_str`'s
/// "true" slab.
@@ -2812,7 +2813,7 @@ fn bool_to_str_emits_true_branch() {
assert_eq!(out, "true\n");
}
/// Iter 24.1: stdout-smoke for the false branch. Pins the byte
/// stdout-smoke for the false branch. Pins the byte
/// content of `ailang_bool_to_str`'s "false" slab.
#[test]
fn bool_to_str_emits_false_branch() {
@@ -2820,7 +2821,7 @@ fn bool_to_str_emits_false_branch() {
assert_eq!(out, "false\n");
}
/// Iter 24.1: `str_clone("hello")` bound to a let, consumed by
/// `str_clone("hello")` bound to a let, consumed by
/// `io/print_str`. The heap-Str clone allocates a fresh slab (via
/// str_alloc) and the let-binder drops it at scope close.
/// `allocs == 1, frees == 1, live == 0`; stdout is `hello\n` (puts
@@ -2835,7 +2836,7 @@ fn str_clone_drop_balances_rc_stats() {
assert_eq!(live, 0, "no leaked heap-Str slabs allowed; live={live}");
}
/// Iter 24.1: cross-realisation invariant — `str_clone` works
/// cross-realisation invariant — `str_clone` works
/// uniformly on heap-Str input (`int_to_str 42`'s output) AND on
/// static-Str input (literal `"abc"`). Both clones print their
/// input bytes; RC stats account for three heap-Str slabs (one