Files
AILang/crates/ail/tests/cut55_cutover_surface.rs
T
Brummel 1be2c1f145 docs: fix doc_lazy_continuation across the workspace
Eighteen markdown list items in module- and item-doc comments had
continuation lines that were not indented to align under the item
text, so rustdoc rendered them as separate paragraphs and broke the
list. Corrected the indentation (and, at two sites where a general
wrap-up sentence followed a sublist, separated it with a blank doc
line so it does not mis-attach to the last item). Doc whitespace only;
no prose reworded, no code changed.

Workspace clippy is now warning-clean.
2026-06-02 02:34:40 +02:00

167 lines
7.2 KiB
Rust

//! Regression net for the #55 ParamMode={Own,Borrow} totality cutover
//! (commit `76b21c0`, spec 0062 / plan 0121) and its #58 follow-up
//! (`b129af1`, sub-binder borrow inheritance).
//!
//! Property protected: the post-#55-cutover ownership surface accepts the
//! legitimate authoring forms and rejects the unsound / over-strict ones,
//! AT THE RIGHT PIPELINE STAGE. Concretely, for each fixture under
//! examples/fieldtest/cut55_*.ail, `ail check` must:
//! - exit 0 for the legitimate forms (read-only borrow, consume own,
//! value-typed own, clone-to-return escape hatch, and the
//! borrow-traverse-and-rebuild that must NOT trip the #58 check);
//! - reject the unsound / over-strict forms with the EXACT diagnostic
//! code, at the stage that owns the rule:
//! * bare fn-type slot -> `surface-parse-error` (parser stage),
//! * borrow over value type -> `borrow-over-value` (signature),
//! * borrowed return -> `borrow-return-not-permitted` (sig),
//! * consume while borrowed -> `consume-while-borrowed` (linearity),
//! and emit the `over-strict-mode` advisory (a warning, exit 0) on a
//! true read-only `(own)` heap param.
//!
//! Coupling discipline: the assertions key on the bracketed diagnostic
//! CODE (e.g. `[consume-while-borrowed]`) appearing in stderr, never on
//! the surrounding message wording — that wording is slated to change
//! (the borrow-return and consume-while-borrowed friction tidy-ups noted
//! in the spec-0065 findings) and the regression net must survive it.
//! Each row carries the fixture stem so a failure names which authoring
//! form regressed.
//!
//! Without this test the twelve cut55_* fixtures are dead files that the
//! build never executes; a regression in the totality check (a relaxed
//! reject, a moved stage, a re-introduced default) would land silently.
use std::path::{Path, PathBuf};
use std::process::Command;
fn ail_bin() -> &'static str {
env!("CARGO_BIN_EXE_ail")
}
fn fixture(stem: &str) -> PathBuf {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
workspace
.join("examples")
.join("fieldtest")
.join(format!("{stem}.ail"))
}
/// What `ail check` must do with a fixture. Codes name the bracketed
/// diagnostic tag, never the message prose.
enum Expect {
/// exit 0, no warning expected (legitimate form).
Accept,
/// exit 0, but the `over-strict-mode` advisory warning must appear.
AcceptWithWarning(&'static str),
/// exit non-zero, and `[code]` must appear in stderr.
Reject(&'static str),
}
fn check(stem: &str) -> (bool, String) {
let out = Command::new(ail_bin())
.args(["check", fixture(stem).to_str().unwrap()])
.output()
.expect("ail check failed to run");
let stderr = String::from_utf8(out.stderr).expect("stderr utf8");
(out.status.success(), stderr)
}
#[test]
fn cut55_cutover_surface_accepts_and_rejects_at_the_right_stage() {
// (fixture stem, expectation). The stem is in every failure message
// so a red row names the regressed authoring form.
let table: &[(&str, Expect)] = &[
// --- decision (a): borrow vs own on a heap value -----------------
("cut55_1_readonly_length", Expect::Accept),
// over-strict own that does NOT trip the lint: the recursive call
// consumes the heap tail into an own self-param, so the lint's
// consume_count==0 premise fails. Still a sound (if strict)
// annotation -> accepted, silent.
("cut55_1b_overstrict_own", Expect::Accept),
// true read-only own: nothing consumed -> the advisory fires.
(
"cut55_1c_overstrict_isempty",
Expect::AcceptWithWarning("over-strict-mode"),
),
// --- decision (b): consume vs borrow -----------------------------
("cut55_2_consume_map", Expect::Accept),
// false-positive guard for #58: borrow-traverse-and-rebuild, the
// recursive param is (borrow) so the tail is never consumed.
("cut55_2b_borrow_traversal_clean", Expect::Accept),
// #58 fix: tail sub-binder of a borrowed scrutinee moved into an
// own sink -> reject at the linearity check.
(
"cut55_2c_consume_borrow_reject",
Expect::Reject("consume-while-borrowed"),
),
// whole borrowed param moved into an own sink -> reject.
(
"cut55_2d_consume_borrow_whole",
Expect::Reject("consume-while-borrowed"),
),
// --- decision (c): value-typed params ----------------------------
("cut55_3_value_param", Expect::Accept),
// borrow over an unboxed value type -> reject at the signature.
(
"cut55_3b_borrow_over_value",
Expect::Reject("borrow-over-value"),
),
// --- decision (d): borrowed return -------------------------------
// a (borrow) return is refcount-invisible -> reject at the signature.
(
"cut55_4_borrow_return_reject",
Expect::Reject("borrow-return-not-permitted"),
),
// the clone escape hatch lifts a borrowed field to an owned return.
("cut55_4b_clone_to_return", Expect::Accept),
// --- the cutover's core promise: no defaulted mode ---------------
// a bare fn-type slot is rejected at the PARSER stage, not linearity.
(
"cut55_5_bare_slot_reject",
Expect::Reject("surface-parse-error"),
),
];
for (stem, expect) in table {
let (ok, stderr) = check(stem);
match expect {
Expect::Accept => {
assert!(
ok,
"fixture `{stem}` must be ACCEPTED (exit 0) by the \
post-#55 ownership surface, but `ail check` rejected \
it. stderr:\n{stderr}"
);
}
Expect::AcceptWithWarning(code) => {
assert!(
ok,
"fixture `{stem}` must exit 0 (the `{code}` advisory \
is a warning, not an error), but `ail check` exited \
non-zero. stderr:\n{stderr}"
);
assert!(
stderr.contains(&format!("[{code}]")),
"fixture `{stem}` must emit the `[{code}]` advisory \
warning, but it did not appear in stderr. stderr:\n{stderr}"
);
}
Expect::Reject(code) => {
assert!(
!ok,
"fixture `{stem}` must be REJECTED (non-zero exit) by \
the post-#55 ownership surface, but `ail check` \
accepted it (exit 0). stderr:\n{stderr}"
);
assert!(
stderr.contains(&format!("[{code}]")),
"fixture `{stem}` must be rejected with diagnostic code \
`[{code}]`, but that code did not appear in stderr. \
The reject may have moved to a different stage or code. \
stderr:\n{stderr}"
);
}
}
}
}