|
|
|
@@ -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
|
|
|
|
|